From 579f129e45071261819a54ffe772d2c463eeb656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E4=BA=88=E9=A1=BA?= <ys_37677@126.com> Date: Fri, 18 May 2018 16:29:10 +0800 Subject: [PATCH 01/12] Reformat --- src/CMakeLists.txt | 124 +- src/autocomplete.cc | 318 +- src/autocomplete.h | 99 +- src/buildsystem/buildsystem.h | 20 + src/buildsystem/cmake.cc | 386 ++ src/buildsystem/cmake.h | 41 + src/buildsystem/meson.cc | 123 + src/buildsystem/meson.h | 18 + src/cmake.cc | 378 -- src/cmake.h | 30 - src/compile_commands.cc | 290 +- src/compile_commands.h | 31 +- src/config.cc | 347 +- src/config.h | 219 +- src/ctags.cc | 350 +- src/ctags.h | 39 +- src/debug_lldb.cc | 862 ++--- src/debug_lldb.h | 171 +- src/dialogs.cc | 131 +- src/dialogs.h | 38 +- src/dialogs_unix.cc | 30 +- src/dialogs_win.cc | 252 +- src/directories.cc | 1323 +++---- src/directories.h | 159 +- src/dispatcher.cc | 40 +- src/dispatcher.h | 34 +- src/documentation_cppreference.cc | 72 +- src/documentation_cppreference.h | 9 +- src/entrybox.cc | 161 +- src/entrybox.h | 94 +- src/files.h | 59 +- src/filesystem.cc | 365 +- src/filesystem.h | 79 +- src/git.cc | 479 +-- src/git.h | 202 +- src/info.cc | 58 +- src/info.h | 22 +- src/juci.cc | 212 +- src/juci.h | 18 +- src/menu.cc | 56 +- src/menu.h | 40 +- src/meson.cc | 117 - src/meson.h | 15 - src/notebook.cc | 1139 +++--- src/notebook.h | 175 +- src/project.cc | 968 +++--- src/project.h | 350 +- src/project_build.cc | 190 +- src/project_build.h | 130 +- src/selection_dialog.cc | 800 ++--- src/selection_dialog.h | 189 +- src/source.cc | 5408 +++++++++++++++-------------- src/source.h | 369 +- src/source_base.cc | 606 ++-- src/source_base.h | 190 +- src/source_clang.cc | 3395 +++++++++--------- src/source_clang.h | 225 +- src/source_diff.cc | 634 ++-- src/source_diff.h | 116 +- src/source_language_protocol.cc | 2728 ++++++++------- src/source_language_protocol.h | 250 +- src/source_spellcheck.cc | 891 ++--- src/source_spellcheck.h | 87 +- src/terminal.cc | 721 ++-- src/terminal.h | 85 +- src/tooltips.cc | 429 +-- src/tooltips.h | 92 +- src/usages_clang.cc | 1266 +++---- src/usages_clang.h | 254 +- src/window.cc | 3294 +++++++++--------- src/window.h | 71 +- 71 files changed, 16954 insertions(+), 16009 deletions(-) create mode 100644 src/buildsystem/buildsystem.h create mode 100644 src/buildsystem/cmake.cc create mode 100644 src/buildsystem/cmake.h create mode 100644 src/buildsystem/meson.cc create mode 100644 src/buildsystem/meson.h delete mode 100644 src/cmake.cc delete mode 100644 src/cmake.h delete mode 100644 src/meson.cc delete mode 100644 src/meson.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9c32003c..b988f114 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,75 +1,77 @@ # Files used both in ../src and ../tests + +file(GLOB JUCI_SRC_BUILDSYSTEMS buildsystem/*.*) + set(JUCI_SHARED_FILES - autocomplete.cc - cmake.cc - compile_commands.cc - ctags.cc - dispatcher.cc - documentation_cppreference.cc - filesystem.cc - git.cc - menu.cc - meson.cc - project_build.cc - source.cc - source_base.cc - source_clang.cc - source_diff.cc - source_language_protocol.cc - source_spellcheck.cc - terminal.cc - usages_clang.cc -) -if(LIBLLDB_FOUND) - list(APPEND JUCI_SHARED_FILES debug_lldb.cc) -endif() + ${JUCI_SRC_BUILDSYSTEMS} + autocomplete.cc + compile_commands.cc + ctags.cc + dispatcher.cc + documentation_cppreference.cc + filesystem.cc + git.cc + menu.cc + project_build.cc + source.cc + source_base.cc + source_clang.cc + source_diff.cc + source_language_protocol.cc + source_spellcheck.cc + terminal.cc + usages_clang.cc + ) +if (LIBLLDB_FOUND) + list(APPEND JUCI_SHARED_FILES debug_lldb.cc) +endif () add_library(juci_shared STATIC ${JUCI_SHARED_FILES}) target_link_libraries(juci_shared - ${GTKMM_LIBRARIES} - ${GTKSVMM_LIBRARIES} - ${Boost_LIBRARIES} - ${LIBLLDB_LIBRARIES} - ${ASPELL_LIBRARIES} - ${LIBGIT2_LIBRARIES} - clangmm - tiny-process-library -) + ${GTKMM_LIBRARIES} + ${GTKSVMM_LIBRARIES} + ${Boost_LIBRARIES} + ${LIBLLDB_LIBRARIES} + ${ASPELL_LIBRARIES} + ${LIBGIT2_LIBRARIES} + clangmm + tiny-process-library + ) add_executable(juci - config.cc - dialogs.cc - dialogs_unix.cc - directories.cc - entrybox.cc - info.cc - juci.cc - notebook.cc - project.cc - selection_dialog.cc - tooltips.cc - window.cc -) + config.cc + dialogs.cc + dialogs_unix.cc + directories.cc + entrybox.cc + info.cc + juci.cc + notebook.cc + project.cc + selection_dialog.cc + tooltips.cc + window.cc + ) target_link_libraries(juci juci_shared) install(TARGETS juci RUNTIME DESTINATION bin) -if(${CMAKE_SYSTEM_NAME} MATCHES Linux|.*BSD|DragonFly) - install(FILES "${CMAKE_SOURCE_DIR}/share/juci.desktop" - DESTINATION "${CMAKE_INSTALL_PREFIX}/share/applications") - install(FILES "${CMAKE_SOURCE_DIR}/share/juci.svg" - DESTINATION "${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/scalable/apps") -elseif(APPLE) - install(CODE "execute_process(COMMAND /usr/bin/python ${CMAKE_SOURCE_DIR}/share/set_icon_macos.py ${CMAKE_SOURCE_DIR}/share/juci.png ${CMAKE_INSTALL_PREFIX}/bin/juci)") -endif() +if (${CMAKE_SYSTEM_NAME} MATCHES Linux|.*BSD|DragonFly) + install(FILES "${CMAKE_SOURCE_DIR}/share/juci.desktop" + DESTINATION "${CMAKE_INSTALL_PREFIX}/share/applications") + install(FILES "${CMAKE_SOURCE_DIR}/share/juci.svg" + DESTINATION "${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/scalable/apps") +elseif (APPLE) + install(CODE "execute_process(COMMAND /usr/bin/python ${CMAKE_SOURCE_DIR}/share/set_icon_macos.py ${CMAKE_SOURCE_DIR}/share/juci.png ${CMAKE_INSTALL_PREFIX}/bin/juci)") +endif () # add a target to generate API documentation with Doxygen set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake_modules/") find_package(Plantuml) find_package(Doxygen) -if(DOXYGEN_FOUND) - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY) - add_custom_target(doc - ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "Generating API documentation with Doxygen to ${CMAKE_CURRENT_BINARY_DIR}" VERBATIM - ) -endif(DOXYGEN_FOUND) +if (DOXYGEN_FOUND) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY) + add_custom_target(doc + ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating API documentation with Doxygen to ${CMAKE_CURRENT_BINARY_DIR}" VERBATIM + ) +endif (DOXYGEN_FOUND) diff --git a/src/autocomplete.cc b/src/autocomplete.cc index 8d5b6848..374e25c7 100644 --- a/src/autocomplete.cc +++ b/src/autocomplete.cc @@ -2,178 +2,178 @@ #include "selection_dialog.h" Autocomplete::Autocomplete(Gtk::TextView *view, bool &interactive_completion, guint &last_keyval, bool strip_word) - : view(view), interactive_completion(interactive_completion), strip_word(strip_word), state(State::IDLE) { - view->get_buffer()->signal_changed().connect([this, &last_keyval] { - if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) { - cancel_reparse(); - return; - } - if(!this->view->has_focus()) - return; - if(is_continue_key(last_keyval) && (this->interactive_completion || state != State::IDLE)) - run(); - else { - stop(); - - if(is_restart_key(last_keyval) && this->interactive_completion) - run(); - } - }); - - view->get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { - if(mark->get_name() == "insert") - stop(); - }); + : view(view), interactive_completion(interactive_completion), strip_word(strip_word), state(State::IDLE) { + view->get_buffer()->signal_changed().connect([this, &last_keyval] { + if (CompletionDialog::get() && CompletionDialog::get()->is_visible()) { + cancel_reparse(); + return; + } + if (!this->view->has_focus()) + return; + if (is_continue_key(last_keyval) && (this->interactive_completion || state != State::IDLE)) + run(); + else { + stop(); - view->signal_key_release_event().connect([](GdkEventKey *key) { - if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) { - if(CompletionDialog::get()->on_key_release(key)) - return true; - } - return false; - }, false); + if (is_restart_key(last_keyval) && this->interactive_completion) + run(); + } + }); - view->signal_focus_out_event().connect([this](GdkEventFocus *event) { - stop(); - return false; - }); + view->get_buffer()->signal_mark_set().connect( + [this](const Gtk::TextBuffer::iterator &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { + if (mark->get_name() == "insert") + stop(); + }); + + view->signal_key_release_event().connect([](GdkEventKey *key) { + if (CompletionDialog::get() && CompletionDialog::get()->is_visible()) { + if (CompletionDialog::get()->on_key_release(key)) + return true; + } + return false; + }, false); + + view->signal_focus_out_event().connect([this](GdkEventFocus *event) { + stop(); + return false; + }); } void Autocomplete::run() { - if(run_check()) { - if(!is_processing()) - return; - - if(state == State::CANCELED) - state = State::RESTARTING; - - if(state != State::IDLE) - return; - - state = State::STARTING; - - before_add_rows(); - - if(thread.joinable()) - thread.join(); - auto buffer = view->get_buffer()->get_text(); - auto iter = view->get_buffer()->get_insert()->get_iter(); - auto line_nr = iter.get_line() + 1; - auto column_nr = iter.get_line_index() + 1; - if(strip_word) { - auto pos = iter.get_offset() - 1; - while(pos >= 0 && ((buffer[pos] >= 'a' && buffer[pos] <= 'z') || (buffer[pos] >= 'A' && buffer[pos] <= 'Z') || - (buffer[pos] >= '0' && buffer[pos] <= '9') || buffer[pos] == '_')) { - buffer.replace(pos, 1, " "); - column_nr--; - pos--; - } - } - thread = std::thread([this, line_nr, column_nr, buffer = std::move(buffer)] { - auto lock = get_parse_lock(); - if(!is_processing()) - return; - stop_parse(); - - auto &buffer_raw = const_cast<std::string &>(buffer.raw()); - rows.clear(); - add_rows(buffer_raw, line_nr, column_nr); - - if(is_processing()) { - dispatcher.post([this]() { - after_add_rows(); - if(state == State::RESTARTING) { - state = State::IDLE; - reparse(); - run(); - } - else if(state == State::CANCELED || rows.empty()) { - state = State::IDLE; - reparse(); - } - else { - auto start_iter = view->get_buffer()->get_insert()->get_iter(); - if(prefix.size() > 0 && !start_iter.backward_chars(prefix.size())) { - state = State::IDLE; - reparse(); - return; + if (run_check()) { + if (!is_processing()) + return; + + if (state == State::CANCELED) + state = State::RESTARTING; + + if (state != State::IDLE) + return; + + state = State::STARTING; + + before_add_rows(); + + if (thread.joinable()) + thread.join(); + auto buffer = view->get_buffer()->get_text(); + auto iter = view->get_buffer()->get_insert()->get_iter(); + auto line_nr = iter.get_line() + 1; + auto column_nr = iter.get_line_index() + 1; + if (strip_word) { + auto pos = iter.get_offset() - 1; + while (pos >= 0 && + ((buffer[pos] >= 'a' && buffer[pos] <= 'z') || (buffer[pos] >= 'A' && buffer[pos] <= 'Z') || + (buffer[pos] >= '0' && buffer[pos] <= '9') || buffer[pos] == '_')) { + buffer.replace(pos, 1, " "); + column_nr--; + pos--; } - CompletionDialog::create(view, view->get_buffer()->create_mark(start_iter)); - setup_dialog(); - for(auto &row : rows) { - CompletionDialog::get()->add_row(row); - row.clear(); + } + thread = std::thread([this, line_nr, column_nr, buffer = std::move(buffer)] { + auto lock = get_parse_lock(); + if (!is_processing()) + return; + stop_parse(); + + auto &buffer_raw = const_cast<std::string &>(buffer.raw()); + rows.clear(); + add_rows(buffer_raw, line_nr, column_nr); + + if (is_processing()) { + dispatcher.post([this]() { + after_add_rows(); + if (state == State::RESTARTING) { + state = State::IDLE; + reparse(); + run(); + } else if (state == State::CANCELED || rows.empty()) { + state = State::IDLE; + reparse(); + } else { + auto start_iter = view->get_buffer()->get_insert()->get_iter(); + if (prefix.size() > 0 && !start_iter.backward_chars(prefix.size())) { + state = State::IDLE; + reparse(); + return; + } + CompletionDialog::create(view, view->get_buffer()->create_mark(start_iter)); + setup_dialog(); + for (auto &row : rows) { + CompletionDialog::get()->add_row(row); + row.clear(); + } + state = State::IDLE; + + view->get_buffer()->begin_user_action(); + CompletionDialog::get()->show(); + } + }); + } else { + dispatcher.post([this] { + state = State::CANCELED; + on_add_rows_error(); + }); } - state = State::IDLE; - - view->get_buffer()->begin_user_action(); - CompletionDialog::get()->show(); - } }); - } - else { - dispatcher.post([this] { - state = State::CANCELED; - on_add_rows_error(); - }); - } - }); - } + } - if(state != State::IDLE) - cancel_reparse(); + if (state != State::IDLE) + cancel_reparse(); } void Autocomplete::stop() { - if(state == State::STARTING || state == State::RESTARTING) - state = State::CANCELED; + if (state == State::STARTING || state == State::RESTARTING) + state = State::CANCELED; } void Autocomplete::setup_dialog() { - CompletionDialog::get()->on_show=[this] { - on_show(); - }; - - CompletionDialog::get()->on_hide = [this]() { - view->get_buffer()->end_user_action(); - tooltips.hide(); - tooltips.clear(); - on_hide(); - reparse(); - }; - - CompletionDialog::get()->on_changed = [this](unsigned int index, const std::string &text) { - if(index >= rows.size()) { - tooltips.hide(); - return; - } - - on_changed(index, text); - - auto tooltip = get_tooltip(index); - if(tooltip.empty()) - tooltips.hide(); - else { - tooltips.clear(); - auto create_tooltip_buffer = [ this, tooltip = std::move(tooltip) ]() { - auto tooltip_buffer = Gtk::TextBuffer::create(view->get_buffer()->get_tag_table()); - - tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), tooltip); - - return tooltip_buffer; - }; - - auto iter = CompletionDialog::get()->start_mark->get_iter(); - tooltips.emplace_back(create_tooltip_buffer, view, view->get_buffer()->create_mark(iter), view->get_buffer()->create_mark(iter)); - - tooltips.show(true); - } - }; - - CompletionDialog::get()->on_select=[this](unsigned int index, const std::string &text, bool hide_window) { - if(index>=rows.size()) - return; - - on_select(index, text, hide_window); - }; + CompletionDialog::get()->on_show = [this] { + on_show(); + }; + + CompletionDialog::get()->on_hide = [this]() { + view->get_buffer()->end_user_action(); + tooltips.hide(); + tooltips.clear(); + on_hide(); + reparse(); + }; + + CompletionDialog::get()->on_changed = [this](unsigned int index, const std::string &text) { + if (index >= rows.size()) { + tooltips.hide(); + return; + } + + on_changed(index, text); + + auto tooltip = get_tooltip(index); + if (tooltip.empty()) + tooltips.hide(); + else { + tooltips.clear(); + auto create_tooltip_buffer = [this, tooltip = std::move(tooltip)]() { + auto tooltip_buffer = Gtk::TextBuffer::create(view->get_buffer()->get_tag_table()); + + tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), tooltip); + + return tooltip_buffer; + }; + + auto iter = CompletionDialog::get()->start_mark->get_iter(); + tooltips.emplace_back(create_tooltip_buffer, view, view->get_buffer()->create_mark(iter), + view->get_buffer()->create_mark(iter)); + + tooltips.show(true); + } + }; + + CompletionDialog::get()->on_select = [this](unsigned int index, const std::string &text, bool hide_window) { + if (index >= rows.size()) + return; + + on_select(index, text, hide_window); + }; } diff --git a/src/autocomplete.h b/src/autocomplete.h index 1d5939bc..b808ca04 100644 --- a/src/autocomplete.h +++ b/src/autocomplete.h @@ -1,58 +1,65 @@ #pragma once + #include "dispatcher.h" #include "tooltips.h" #include <atomic> #include <thread> class Autocomplete { - Gtk::TextView *view; - bool &interactive_completion; - /// Some libraries/utilities, like libclang, require that autocomplete is started at the beginning of a word - bool strip_word; + Gtk::TextView *view; + bool &interactive_completion; + /// Some libraries/utilities, like libclang, require that autocomplete is started at the beginning of a word + bool strip_word; - Dispatcher dispatcher; + Dispatcher dispatcher; public: - enum class State { IDLE, STARTING, RESTARTING, CANCELED }; - - std::string prefix; - std::mutex prefix_mutex; - std::vector<std::string> rows; - Tooltips tooltips; - - std::atomic<State> state; - - std::thread thread; - - std::function<bool()> is_processing = [] { return true; }; - std::function<void()> reparse = [] {}; - std::function<void()> cancel_reparse = [] {}; - std::function<std::unique_ptr<std::lock_guard<std::mutex>>()> get_parse_lock = [] { return nullptr; }; - std::function<void()> stop_parse = [] {}; - - std::function<bool(guint last_keyval)> is_continue_key = [](guint) { return false; }; - std::function<bool(guint last_keyval)> is_restart_key = [](guint) { return false; }; - std::function<bool()> run_check = [] { return false; }; - - std::function<void()> before_add_rows = [] {}; - std::function<void()> after_add_rows = [] {}; - std::function<void()> on_add_rows_error = [] {}; - - /// The handler is not run in the main loop. - std::function<void(std::string &buffer, int line_number, int column)> add_rows = [](std::string &, int, int) {}; - - std::function<void()> on_show = [] {}; - std::function<void()> on_hide = [] {}; - std::function<void(unsigned int, const std::string &)> on_changed = [](unsigned int index, const std::string &text) {}; - std::function<void(unsigned int, const std::string &, bool)> on_select = [](unsigned int index, const std::string &text, bool hide_window) {}; - - std::function<std::string(unsigned int)> get_tooltip = [](unsigned int index) {return std::string();}; - - Autocomplete(Gtk::TextView *view, bool &interactive_completion, guint &last_keyval, bool strip_word); - - void run(); - void stop(); - + enum class State { + IDLE, STARTING, RESTARTING, CANCELED + }; + + std::string prefix; + std::mutex prefix_mutex; + std::vector<std::string> rows; + Tooltips tooltips; + + std::atomic<State> state; + + std::thread thread; + + std::function<bool()> is_processing = [] { return true; }; + std::function<void()> reparse = [] {}; + std::function<void()> cancel_reparse = [] {}; + std::function<std::unique_ptr<std::lock_guard<std::mutex>>()> get_parse_lock = [] { return nullptr; }; + std::function<void()> stop_parse = [] {}; + + std::function<bool(guint last_keyval)> is_continue_key = [](guint) { return false; }; + std::function<bool(guint last_keyval)> is_restart_key = [](guint) { return false; }; + std::function<bool()> run_check = [] { return false; }; + + std::function<void()> before_add_rows = [] {}; + std::function<void()> after_add_rows = [] {}; + std::function<void()> on_add_rows_error = [] {}; + + /// The handler is not run in the main loop. + std::function<void(std::string &buffer, int line_number, int column)> add_rows = [](std::string &, int, int) {}; + + std::function<void()> on_show = [] {}; + std::function<void()> on_hide = [] {}; + std::function<void(unsigned int, const std::string &)> on_changed = [](unsigned int index, + const std::string &text) {}; + std::function<void(unsigned int, const std::string &, bool)> on_select = [](unsigned int index, + const std::string &text, + bool hide_window) {}; + + std::function<std::string(unsigned int)> get_tooltip = [](unsigned int index) { return std::string(); }; + + Autocomplete(Gtk::TextView *view, bool &interactive_completion, guint &last_keyval, bool strip_word); + + void run(); + + void stop(); + private: - void setup_dialog(); + void setup_dialog(); }; diff --git a/src/buildsystem/buildsystem.h b/src/buildsystem/buildsystem.h new file mode 100644 index 00000000..d384bec2 --- /dev/null +++ b/src/buildsystem/buildsystem.h @@ -0,0 +1,20 @@ +#pragma once + +#include <boost/filesystem.hpp> +#include <vector> + +class BuildSystemBase { +public: + auto get_project_path() const { return project_path; } + void set_project_path(const boost::filesystem::path& rhs) { project_path = rhs; } + + virtual bool update_default_build(const boost::filesystem::path &default_build_path, bool force = false); + + virtual bool update_debug_build(const boost::filesystem::path &debug_build_path, bool force = false); + + virtual boost::filesystem::path + get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path); + +private: + boost::filesystem::path project_path; +}; diff --git a/src/buildsystem/cmake.cc b/src/buildsystem/cmake.cc new file mode 100644 index 00000000..c656c536 --- /dev/null +++ b/src/buildsystem/cmake.cc @@ -0,0 +1,386 @@ +#include "cmake.h" +#include "../filesystem.h" +#include "../dialogs.h" +#include "../config.h" +#include "../terminal.h" +#include <regex> +#include "../compile_commands.h" + +CMake::CMake(const boost::filesystem::path &path) { + const auto find_cmake_project = [](const boost::filesystem::path &cmake_path) { + for (auto &line: filesystem::read_lines(cmake_path)) { + const static std::regex project_regex("^ *project *\\(.*\\r?$", std::regex::icase); + std::smatch sm; + if (std::regex_match(line, sm, project_regex)) + return true; + } + return false; + }; + + auto search_path = boost::filesystem::is_directory(path) ? path : path.parent_path(); + while (true) { + auto search_cmake_path = search_path / "CMakeLists.txt"; + if (boost::filesystem::exists(search_cmake_path)) { + paths.emplace(paths.begin(), search_cmake_path); + if (find_cmake_project(search_cmake_path)) { + set_project_path(search_path); + break; + } + } + if (search_path == search_path.root_directory()) + break; + search_path = search_path.parent_path(); + } +} + +bool CMake::update_default_build(const boost::filesystem::path &default_build_path, bool force) { + if (get_project_path().empty() || !boost::filesystem::exists(get_project_path() / "CMakeLists.txt") || + default_build_path.empty()) + return false; + + if (!boost::filesystem::exists(default_build_path)) { + boost::system::error_code ec; + boost::filesystem::create_directories(default_build_path, ec); + if (ec) { + Terminal::get().print("Error: could not create " + default_build_path.string() + ": " + ec.message() + "\n", + true); + return false; + } + } + + if (!force && boost::filesystem::exists(default_build_path / "compile_commands.json")) + return true; + + 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().project.cmake.command + ' ' + + filesystem::escape_argument(get_project_path().string()) + + " -DCMAKE_EXPORT_COMPILE_COMMANDS=ON", default_build_path); + message.hide(); + if (exit_status == EXIT_SUCCESS) { +#ifdef _WIN32 //Temporary fix to MSYS2's libclang + auto compile_commands_file = filesystem::read(compile_commands_path); + auto replace_drive = [&compile_commands_file](const std::string ¶m) { + size_t pos = 0; + auto param_size = param.length(); + while ((pos = compile_commands_file.find(param + "/", pos)) != std::string::npos) { + if (pos + param_size + 1 < compile_commands_file.size()) + compile_commands_file.replace(pos, param_size + 2, + param + compile_commands_file[pos + param_size + 1] + ":"); + else + break; + } + }; + replace_drive("-I"); + replace_drive("-isystem "); + filesystem::write(compile_commands_path, compile_commands_file); +#endif + return true; + } + return false; +} + +bool CMake::update_debug_build(const boost::filesystem::path &debug_build_path, bool force) { + if (get_project_path().empty() || !boost::filesystem::exists(get_project_path() / "CMakeLists.txt") || debug_build_path.empty()) + return false; + + if (!boost::filesystem::exists(debug_build_path)) { + boost::system::error_code ec; + boost::filesystem::create_directories(debug_build_path, ec); + if (ec) { + Terminal::get().print("Error: could not create " + debug_build_path.string() + ": " + ec.message() + "\n", + true); + return false; + } + } + + if (!force && boost::filesystem::exists(debug_build_path / "CMakeCache.txt")) + return true; + + Dialog::Message message("Creating/updating debug build"); + auto exit_status = Terminal::get().process(Config::get().project.cmake.command + ' ' + + filesystem::escape_argument(get_project_path().string()) + + " -DCMAKE_BUILD_TYPE=Debug", debug_build_path); + message.hide(); + if (exit_status == EXIT_SUCCESS) + return true; + return false; +} + +boost::filesystem::path +CMake::get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) { + // CMake does not store in compile_commands.json if an object is part of an executable or not. + // Therefore, executables are first attempted found in the cmake files. These executables + // are then used to identify if a file in compile_commands.json is part of an executable or not + + auto parameters = get_functions_parameters("add_executable"); + + std::vector<boost::filesystem::path> cmake_executables; + for (auto ¶meter: parameters) { + if (parameter.second.size() > 1 && parameter.second[0].size() > 0 && + parameter.second[0].compare(0, 2, "${") != 0) { + auto executable = (parameter.first.parent_path() / parameter.second[0]).string(); + auto project_path_str = get_project_path().string(); + size_t pos = executable.find(project_path_str); + if (pos != std::string::npos) + executable.replace(pos, project_path_str.size(), build_path.string()); + cmake_executables.emplace_back(executable); + } + } + + + CompileCommands compile_commands(build_path); + std::vector<std::pair<boost::filesystem::path, boost::filesystem::path>> command_files_and_maybe_executables; + for (auto &command: compile_commands.commands) { + auto command_file = filesystem::get_normal_path(command.file); + auto values = command.parameter_values("-o"); + if (!values.empty()) { + size_t pos; + values[0].erase(0, 11); + if ((pos = values[0].find(".dir")) != std::string::npos) { + auto executable = command.directory / values[0].substr(0, pos); + command_files_and_maybe_executables.emplace_back(command_file, executable); + } + } + } + + size_t best_match_size = -1; + boost::filesystem::path best_match_executable; + + for (auto &cmake_executable: cmake_executables) { + for (auto &command_file_and_maybe_executable: command_files_and_maybe_executables) { + auto &command_file = command_file_and_maybe_executable.first; + auto &maybe_executable = command_file_and_maybe_executable.second; + if (cmake_executable == maybe_executable) { + if (command_file == file_path) + return maybe_executable; + auto command_file_directory = command_file.parent_path(); + if (filesystem::file_in_path(file_path, command_file_directory)) { + auto size = static_cast<size_t>(std::distance(command_file_directory.begin(), + command_file_directory.end())); + if (best_match_size == static_cast<size_t>(-1) || best_match_size < size) { + best_match_size = size; + best_match_executable = maybe_executable; + } + } + } + } + } + if (!best_match_executable.empty()) + return best_match_executable; + + for (auto &command_file_and_maybe_executable: command_files_and_maybe_executables) { + auto &command_file = command_file_and_maybe_executable.first; + auto &maybe_executable = command_file_and_maybe_executable.second; + if (command_file == file_path) + return maybe_executable; + auto command_file_directory = command_file.parent_path(); + if (filesystem::file_in_path(file_path, command_file_directory)) { + auto size = static_cast<size_t>(std::distance(command_file_directory.begin(), + command_file_directory.end())); + if (best_match_size == static_cast<size_t>(-1) || best_match_size < size) { + best_match_size = size; + best_match_executable = maybe_executable; + } + } + } + return best_match_executable; +} + +void CMake::read_files() { + for (auto &path: paths) + files.emplace_back(filesystem::read(path)); +} + +void CMake::remove_tabs() { + for (auto &file: files) { + for (auto &chr: file) { + if (chr == '\t') + chr = ' '; + } + } +} + +void CMake::remove_comments() { + for (auto &file: files) { + size_t pos = 0; + size_t comment_start; + bool inside_comment = false; + while (pos < file.size()) { + if (!inside_comment && file[pos] == '#') { + comment_start = pos; + inside_comment = true; + } + if (inside_comment && file[pos] == '\n') { + file.erase(comment_start, pos - comment_start); + pos -= pos - comment_start; + inside_comment = false; + } + pos++; + } + if (inside_comment) + file.erase(comment_start); + } +} + +void CMake::remove_newlines_inside_parentheses() { + for (auto &file: files) { + size_t pos = 0; + bool inside_para = false; + bool inside_quote = false; + char last_char = 0; + while (pos < file.size()) { + if (!inside_quote && file[pos] == '"' && last_char != '\\') + inside_quote = true; + else if (inside_quote && file[pos] == '"' && last_char != '\\') + inside_quote = false; + + else if (!inside_quote && file[pos] == '(') + inside_para = true; + else if (!inside_quote && file[pos] == ')') + inside_para = false; + + else if (inside_para && file[pos] == '\n') + file.replace(pos, 1, 1, ' '); + last_char = file[pos]; + pos++; + } + } +} + +void CMake::parse_variable_parameters(std::string &data) { + size_t pos = 0; + bool inside_quote = false; + char last_char = 0; + while (pos < data.size()) { + if (!inside_quote && data[pos] == '"' && last_char != '\\') { + inside_quote = true; + data.erase(pos, + 1); //TODO: instead remove quote-mark if pasted into a quote, for instance: "test${test}test"<-remove quotes from ${test} + pos--; + } else if (inside_quote && data[pos] == '"' && last_char != '\\') { + inside_quote = false; + data.erase(pos, + 1); //TODO: instead remove quote-mark if pasted into a quote, for instance: "test${test}test"<-remove quotes from ${test} + pos--; + } else if (!inside_quote && data[pos] == ' ' && pos + 1 < data.size() && data[pos + 1] == ' ') { + data.erase(pos, 1); + pos--; + } + + if (pos != static_cast<size_t>(-1)) + last_char = data[pos]; + pos++; + } + for (auto &var: variables) { + auto pos = data.find("${" + var.first + '}'); + while (pos != std::string::npos) { + data.replace(pos, var.first.size() + 3, var.second); + pos = data.find("${" + var.first + '}'); + } + } + + //Remove variables we do not know: + pos = data.find("${"); + auto pos_end = data.find("}", pos + 2); + while (pos != std::string::npos && pos_end != std::string::npos) { + data.erase(pos, pos_end - pos + 1); + pos = data.find("${"); + pos_end = data.find("}", pos + 2); + } +} + +void CMake::parse() { + read_files(); + remove_tabs(); + remove_comments(); + remove_newlines_inside_parentheses(); + parsed = true; +} + +std::vector<std::string> CMake::get_function_parameters(std::string &data) { + std::vector<std::string> parameters; + size_t pos = 0; + size_t parameter_pos = 0; + bool inside_quote = false; + char last_char = 0; + while (pos < data.size()) { + if (!inside_quote && data[pos] == '"' && last_char != '\\') { + inside_quote = true; + data.erase(pos, 1); + pos--; + } else if (inside_quote && data[pos] == '"' && last_char != '\\') { + inside_quote = false; + data.erase(pos, 1); + pos--; + } else if (!inside_quote && pos + 1 < data.size() && data[pos] == ' ' && data[pos + 1] == ' ') { + data.erase(pos, 1); + pos--; + } else if (!inside_quote && data[pos] == ' ') { + parameters.emplace_back(data.substr(parameter_pos, pos - parameter_pos)); + if (pos + 1 < data.size()) + parameter_pos = pos + 1; + } + + if (pos != static_cast<size_t>(-1)) + last_char = data[pos]; + pos++; + } + parameters.emplace_back(data.substr(parameter_pos)); + for (auto &var: variables) { + for (auto ¶meter: parameters) { + auto pos = parameter.find("${" + var.first + '}'); + while (pos != std::string::npos) { + parameter.replace(pos, var.first.size() + 3, var.second); + pos = parameter.find("${" + var.first + '}'); + } + } + } + return parameters; +} + +std::vector<std::pair<boost::filesystem::path, std::vector<std::string> > > +CMake::get_functions_parameters(const std::string &name) { + const std::regex function_regex("^ *" + name + " *\\( *(.*)\\) *\\r?$", std::regex::icase); + variables.clear(); + if (!parsed) + parse(); + std::vector<std::pair<boost::filesystem::path, std::vector<std::string> > > functions; + for (size_t c = 0; c < files.size(); ++c) { + size_t pos = 0; + while (pos < files[c].size()) { + auto start_line = pos; + auto end_line = files[c].find('\n', start_line); + if (end_line == std::string::npos) + end_line = files[c].size(); + if (end_line > start_line) { + auto line = files[c].substr(start_line, end_line - start_line); + std::smatch sm; + const static std::regex set_regex("^ *set *\\( *([A-Za-z_][A-Za-z_0-9]*) +(.*)\\) *\\r?$", + std::regex::icase); + const static std::regex project_regex("^ *project *\\( *([^ ]+).*\\) *\\r?$", std::regex::icase); + if (std::regex_match(line, sm, set_regex)) { + auto data = sm[2].str(); + while (data.size() > 0 && data.back() == ' ') + data.pop_back(); + parse_variable_parameters(data); + variables[sm[1].str()] = data; + } else if (std::regex_match(line, sm, project_regex)) { + auto data = sm[1].str(); + parse_variable_parameters(data); + variables["CMAKE_PROJECT_NAME"] = data; //TODO: is this variable deprecated/non-standard? + variables["PROJECT_NAME"] = data; + } + if (std::regex_match(line, sm, function_regex)) { + auto data = sm[1].str(); + while (data.size() > 0 && data.back() == ' ') + data.pop_back(); + auto parameters = get_function_parameters(data); + functions.emplace_back(paths[c], parameters); + } + } + pos = end_line + 1; + } + } + return functions; +} diff --git a/src/buildsystem/cmake.h b/src/buildsystem/cmake.h new file mode 100644 index 00000000..9ea4c41b --- /dev/null +++ b/src/buildsystem/cmake.h @@ -0,0 +1,41 @@ +#pragma once + +#include "buildsystem.h" +#include <unordered_map> +#include <unordered_set> + +class CMake : public BuildSystemBase { +public: + CMake(const boost::filesystem::path &path); + + bool update_default_build(const boost::filesystem::path &default_build_path, bool force = false) override; + + bool update_debug_build(const boost::filesystem::path &debug_build_path, bool force = false) override; + + boost::filesystem::path + get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) override; + +private: + std::vector<boost::filesystem::path> paths; + std::vector<std::string> files; + std::unordered_map<std::string, std::string> variables; + + void read_files(); + + void remove_tabs(); + + void remove_comments(); + + void remove_newlines_inside_parentheses(); + + void parse_variable_parameters(std::string &data); + + void parse(); + + std::vector<std::string> get_function_parameters(std::string &data); + + std::vector<std::pair<boost::filesystem::path, std::vector<std::string> > > + get_functions_parameters(const std::string &name); + + bool parsed = false; +}; diff --git a/src/buildsystem/meson.cc b/src/buildsystem/meson.cc new file mode 100644 index 00000000..9b2b994c --- /dev/null +++ b/src/buildsystem/meson.cc @@ -0,0 +1,123 @@ +#include "meson.h" +#include "../filesystem.h" +#include "../compile_commands.h" +#include <regex> +#include "../terminal.h" +#include "../dialogs.h" +#include "../config.h" + +Meson::Meson(const boost::filesystem::path &path) { + const auto find_project = [](const boost::filesystem::path &file_path) { + for (auto &line: filesystem::read_lines(file_path)) { + const static std::regex project_regex("^ *project *\\(.*\\r?$", std::regex::icase); + std::smatch sm; + if (std::regex_match(line, sm, project_regex)) + return true; + } + return false; + }; + + auto search_path = boost::filesystem::is_directory(path) ? path : path.parent_path(); + while (true) { + auto search_file = search_path / "meson.build"; + if (boost::filesystem::exists(search_file)) { + if (find_project(search_file)) { + project_path = search_path; + break; + } + } + if (search_path == search_path.root_directory()) + break; + search_path = search_path.parent_path(); + } +} + +bool Meson::update_default_build(const boost::filesystem::path &default_build_path, bool force) { + if (project_path.empty() || !boost::filesystem::exists(project_path / "meson.build") || default_build_path.empty()) + return false; + + if (!boost::filesystem::exists(default_build_path)) { + boost::system::error_code ec; + boost::filesystem::create_directories(default_build_path, ec); + if (ec) { + Terminal::get().print("Error: could not create " + default_build_path.string() + ": " + ec.message() + "\n", + true); + return false; + } + } + + auto compile_commands_path = default_build_path / "compile_commands.json"; + bool compile_commands_exists = boost::filesystem::exists(compile_commands_path); + if (!force && compile_commands_exists) + return true; + + Dialog::Message message("Creating/updating default build"); + auto exit_status = Terminal::get().process( + Config::get().project.meson.command + ' ' + (compile_commands_exists ? "--internal regenerate " : "") + + filesystem::escape_argument(project_path.string()), default_build_path); + message.hide(); + if (exit_status == EXIT_SUCCESS) + return true; + return false; +} + +bool Meson::update_debug_build(const boost::filesystem::path &debug_build_path, bool force) { + if (project_path.empty() || !boost::filesystem::exists(project_path / "meson.build") || debug_build_path.empty()) + return false; + + if (!boost::filesystem::exists(debug_build_path)) { + boost::system::error_code ec; + boost::filesystem::create_directories(debug_build_path, ec); + if (ec) { + Terminal::get().print("Error: could not create " + debug_build_path.string() + ": " + ec.message() + "\n", + true); + return false; + } + } + + bool compile_commands_exists = boost::filesystem::exists(debug_build_path / "compile_commands.json"); + if (!force && compile_commands_exists) + return true; + + Dialog::Message message("Creating/updating debug build"); + auto exit_status = Terminal::get().process( + Config::get().project.meson.command + ' ' + (compile_commands_exists ? "--internal regenerate " : "") + + "--buildtype debug " + filesystem::escape_argument(project_path.string()), debug_build_path); + message.hide(); + if (exit_status == EXIT_SUCCESS) + return true; + return false; +} + +boost::filesystem::path +Meson::get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) { + CompileCommands compile_commands(build_path); + + size_t best_match_size = -1; + boost::filesystem::path best_match_executable; + for (auto &command: compile_commands.commands) { + auto command_file = filesystem::get_normal_path(command.file); + auto values = command.parameter_values("-o"); + if (!values.empty()) { + size_t pos; + if ((pos = values[0].find("@")) != std::string::npos) { + if (pos + 1 < values[0].size() && values[0].compare(pos + 1, 3, "exe") == 0) { + auto executable = build_path / values[0].substr(0, pos); + if (command_file == file_path) + return executable; + auto command_file_directory = command_file.parent_path(); + if (filesystem::file_in_path(file_path, command_file_directory)) { + auto size = static_cast<size_t>(std::distance(command_file_directory.begin(), + command_file_directory.end())); + if (best_match_size == static_cast<size_t>(-1) || best_match_size < size) { + best_match_size = size; + best_match_executable = executable; + } + } + } + } + } + } + + return best_match_executable; +} diff --git a/src/buildsystem/meson.h b/src/buildsystem/meson.h new file mode 100644 index 00000000..17b9a540 --- /dev/null +++ b/src/buildsystem/meson.h @@ -0,0 +1,18 @@ +#pragma once + +#include <boost/filesystem.hpp> +#include <vector> + +class Meson { +public: + Meson(const boost::filesystem::path &path); + + boost::filesystem::path project_path; + + bool update_default_build(const boost::filesystem::path &default_build_path, bool force = false); + + bool update_debug_build(const boost::filesystem::path &debug_build_path, bool force = false); + + boost::filesystem::path + get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path); +}; diff --git a/src/cmake.cc b/src/cmake.cc deleted file mode 100644 index f3945acb..00000000 --- a/src/cmake.cc +++ /dev/null @@ -1,378 +0,0 @@ -#include "cmake.h" -#include "filesystem.h" -#include "dialogs.h" -#include "config.h" -#include "terminal.h" -#include <regex> -#include "compile_commands.h" - -CMake::CMake(const boost::filesystem::path &path) { - const auto find_cmake_project=[](const boost::filesystem::path &cmake_path) { - for(auto &line: filesystem::read_lines(cmake_path)) { - const static std::regex project_regex("^ *project *\\(.*\\r?$", std::regex::icase); - std::smatch sm; - if(std::regex_match(line, sm, project_regex)) - return true; - } - return false; - }; - - auto search_path=boost::filesystem::is_directory(path)?path:path.parent_path(); - while(true) { - auto search_cmake_path=search_path/"CMakeLists.txt"; - if(boost::filesystem::exists(search_cmake_path)) { - paths.emplace(paths.begin(), search_cmake_path); - if(find_cmake_project(search_cmake_path)) { - project_path=search_path; - break; - } - } - if(search_path==search_path.root_directory()) - break; - search_path=search_path.parent_path(); - } -} - -bool CMake::update_default_build(const boost::filesystem::path &default_build_path, bool force) { - if(project_path.empty() || !boost::filesystem::exists(project_path/"CMakeLists.txt") || default_build_path.empty()) - return false; - - if(!boost::filesystem::exists(default_build_path)) { - boost::system::error_code ec; - boost::filesystem::create_directories(default_build_path, ec); - if(ec) { - Terminal::get().print("Error: could not create "+default_build_path.string()+": "+ec.message()+"\n", true); - return false; - } - } - - if(!force && boost::filesystem::exists(default_build_path/"compile_commands.json")) - return true; - - 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().project.cmake.command+' '+ - filesystem::escape_argument(project_path.string())+" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON", default_build_path); - message.hide(); - if(exit_status==EXIT_SUCCESS) { -#ifdef _WIN32 //Temporary fix to MSYS2's libclang - auto compile_commands_file=filesystem::read(compile_commands_path); - auto replace_drive = [&compile_commands_file](const std::string& param) { - size_t pos=0; - auto param_size = param.length(); - while((pos=compile_commands_file.find(param+"/", pos))!=std::string::npos) { - if(pos+param_size+1<compile_commands_file.size()) - compile_commands_file.replace(pos, param_size+2, param+compile_commands_file[pos+param_size+1]+":"); - else - break; - } - }; - replace_drive("-I"); - replace_drive("-isystem "); - filesystem::write(compile_commands_path, compile_commands_file); -#endif - return true; - } - return false; -} - -bool CMake::update_debug_build(const boost::filesystem::path &debug_build_path, bool force) { - if(project_path.empty() || !boost::filesystem::exists(project_path/"CMakeLists.txt") || debug_build_path.empty()) - return false; - - if(!boost::filesystem::exists(debug_build_path)) { - boost::system::error_code ec; - boost::filesystem::create_directories(debug_build_path, ec); - if(ec) { - Terminal::get().print("Error: could not create "+debug_build_path.string()+": "+ec.message()+"\n", true); - return false; - } - } - - if(!force && boost::filesystem::exists(debug_build_path/"CMakeCache.txt")) - return true; - - Dialog::Message message("Creating/updating debug build"); - auto exit_status=Terminal::get().process(Config::get().project.cmake.command+' '+ - filesystem::escape_argument(project_path.string())+" -DCMAKE_BUILD_TYPE=Debug", debug_build_path); - message.hide(); - if(exit_status==EXIT_SUCCESS) - return true; - return false; -} - -boost::filesystem::path CMake::get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) { - // CMake does not store in compile_commands.json if an object is part of an executable or not. - // Therefore, executables are first attempted found in the cmake files. These executables - // are then used to identify if a file in compile_commands.json is part of an executable or not - - auto parameters = get_functions_parameters("add_executable"); - - std::vector<boost::filesystem::path> cmake_executables; - for(auto ¶meter: parameters) { - if(parameter.second.size()>1 && parameter.second[0].size()>0 && parameter.second[0].compare(0, 2, "${")!=0) { - auto executable=(parameter.first.parent_path()/parameter.second[0]).string(); - auto project_path_str=project_path.string(); - size_t pos=executable.find(project_path_str); - if(pos!=std::string::npos) - executable.replace(pos, project_path_str.size(), build_path.string()); - cmake_executables.emplace_back(executable); - } - } - - - CompileCommands compile_commands(build_path); - std::vector<std::pair<boost::filesystem::path, boost::filesystem::path>> command_files_and_maybe_executables; - for(auto &command: compile_commands.commands) { - auto command_file=filesystem::get_normal_path(command.file); - auto values=command.parameter_values("-o"); - if(!values.empty()) { - size_t pos; - values[0].erase(0, 11); - if((pos=values[0].find(".dir"))!=std::string::npos) { - auto executable=command.directory/values[0].substr(0, pos); - command_files_and_maybe_executables.emplace_back(command_file, executable); - } - } - } - - size_t best_match_size=-1; - boost::filesystem::path best_match_executable; - - for(auto &cmake_executable: cmake_executables) { - for(auto &command_file_and_maybe_executable: command_files_and_maybe_executables) { - auto &command_file=command_file_and_maybe_executable.first; - auto &maybe_executable=command_file_and_maybe_executable.second; - if(cmake_executable==maybe_executable) { - if(command_file==file_path) - return maybe_executable; - auto command_file_directory=command_file.parent_path(); - if(filesystem::file_in_path(file_path, command_file_directory)) { - auto size=static_cast<size_t>(std::distance(command_file_directory.begin(), command_file_directory.end())); - if(best_match_size==static_cast<size_t>(-1) || best_match_size<size) { - best_match_size=size; - best_match_executable=maybe_executable; - } - } - } - } - } - if(!best_match_executable.empty()) - return best_match_executable; - - for(auto &command_file_and_maybe_executable: command_files_and_maybe_executables) { - auto &command_file=command_file_and_maybe_executable.first; - auto &maybe_executable=command_file_and_maybe_executable.second; - if(command_file==file_path) - return maybe_executable; - auto command_file_directory=command_file.parent_path(); - if(filesystem::file_in_path(file_path, command_file_directory)) { - auto size=static_cast<size_t>(std::distance(command_file_directory.begin(), command_file_directory.end())); - if(best_match_size==static_cast<size_t>(-1) || best_match_size<size) { - best_match_size=size; - best_match_executable=maybe_executable; - } - } - } - return best_match_executable; -} - -void CMake::read_files() { - for(auto &path: paths) - files.emplace_back(filesystem::read(path)); -} - -void CMake::remove_tabs() { - for(auto &file: files) { - for(auto &chr: file) { - if(chr=='\t') - chr=' '; - } - } -} - -void CMake::remove_comments() { - for(auto &file: files) { - size_t pos=0; - size_t comment_start; - bool inside_comment=false; - while(pos<file.size()) { - if(!inside_comment && file[pos]=='#') { - comment_start=pos; - inside_comment=true; - } - if(inside_comment && file[pos]=='\n') { - file.erase(comment_start, pos-comment_start); - pos-=pos-comment_start; - inside_comment=false; - } - pos++; - } - if(inside_comment) - file.erase(comment_start); - } -} - -void CMake::remove_newlines_inside_parentheses() { - for(auto &file: files) { - size_t pos=0; - bool inside_para=false; - bool inside_quote=false; - char last_char=0; - while(pos<file.size()) { - if(!inside_quote && file[pos]=='"' && last_char!='\\') - inside_quote=true; - else if(inside_quote && file[pos]=='"' && last_char!='\\') - inside_quote=false; - - else if(!inside_quote && file[pos]=='(') - inside_para=true; - else if(!inside_quote && file[pos]==')') - inside_para=false; - - else if(inside_para && file[pos]=='\n') - file.replace(pos, 1, 1, ' '); - last_char=file[pos]; - pos++; - } - } -} - -void CMake::parse_variable_parameters(std::string &data) { - size_t pos=0; - bool inside_quote=false; - char last_char=0; - while(pos<data.size()) { - if(!inside_quote && data[pos]=='"' && last_char!='\\') { - inside_quote=true; - data.erase(pos, 1); //TODO: instead remove quote-mark if pasted into a quote, for instance: "test${test}test"<-remove quotes from ${test} - pos--; - } - else if(inside_quote && data[pos]=='"' && last_char!='\\') { - inside_quote=false; - data.erase(pos, 1); //TODO: instead remove quote-mark if pasted into a quote, for instance: "test${test}test"<-remove quotes from ${test} - pos--; - } - else if(!inside_quote && data[pos]==' ' && pos+1<data.size() && data[pos+1]==' ') { - data.erase(pos, 1); - pos--; - } - - if(pos!=static_cast<size_t>(-1)) - last_char=data[pos]; - pos++; - } - for(auto &var: variables) { - auto pos=data.find("${"+var.first+'}'); - while(pos!=std::string::npos) { - data.replace(pos, var.first.size()+3, var.second); - pos=data.find("${"+var.first+'}'); - } - } - - //Remove variables we do not know: - pos=data.find("${"); - auto pos_end=data.find("}", pos+2); - while(pos!=std::string::npos && pos_end!=std::string::npos) { - data.erase(pos, pos_end-pos+1); - pos=data.find("${"); - pos_end=data.find("}", pos+2); - } -} - -void CMake::parse() { - read_files(); - remove_tabs(); - remove_comments(); - remove_newlines_inside_parentheses(); - parsed=true; -} - -std::vector<std::string> CMake::get_function_parameters(std::string &data) { - std::vector<std::string> parameters; - size_t pos=0; - size_t parameter_pos=0; - bool inside_quote=false; - char last_char=0; - while(pos<data.size()) { - if(!inside_quote && data[pos]=='"' && last_char!='\\') { - inside_quote=true; - data.erase(pos, 1); - pos--; - } - else if(inside_quote && data[pos]=='"' && last_char!='\\') { - inside_quote=false; - data.erase(pos, 1); - pos--; - } - else if(!inside_quote && pos+1<data.size() && data[pos]==' ' && data[pos+1]==' ') { - data.erase(pos, 1); - pos--; - } - else if(!inside_quote && data[pos]==' ') { - parameters.emplace_back(data.substr(parameter_pos, pos-parameter_pos)); - if(pos+1<data.size()) - parameter_pos=pos+1; - } - - if(pos!=static_cast<size_t>(-1)) - last_char=data[pos]; - pos++; - } - parameters.emplace_back(data.substr(parameter_pos)); - for(auto &var: variables) { - for(auto ¶meter: parameters) { - auto pos=parameter.find("${"+var.first+'}'); - while(pos!=std::string::npos) { - parameter.replace(pos, var.first.size()+3, var.second); - pos=parameter.find("${"+var.first+'}'); - } - } - } - return parameters; -} - -std::vector<std::pair<boost::filesystem::path, std::vector<std::string> > > CMake::get_functions_parameters(const std::string &name) { - const std::regex function_regex("^ *"+name+" *\\( *(.*)\\) *\\r?$", std::regex::icase); - variables.clear(); - if(!parsed) - parse(); - std::vector<std::pair<boost::filesystem::path, std::vector<std::string> > > functions; - for(size_t c=0;c<files.size();++c) { - size_t pos=0; - while(pos<files[c].size()) { - auto start_line=pos; - auto end_line=files[c].find('\n', start_line); - if(end_line==std::string::npos) - end_line=files[c].size(); - if(end_line>start_line) { - auto line=files[c].substr(start_line, end_line-start_line); - std::smatch sm; - const static std::regex set_regex("^ *set *\\( *([A-Za-z_][A-Za-z_0-9]*) +(.*)\\) *\\r?$", std::regex::icase); - const static std::regex project_regex("^ *project *\\( *([^ ]+).*\\) *\\r?$", std::regex::icase); - if(std::regex_match(line, sm, set_regex)) { - auto data=sm[2].str(); - while(data.size()>0 && data.back()==' ') - data.pop_back(); - parse_variable_parameters(data); - variables[sm[1].str()]=data; - } - else if(std::regex_match(line, sm, project_regex)) { - auto data=sm[1].str(); - parse_variable_parameters(data); - variables["CMAKE_PROJECT_NAME"]=data; //TODO: is this variable deprecated/non-standard? - variables["PROJECT_NAME"]=data; - } - if(std::regex_match(line, sm, function_regex)) { - auto data=sm[1].str(); - while(data.size()>0 && data.back()==' ') - data.pop_back(); - auto parameters=get_function_parameters(data); - functions.emplace_back(paths[c], parameters); - } - } - pos=end_line+1; - } - } - return functions; -} diff --git a/src/cmake.h b/src/cmake.h deleted file mode 100644 index f87010a3..00000000 --- a/src/cmake.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once -#include <boost/filesystem.hpp> -#include <vector> -#include <unordered_map> -#include <unordered_set> - -class CMake { -public: - CMake(const boost::filesystem::path &path); - boost::filesystem::path project_path; - - bool update_default_build(const boost::filesystem::path &default_build_path, bool force=false); - bool update_debug_build(const boost::filesystem::path &debug_build_path, bool force=false); - - boost::filesystem::path get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path); - -private: - std::vector<boost::filesystem::path> paths; - std::vector<std::string> files; - std::unordered_map<std::string, std::string> variables; - void read_files(); - void remove_tabs(); - void remove_comments(); - void remove_newlines_inside_parentheses(); - void parse_variable_parameters(std::string &data); - void parse(); - std::vector<std::string> get_function_parameters(std::string &data); - std::vector<std::pair<boost::filesystem::path, std::vector<std::string> > > get_functions_parameters(const std::string &name); - bool parsed=false; -}; diff --git a/src/compile_commands.cc b/src/compile_commands.cc index 5f11afe8..7f283d8d 100644 --- a/src/compile_commands.cc +++ b/src/compile_commands.cc @@ -4,161 +4,159 @@ #include <regex> std::vector<std::string> CompileCommands::Command::parameter_values(const std::string ¶meter_name) const { - std::vector<std::string> parameter_values; - - bool found_argument=false; - for(auto ¶meter: parameters) { - if(found_argument) { - parameter_values.emplace_back(parameter); - found_argument=false; + std::vector<std::string> parameter_values; + + bool found_argument = false; + for (auto ¶meter: parameters) { + if (found_argument) { + parameter_values.emplace_back(parameter); + found_argument = false; + } else if (parameter == parameter_name) + found_argument = true; } - else if(parameter==parameter_name) - found_argument=true; - } - - return parameter_values; + + return parameter_values; } CompileCommands::CompileCommands(const boost::filesystem::path &build_path) { - try { - boost::property_tree::ptree root_pt; - boost::property_tree::json_parser::read_json((build_path/"compile_commands.json").string(), root_pt); - - auto commands_pt=root_pt.get_child(""); - for(auto &command: commands_pt) { - boost::filesystem::path directory=command.second.get<std::string>("directory"); - auto parameters_str=command.second.get<std::string>("command"); - boost::filesystem::path file=command.second.get<std::string>("file"); - - std::vector<std::string> parameters; - bool backslash=false; - bool single_quote=false; - bool double_quote=false; - size_t parameter_start_pos=std::string::npos; - size_t parameter_size=0; - auto add_parameter=[¶meters, ¶meters_str, ¶meter_start_pos, ¶meter_size] { - auto parameter=parameters_str.substr(parameter_start_pos, parameter_size); - // Remove escaping - for(size_t c=0;c<parameter.size()-1;++c) { - if(parameter[c]=='\\') - parameter.replace(c, 2, std::string()+parameter[c+1]); - } - parameters.emplace_back(parameter); - }; - for(size_t c=0;c<parameters_str.size();++c) { - if(backslash) - backslash=false; - else if(parameters_str[c]=='\\') - backslash=true; - else if((parameters_str[c]==' ' || parameters_str[c]=='\t') && !backslash && !single_quote && !double_quote) { - if(parameter_start_pos!=std::string::npos) { - add_parameter(); - parameter_start_pos=std::string::npos; - parameter_size=0; - } - continue; - } - else if(parameters_str[c]=='\'' && !backslash && !double_quote) { - single_quote=!single_quote; - continue; - } - else if(parameters_str[c]=='\"' && !backslash && !single_quote) { - double_quote=!double_quote; - continue; + try { + boost::property_tree::ptree root_pt; + boost::property_tree::json_parser::read_json((build_path / "compile_commands.json").string(), root_pt); + + auto commands_pt = root_pt.get_child(""); + for (auto &command: commands_pt) { + boost::filesystem::path directory = command.second.get<std::string>("directory"); + auto parameters_str = command.second.get<std::string>("command"); + boost::filesystem::path file = command.second.get<std::string>("file"); + + std::vector<std::string> parameters; + bool backslash = false; + bool single_quote = false; + bool double_quote = false; + size_t parameter_start_pos = std::string::npos; + size_t parameter_size = 0; + auto add_parameter = [¶meters, ¶meters_str, ¶meter_start_pos, ¶meter_size] { + auto parameter = parameters_str.substr(parameter_start_pos, parameter_size); + // Remove escaping + for (size_t c = 0; c < parameter.size() - 1; ++c) { + if (parameter[c] == '\\') + parameter.replace(c, 2, std::string() + parameter[c + 1]); + } + parameters.emplace_back(parameter); + }; + for (size_t c = 0; c < parameters_str.size(); ++c) { + if (backslash) + backslash = false; + else if (parameters_str[c] == '\\') + backslash = true; + else if ((parameters_str[c] == ' ' || parameters_str[c] == '\t') && !backslash && !single_quote && + !double_quote) { + if (parameter_start_pos != std::string::npos) { + add_parameter(); + parameter_start_pos = std::string::npos; + parameter_size = 0; + } + continue; + } else if (parameters_str[c] == '\'' && !backslash && !double_quote) { + single_quote = !single_quote; + continue; + } else if (parameters_str[c] == '\"' && !backslash && !single_quote) { + double_quote = !double_quote; + continue; + } + + if (parameter_start_pos == std::string::npos) + parameter_start_pos = c; + ++parameter_size; + } + if (parameter_start_pos != std::string::npos) + add_parameter(); + + commands.emplace_back(Command{directory, parameters, boost::filesystem::absolute(file, build_path)}); } - - if(parameter_start_pos==std::string::npos) - parameter_start_pos=c; - ++parameter_size; - } - if(parameter_start_pos!=std::string::npos) - add_parameter(); - - commands.emplace_back(Command{directory, parameters, boost::filesystem::absolute(file, build_path)}); } - } - catch(...) {} + catch (...) {} } -std::vector<std::string> CompileCommands::get_arguments(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) { - std::string default_std_argument="-std=c++1y"; - - std::vector<std::string> arguments; - if(!build_path.empty()) { - clangmm::CompilationDatabase db(build_path.string()); - if(db) { - clangmm::CompileCommands commands(file_path.string(), db); - auto cmds = commands.get_commands(); - for (auto &cmd : cmds) { - auto cmd_arguments = cmd.get_arguments(); - bool ignore_next=false; - for (size_t c = 1; c < cmd_arguments.size(); c++) { - if(ignore_next) { - ignore_next=false; - continue; - } - else if(cmd_arguments[c]=="-o" || cmd_arguments[c]=="-c") { - ignore_next=true; - continue; - } - arguments.emplace_back(cmd_arguments[c]); - } - } - } - else - arguments.emplace_back(default_std_argument); - } - else - arguments.emplace_back(default_std_argument); - - auto clang_version_string=clangmm::to_string(clang_getClangVersion()); - const static std::regex clang_version_regex("^[A-Za-z ]+([0-9.]+).*$"); - std::smatch sm; - if(std::regex_match(clang_version_string, sm, clang_version_regex)) { - auto clang_version=sm[1].str(); - arguments.emplace_back("-I/usr/lib/clang/"+clang_version+"/include"); - arguments.emplace_back("-I/usr/lib64/clang/"+clang_version+"/include"); // For Fedora -#if defined(__APPLE__) && CINDEX_VERSION_MAJOR==0 && CINDEX_VERSION_MINOR<32 // TODO: remove during 2018 if llvm3.7 is no longer in homebrew (CINDEX_VERSION_MINOR=32 equals clang-3.8 I think) - arguments.emplace_back("-I/usr/local/Cellar/llvm/"+clang_version+"/lib/clang/"+clang_version+"/include"); - arguments.emplace_back("-I/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1"); - arguments.emplace_back("-I/Library/Developer/CommandLineTools/usr/bin/../include/c++/v1"); //Added for OS X 10.11 +std::vector<std::string> +CompileCommands::get_arguments(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) { + std::string default_std_argument = "-std=c++1y"; + + std::vector<std::string> arguments; + if (!build_path.empty()) { + clangmm::CompilationDatabase db(build_path.string()); + if (db) { + clangmm::CompileCommands commands(file_path.string(), db); + auto cmds = commands.get_commands(); + for (auto &cmd : cmds) { + auto cmd_arguments = cmd.get_arguments(); + bool ignore_next = false; + for (size_t c = 1; c < cmd_arguments.size(); c++) { + if (ignore_next) { + ignore_next = false; + continue; + } else if (cmd_arguments[c] == "-o" || cmd_arguments[c] == "-c") { + ignore_next = true; + continue; + } + arguments.emplace_back(cmd_arguments[c]); + } + } + } else + arguments.emplace_back(default_std_argument); + } else + arguments.emplace_back(default_std_argument); + + auto clang_version_string = clangmm::to_string(clang_getClangVersion()); + const static std::regex clang_version_regex("^[A-Za-z ]+([0-9.]+).*$"); + std::smatch sm; + if (std::regex_match(clang_version_string, sm, clang_version_regex)) { + auto clang_version = sm[1].str(); + arguments.emplace_back("-I/usr/lib/clang/" + clang_version + "/include"); + arguments.emplace_back("-I/usr/lib64/clang/" + clang_version + "/include"); // For Fedora +#if defined(__APPLE__) && CINDEX_VERSION_MAJOR == 0 && CINDEX_VERSION_MINOR < 32 // TODO: remove during 2018 if llvm3.7 is no longer in homebrew (CINDEX_VERSION_MINOR=32 equals clang-3.8 I think) + arguments.emplace_back("-I/usr/local/Cellar/llvm/"+clang_version+"/lib/clang/"+clang_version+"/include"); + arguments.emplace_back("-I/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1"); + arguments.emplace_back("-I/Library/Developer/CommandLineTools/usr/bin/../include/c++/v1"); //Added for OS X 10.11 #endif #ifdef _WIN32 - auto env_msystem_prefix=std::getenv("MSYSTEM_PREFIX"); - if(env_msystem_prefix!=nullptr) - arguments.emplace_back("-I"+(boost::filesystem::path(env_msystem_prefix)/"lib/clang"/clang_version/"include").string()); + auto env_msystem_prefix = std::getenv("MSYSTEM_PREFIX"); + if (env_msystem_prefix != nullptr) + arguments.emplace_back("-I" + (boost::filesystem::path(env_msystem_prefix) / "lib/clang" / clang_version / + "include").string()); #endif - } - arguments.emplace_back("-fretain-comments-from-system-headers"); - - auto extension=file_path.extension().string(); - if(extension==".h" || //TODO: temporary fix for .h-files (parse as c++) - extension!=".c") - arguments.emplace_back("-xc++"); - - if(extension.empty() || (1<extension.size() && extension[1]=='h') || extension==".tcc" || extension==".cuh") { - arguments.emplace_back("-Wno-pragma-once-outside-header"); - arguments.emplace_back("-Wno-pragma-system-header-outside-header"); - arguments.emplace_back("-Wno-include-next-outside-header"); - } - - if(extension==".cu" || extension==".cuh") { - arguments.emplace_back("-include"); - arguments.emplace_back("cuda_runtime.h"); - } - - if(extension==".cl") { - arguments.emplace_back("-xcl"); - arguments.emplace_back("-cl-std=CL2.0"); - arguments.emplace_back("-Xclang"); - arguments.emplace_back("-finclude-default-header"); - arguments.emplace_back("-Wno-gcc-compat"); - } - - if(!build_path.empty()) { - arguments.emplace_back("-working-directory"); - arguments.emplace_back(build_path.string()); - } - - return arguments; + } + arguments.emplace_back("-fretain-comments-from-system-headers"); + + auto extension = file_path.extension().string(); + if (extension == ".h" || //TODO: temporary fix for .h-files (parse as c++) + extension != ".c") + arguments.emplace_back("-xc++"); + + if (extension.empty() || (1 < extension.size() && extension[1] == 'h') || extension == ".tcc" || + extension == ".cuh") { + arguments.emplace_back("-Wno-pragma-once-outside-header"); + arguments.emplace_back("-Wno-pragma-system-header-outside-header"); + arguments.emplace_back("-Wno-include-next-outside-header"); + } + + if (extension == ".cu" || extension == ".cuh") { + arguments.emplace_back("-include"); + arguments.emplace_back("cuda_runtime.h"); + } + + if (extension == ".cl") { + arguments.emplace_back("-xcl"); + arguments.emplace_back("-cl-std=CL2.0"); + arguments.emplace_back("-Xclang"); + arguments.emplace_back("-finclude-default-header"); + arguments.emplace_back("-Wno-gcc-compat"); + } + + if (!build_path.empty()) { + arguments.emplace_back("-working-directory"); + arguments.emplace_back(build_path.string()); + } + + return arguments; } diff --git a/src/compile_commands.h b/src/compile_commands.h index 642cd33f..83c3d6dd 100644 --- a/src/compile_commands.h +++ b/src/compile_commands.h @@ -1,22 +1,25 @@ #pragma once + #include <boost/filesystem.hpp> #include <vector> #include <string> class CompileCommands { public: - class Command { - public: - boost::filesystem::path directory; - std::vector<std::string> parameters; - boost::filesystem::path file; - - std::vector<std::string> parameter_values(const std::string ¶meter_name) const; - }; - - CompileCommands(const boost::filesystem::path &build_path); - std::vector<Command> commands; - - /// Return arguments for the given file using libclangmm - static std::vector<std::string> get_arguments(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path); + class Command { + public: + boost::filesystem::path directory; + std::vector<std::string> parameters; + boost::filesystem::path file; + + std::vector<std::string> parameter_values(const std::string ¶meter_name) const; + }; + + CompileCommands(const boost::filesystem::path &build_path); + + std::vector<Command> commands; + + /// Return arguments for the given file using libclangmm + static std::vector<std::string> + get_arguments(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path); }; diff --git a/src/config.cc b/src/config.cc index 934b4388..625da547 100644 --- a/src/config.cc +++ b/src/config.cc @@ -7,198 +7,203 @@ #include <algorithm> Config::Config() { - home_path=filesystem::get_home_path(); - if(home_path.empty()) - throw std::runtime_error("Could not find home path"); - home_juci_path=home_path/".juci"; + home_path = filesystem::get_home_path(); + if (home_path.empty()) + throw std::runtime_error("Could not find home path"); + home_juci_path = home_path / ".juci"; } void Config::load() { - auto config_json = (home_juci_path/"config"/"config.json").string(); // This causes some redundant copies, but assures windows support - boost::property_tree::ptree cfg; - try { - find_or_create_config_files(); - boost::property_tree::json_parser::read_json(config_json, cfg); - update(cfg); - read(cfg); - } - catch(const std::exception &e) { - dispatcher.post([config_json, e_what=std::string(e.what())] { - ::Terminal::get().print("Error: could not parse "+config_json+": "+e_what+"\n", true); - }); - std::stringstream ss; - ss << default_config_file; - boost::property_tree::read_json(ss, cfg); - read(cfg); - } + auto config_json = (home_juci_path / "config" / + "config.json").string(); // This causes some redundant copies, but assures windows support + boost::property_tree::ptree cfg; + try { + find_or_create_config_files(); + boost::property_tree::json_parser::read_json(config_json, cfg); + update(cfg); + read(cfg); + } + catch (const std::exception &e) { + dispatcher.post([config_json, e_what = std::string(e.what())] { + ::Terminal::get().print("Error: could not parse " + config_json + ": " + e_what + "\n", true); + }); + std::stringstream ss; + ss << default_config_file; + boost::property_tree::read_json(ss, cfg); + read(cfg); + } } void Config::find_or_create_config_files() { - auto config_dir = home_juci_path/"config"; - auto config_json = config_dir/"config.json"; - - boost::filesystem::create_directories(config_dir); // io exp captured by calling method - - if (!boost::filesystem::exists(config_json)) - filesystem::write(config_json, default_config_file); - - auto juci_style_path = home_juci_path/"styles"; - boost::filesystem::create_directories(juci_style_path); // io exp captured by calling method - - juci_style_path/="juci-light.xml"; - if(!boost::filesystem::exists(juci_style_path)) - filesystem::write(juci_style_path, juci_light_style); - juci_style_path=juci_style_path.parent_path(); - juci_style_path/="juci-dark.xml"; - if(!boost::filesystem::exists(juci_style_path)) - filesystem::write(juci_style_path, juci_dark_style); - juci_style_path=juci_style_path.parent_path(); - juci_style_path/="juci-dark-blue.xml"; - if(!boost::filesystem::exists(juci_style_path)) - filesystem::write(juci_style_path, juci_dark_blue_style); + auto config_dir = home_juci_path / "config"; + auto config_json = config_dir / "config.json"; + + boost::filesystem::create_directories(config_dir); // io exp captured by calling method + + if (!boost::filesystem::exists(config_json)) + filesystem::write(config_json, default_config_file); + + auto juci_style_path = home_juci_path / "styles"; + boost::filesystem::create_directories(juci_style_path); // io exp captured by calling method + + juci_style_path /= "juci-light.xml"; + if (!boost::filesystem::exists(juci_style_path)) + filesystem::write(juci_style_path, juci_light_style); + juci_style_path = juci_style_path.parent_path(); + juci_style_path /= "juci-dark.xml"; + if (!boost::filesystem::exists(juci_style_path)) + filesystem::write(juci_style_path, juci_dark_style); + juci_style_path = juci_style_path.parent_path(); + juci_style_path /= "juci-dark-blue.xml"; + if (!boost::filesystem::exists(juci_style_path)) + filesystem::write(juci_style_path, juci_dark_blue_style); } void Config::update(boost::property_tree::ptree &cfg) { - boost::property_tree::ptree default_cfg; - bool cfg_ok=true; - if(cfg.get<std::string>("version")!=JUCI_VERSION) { - std::stringstream ss; - ss << default_config_file; - boost::property_tree::read_json(ss, default_cfg); - cfg_ok=false; - auto it_version=cfg.find("version"); - if(it_version!=cfg.not_found()) { - make_version_dependent_corrections(cfg, default_cfg, it_version->second.data()); - it_version->second.data()=JUCI_VERSION; - } - - auto style_path=home_juci_path/"styles"; - filesystem::write(style_path/"juci-light.xml", juci_light_style); - filesystem::write(style_path/"juci-dark.xml", juci_dark_style); - filesystem::write(style_path/"juci-dark-blue.xml", juci_dark_blue_style); - } - else - return; - cfg_ok&=add_missing_nodes(cfg, default_cfg); - cfg_ok&=remove_deprecated_nodes(cfg, default_cfg); - if(!cfg_ok) - boost::property_tree::write_json((home_juci_path/"config"/"config.json").string(), cfg); -} + boost::property_tree::ptree default_cfg; + bool cfg_ok = true; + if (cfg.get<std::string>("version") != JUCI_VERSION) { + std::stringstream ss; + ss << default_config_file; + boost::property_tree::read_json(ss, default_cfg); + cfg_ok = false; + auto it_version = cfg.find("version"); + if (it_version != cfg.not_found()) { + make_version_dependent_corrections(cfg, default_cfg, it_version->second.data()); + it_version->second.data() = JUCI_VERSION; + } -void Config::make_version_dependent_corrections(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, const std::string &version) { - auto &keybindings_cfg=cfg.get_child("keybindings"); - try { - if(version<="1.2.4") { - auto it_file_print=keybindings_cfg.find("print"); - if(it_file_print!=keybindings_cfg.not_found() && it_file_print->second.data()=="<primary>p") { - dispatcher.post([] { - ::Terminal::get().print("Preference change: keybindings.print set to \"\"\n"); - }); - it_file_print->second.data()=""; - } - } - } - catch(const std::exception &e) { - std::cerr << "Error correcting preferences: " << e.what() << std::endl; - } + auto style_path = home_juci_path / "styles"; + filesystem::write(style_path / "juci-light.xml", juci_light_style); + filesystem::write(style_path / "juci-dark.xml", juci_dark_style); + filesystem::write(style_path / "juci-dark-blue.xml", juci_dark_blue_style); + } else + return; + cfg_ok &= add_missing_nodes(cfg, default_cfg); + cfg_ok &= remove_deprecated_nodes(cfg, default_cfg); + if (!cfg_ok) + boost::property_tree::write_json((home_juci_path / "config" / "config.json").string(), cfg); } -bool Config::add_missing_nodes(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, std::string parent_path) { - if(parent_path.size()>0) - parent_path+="."; - bool unchanged=true; - for(auto &node: default_cfg) { - auto path=parent_path+node.first; +void Config::make_version_dependent_corrections(boost::property_tree::ptree &cfg, + const boost::property_tree::ptree &default_cfg, + const std::string &version) { + auto &keybindings_cfg = cfg.get_child("keybindings"); try { - cfg.get<std::string>(path); + if (version <= "1.2.4") { + auto it_file_print = keybindings_cfg.find("print"); + if (it_file_print != keybindings_cfg.not_found() && it_file_print->second.data() == "<primary>p") { + dispatcher.post([] { + ::Terminal::get().print("Preference change: keybindings.print set to \"\"\n"); + }); + it_file_print->second.data() = ""; + } + } } - catch(const std::exception &e) { - cfg.add(path, node.second.data()); - unchanged=false; + catch (const std::exception &e) { + std::cerr << "Error correcting preferences: " << e.what() << std::endl; } - unchanged&=add_missing_nodes(cfg, node.second, path); - } - return unchanged; } -bool Config::remove_deprecated_nodes(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, std::string parent_path) { - if(parent_path.size()>0) - parent_path+="."; - bool unchanged=true; - for(auto it=cfg.begin();it!=cfg.end();) { - auto path=parent_path+it->first; - try { - default_cfg.get<std::string>(path); - unchanged&=remove_deprecated_nodes(it->second, default_cfg, path); - ++it; +bool Config::add_missing_nodes(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, + std::string parent_path) { + if (parent_path.size() > 0) + parent_path += "."; + bool unchanged = true; + for (auto &node: default_cfg) { + auto path = parent_path + node.first; + try { + cfg.get<std::string>(path); + } + catch (const std::exception &e) { + cfg.add(path, node.second.data()); + unchanged = false; + } + unchanged &= add_missing_nodes(cfg, node.second, path); } - catch(const std::exception &e) { - it=cfg.erase(it); - unchanged=false; + return unchanged; +} + +bool Config::remove_deprecated_nodes(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, + std::string parent_path) { + if (parent_path.size() > 0) + parent_path += "."; + bool unchanged = true; + for (auto it = cfg.begin(); it != cfg.end();) { + auto path = parent_path + it->first; + try { + default_cfg.get<std::string>(path); + unchanged &= remove_deprecated_nodes(it->second, default_cfg, path); + ++it; + } + catch (const std::exception &e) { + it = cfg.erase(it); + unchanged = false; + } } - } - return unchanged; + return unchanged; } void Config::read(const boost::property_tree::ptree &cfg) { - auto keybindings_pt = cfg.get_child("keybindings"); - for (auto &i : keybindings_pt) { - menu.keys[i.first] = i.second.get_value<std::string>(); - } - - auto source_json = cfg.get_child("source"); - source.style=source_json.get<std::string>("style"); - source.font=source_json.get<std::string>("font"); - source.cleanup_whitespace_characters=source_json.get<bool>("cleanup_whitespace_characters"); - source.show_whitespace_characters=source_json.get<std::string>("show_whitespace_characters"); - source.format_style_on_save=source_json.get<bool>("format_style_on_save"); - source.format_style_on_save_if_style_file_found=source_json.get<bool>("format_style_on_save_if_style_file_found"); - source.smart_brackets=source_json.get<bool>("smart_brackets"); - source.smart_inserts=source_json.get<bool>("smart_inserts"); - if(source.smart_inserts) - source.smart_brackets=true; - source.show_map = source_json.get<bool>("show_map"); - source.map_font_size = source_json.get<std::string>("map_font_size"); - source.show_git_diff = source_json.get<bool>("show_git_diff"); - source.show_background_pattern = source_json.get<bool>("show_background_pattern"); - source.show_right_margin = source_json.get<bool>("show_right_margin"); - source.right_margin_position = source_json.get<unsigned>("right_margin_position"); - source.spellcheck_language = source_json.get<std::string>("spellcheck_language"); - source.default_tab_char = source_json.get<char>("default_tab_char"); - source.default_tab_size = source_json.get<unsigned>("default_tab_size"); - source.auto_tab_char_and_size = source_json.get<bool>("auto_tab_char_and_size"); - source.tab_indents_line = source_json.get<bool>("tab_indents_line"); - source.wrap_lines = source_json.get<bool>("wrap_lines"); - source.highlight_current_line = source_json.get<bool>("highlight_current_line"); - source.show_line_numbers = source_json.get<bool>("show_line_numbers"); - source.enable_multiple_cursors = source_json.get<bool>("enable_multiple_cursors"); - source.auto_reload_changed_files = source_json.get<bool>("auto_reload_changed_files"); - source.clang_format_style = source_json.get<std::string>("clang_format_style"); - source.clang_usages_threads = static_cast<unsigned>(source_json.get<int>("clang_usages_threads")); - auto pt_doc_search=cfg.get_child("documentation_searches"); - for(auto &pt_doc_search_lang: pt_doc_search) { - source.documentation_searches[pt_doc_search_lang.first].separator=pt_doc_search_lang.second.get<std::string>("separator"); - auto &queries=source.documentation_searches.find(pt_doc_search_lang.first)->second.queries; - for(auto &i: pt_doc_search_lang.second.get_child("queries")) { - queries[i.first]=i.second.get_value<std::string>(); + auto keybindings_pt = cfg.get_child("keybindings"); + for (auto &i : keybindings_pt) { + menu.keys[i.first] = i.second.get_value<std::string>(); } - } - - window.theme_name=cfg.get<std::string>("gtk_theme.name"); - window.theme_variant=cfg.get<std::string>("gtk_theme.variant"); - window.version = cfg.get<std::string>("version"); - - 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.cmake.command=cfg.get<std::string>("project.cmake.command"); - project.cmake.compile_command=cfg.get<std::string>("project.cmake.compile_command"); - project.meson.command=cfg.get<std::string>("project.meson.command"); - project.meson.compile_command=cfg.get<std::string>("project.meson.compile_command"); - project.save_on_compile_or_run=cfg.get<bool>("project.save_on_compile_or_run"); - project.clear_terminal_on_compile=cfg.get<bool>("project.clear_terminal_on_compile"); - project.ctags_command=cfg.get<std::string>("project.ctags_command"); - project.python_command=cfg.get<std::string>("project.python_command"); - - terminal.history_size=cfg.get<int>("terminal.history_size"); - terminal.font=cfg.get<std::string>("terminal.font"); + + auto source_json = cfg.get_child("source"); + source.style = source_json.get<std::string>("style"); + source.font = source_json.get<std::string>("font"); + source.cleanup_whitespace_characters = source_json.get<bool>("cleanup_whitespace_characters"); + source.show_whitespace_characters = source_json.get<std::string>("show_whitespace_characters"); + source.format_style_on_save = source_json.get<bool>("format_style_on_save"); + source.format_style_on_save_if_style_file_found = source_json.get<bool>("format_style_on_save_if_style_file_found"); + source.smart_brackets = source_json.get<bool>("smart_brackets"); + source.smart_inserts = source_json.get<bool>("smart_inserts"); + if (source.smart_inserts) + source.smart_brackets = true; + source.show_map = source_json.get<bool>("show_map"); + source.map_font_size = source_json.get<std::string>("map_font_size"); + source.show_git_diff = source_json.get<bool>("show_git_diff"); + source.show_background_pattern = source_json.get<bool>("show_background_pattern"); + source.show_right_margin = source_json.get<bool>("show_right_margin"); + source.right_margin_position = source_json.get<unsigned>("right_margin_position"); + source.spellcheck_language = source_json.get<std::string>("spellcheck_language"); + source.default_tab_char = source_json.get<char>("default_tab_char"); + source.default_tab_size = source_json.get<unsigned>("default_tab_size"); + source.auto_tab_char_and_size = source_json.get<bool>("auto_tab_char_and_size"); + source.tab_indents_line = source_json.get<bool>("tab_indents_line"); + source.wrap_lines = source_json.get<bool>("wrap_lines"); + source.highlight_current_line = source_json.get<bool>("highlight_current_line"); + source.show_line_numbers = source_json.get<bool>("show_line_numbers"); + source.enable_multiple_cursors = source_json.get<bool>("enable_multiple_cursors"); + source.auto_reload_changed_files = source_json.get<bool>("auto_reload_changed_files"); + source.clang_format_style = source_json.get<std::string>("clang_format_style"); + source.clang_usages_threads = static_cast<unsigned>(source_json.get<int>("clang_usages_threads")); + auto pt_doc_search = cfg.get_child("documentation_searches"); + for (auto &pt_doc_search_lang: pt_doc_search) { + source.documentation_searches[pt_doc_search_lang.first].separator = pt_doc_search_lang.second.get<std::string>( + "separator"); + auto &queries = source.documentation_searches.find(pt_doc_search_lang.first)->second.queries; + for (auto &i: pt_doc_search_lang.second.get_child("queries")) { + queries[i.first] = i.second.get_value<std::string>(); + } + } + + window.theme_name = cfg.get<std::string>("gtk_theme.name"); + window.theme_variant = cfg.get<std::string>("gtk_theme.variant"); + window.version = cfg.get<std::string>("version"); + + 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.cmake.command = cfg.get<std::string>("project.cmake.command"); + project.cmake.compile_command = cfg.get<std::string>("project.cmake.compile_command"); + project.meson.command = cfg.get<std::string>("project.meson.command"); + project.meson.compile_command = cfg.get<std::string>("project.meson.compile_command"); + project.save_on_compile_or_run = cfg.get<bool>("project.save_on_compile_or_run"); + project.clear_terminal_on_compile = cfg.get<bool>("project.clear_terminal_on_compile"); + project.ctags_command = cfg.get<std::string>("project.ctags_command"); + project.python_command = cfg.get<std::string>("project.python_command"); + + terminal.history_size = cfg.get<int>("terminal.history_size"); + terminal.font = cfg.get<std::string>("terminal.font"); } diff --git a/src/config.h b/src/config.h index 1c1000be..5ec14d86 100644 --- a/src/config.h +++ b/src/config.h @@ -1,4 +1,5 @@ #pragma once + #include <boost/property_tree/json_parser.hpp> #include <boost/filesystem.hpp> #include <unordered_map> @@ -9,117 +10,129 @@ class Config { public: - class Menu { - public: - std::unordered_map<std::string, std::string> keys; - }; - - class Window { - public: - std::string theme_name; - std::string theme_variant; - std::string version; - }; - - class Terminal { - public: - int history_size; - std::string font; - }; - - class Project { - public: - class CMake { + class Menu { + public: + std::unordered_map<std::string, std::string> keys; + }; + + class Window { public: - std::string command; - std::string compile_command; + std::string theme_name; + std::string theme_variant; + std::string version; }; - class Meson { + + class Terminal { public: - std::string command; - std::string compile_command; + int history_size; + std::string font; }; - - std::string default_build_path; - std::string debug_build_path; - CMake cmake; - Meson meson; - bool save_on_compile_or_run; - bool clear_terminal_on_compile; - std::string ctags_command; - std::string python_command; - }; - - class Source { - public: - class DocumentationSearch { + + class Project { public: - std::string separator; - std::unordered_map<std::string, std::string> queries; + class CMake { + public: + std::string command; + std::string compile_command; + }; + + class Meson { + public: + std::string command; + std::string compile_command; + }; + + std::string default_build_path; + std::string debug_build_path; + CMake cmake; + Meson meson; + bool save_on_compile_or_run; + bool clear_terminal_on_compile; + std::string ctags_command; + std::string python_command; + }; + + class Source { + public: + class DocumentationSearch { + public: + std::string separator; + std::unordered_map<std::string, std::string> queries; + }; + + std::string style; + std::string font; + std::string spellcheck_language; + + bool cleanup_whitespace_characters; + std::string show_whitespace_characters; + + bool format_style_on_save; + bool format_style_on_save_if_style_file_found; + + bool smart_brackets; + bool smart_inserts; + + bool show_map; + std::string map_font_size; + bool show_git_diff; + bool show_background_pattern; + bool show_right_margin; + unsigned right_margin_position; + + bool auto_tab_char_and_size; + char default_tab_char; + unsigned default_tab_size; + bool tab_indents_line; + bool wrap_lines; + bool highlight_current_line; + bool show_line_numbers; + bool enable_multiple_cursors; + bool auto_reload_changed_files; + + std::string clang_format_style; + unsigned clang_usages_threads; + + std::unordered_map<std::string, DocumentationSearch> documentation_searches; }; - - std::string style; - std::string font; - std::string spellcheck_language; - - bool cleanup_whitespace_characters; - std::string show_whitespace_characters; - - bool format_style_on_save; - bool format_style_on_save_if_style_file_found; - - bool smart_brackets; - bool smart_inserts; - - bool show_map; - std::string map_font_size; - bool show_git_diff; - bool show_background_pattern; - bool show_right_margin; - unsigned right_margin_position; - - bool auto_tab_char_and_size; - char default_tab_char; - unsigned default_tab_size; - bool tab_indents_line; - bool wrap_lines; - bool highlight_current_line; - bool show_line_numbers; - bool enable_multiple_cursors; - bool auto_reload_changed_files; - - std::string clang_format_style; - unsigned clang_usages_threads; - - std::unordered_map<std::string, DocumentationSearch> documentation_searches; - }; + private: - Config(); + Config(); + public: - static Config &get() { - static Config singleton; - return singleton; - } - - void load(); - - Menu menu; - Window window; - Terminal terminal; - Project project; - Source source; - - boost::filesystem::path home_path; - boost::filesystem::path home_juci_path; + static Config &get() { + static Config singleton; + return singleton; + } + + void load(); + + Menu menu; + Window window; + Terminal terminal; + Project project; + Source source; + + boost::filesystem::path home_path; + boost::filesystem::path home_juci_path; private: - /// Used to dispatch Terminal outputs after juCi++ GUI setup and configuration - Dispatcher dispatcher; - - void find_or_create_config_files(); - void update(boost::property_tree::ptree &cfg); - void make_version_dependent_corrections(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, const std::string &version); - bool add_missing_nodes(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, std::string parent_path=""); - bool remove_deprecated_nodes(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, std::string parent_path=""); - void read(const boost::property_tree::ptree &cfg); + /// Used to dispatch Terminal outputs after juCi++ GUI setup and configuration + Dispatcher dispatcher; + + void find_or_create_config_files(); + + void update(boost::property_tree::ptree &cfg); + + void + make_version_dependent_corrections(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, + const std::string &version); + + bool add_missing_nodes(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, + std::string parent_path = ""); + + bool remove_deprecated_nodes(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, + std::string parent_path = ""); + + void read(const boost::property_tree::ptree &cfg); }; diff --git a/src/ctags.cc b/src/ctags.cc index 31a0845f..e51213c1 100644 --- a/src/ctags.cc +++ b/src/ctags.cc @@ -8,199 +8,199 @@ #include <regex> #include <climits> -std::pair<boost::filesystem::path, std::unique_ptr<std::stringstream> > Ctags::get_result(const boost::filesystem::path &path) { - auto build=Project::Build::create(path); - auto run_path=build->project_path; - std::string exclude; - if(!run_path.empty()) { - auto relative_default_path=filesystem::get_relative_path(build->get_default_path(), run_path); - if(!relative_default_path.empty()) - exclude+=" --exclude="+relative_default_path.string(); - - auto relative_debug_path=filesystem::get_relative_path(build->get_debug_path(), run_path); - if(!relative_debug_path.empty()) - exclude+=" --exclude="+relative_debug_path.string(); - } - else { - boost::system::error_code ec; - if(boost::filesystem::is_directory(path, ec) || ec) - run_path=path; - else - run_path=path.parent_path(); - } - - std::stringstream stdin_stream; - //TODO: when debian stable gets newer g++ version that supports move on streams, remove unique_ptr below - auto stdout_stream=std::make_unique<std::stringstream>(); - auto command=Config::get().project.ctags_command+exclude+" --fields=ns --sort=foldcase -I \"override noexcept\" -f - -R *"; - Terminal::get().process(stdin_stream, *stdout_stream, command, run_path); - return {run_path, std::move(stdout_stream)}; +std::pair<boost::filesystem::path, std::unique_ptr<std::stringstream> > +Ctags::get_result(const boost::filesystem::path &path) { + auto build = Project::Build::create(path); + auto run_path = build->project_path; + std::string exclude; + if (!run_path.empty()) { + auto relative_default_path = filesystem::get_relative_path(build->get_default_path(), run_path); + if (!relative_default_path.empty()) + exclude += " --exclude=" + relative_default_path.string(); + + auto relative_debug_path = filesystem::get_relative_path(build->get_debug_path(), run_path); + if (!relative_debug_path.empty()) + exclude += " --exclude=" + relative_debug_path.string(); + } else { + boost::system::error_code ec; + if (boost::filesystem::is_directory(path, ec) || ec) + run_path = path; + else + run_path = path.parent_path(); + } + + std::stringstream stdin_stream; + //TODO: when debian stable gets newer g++ version that supports move on streams, remove unique_ptr below + auto stdout_stream = std::make_unique<std::stringstream>(); + auto command = Config::get().project.ctags_command + exclude + + " --fields=ns --sort=foldcase -I \"override noexcept\" -f - -R *"; + Terminal::get().process(stdin_stream, *stdout_stream, command, run_path); + return {run_path, std::move(stdout_stream)}; } Ctags::Location Ctags::get_location(const std::string &line, bool markup) { - Location location; + Location location; #ifdef _WIN32 - auto line_fixed=line; - if(!line_fixed.empty() && line_fixed.back()=='\r') - line_fixed.pop_back(); + auto line_fixed = line; + if (!line_fixed.empty() && line_fixed.back() == '\r') + line_fixed.pop_back(); #else - auto &line_fixed=line; + auto &line_fixed=line; #endif - const static std::regex regex("^([^\t]+)\t([^\t]+)\t(?:/\\^)?([ \t]*)(.+?)(\\$/)?;\"\tline:([0-9]+)\t?[a-zA-Z]*:?(.*)$"); - std::smatch sm; - if(std::regex_match(line_fixed, sm, regex)) { - location.symbol=sm[1].str(); - //fix location.symbol for operators - if(9<location.symbol.size() && location.symbol[8]==' ' && location.symbol.compare(0, 8, "operator")==0) { - auto &chr=location.symbol[9]; - if(!((chr>='a' && chr<='z') || (chr>='A' && chr<='Z') || (chr>='0' && chr<='9') || chr=='_')) - location.symbol.erase(8, 1); - } - - location.file_path=sm[2].str(); - location.source=sm[4].str(); - try { - location.line=std::stoul(sm[6])-1; - } - catch(const std::exception&) { - location.line=0; - } - location.scope=sm[7].str(); - if(!sm[5].str().empty()) { - location.index=sm[3].str().size(); - - size_t pos=location.source.find(location.symbol); - if(pos!=std::string::npos) - location.index+=pos; - - if(markup) { - location.source=Glib::Markup::escape_text(location.source); - auto symbol=Glib::Markup::escape_text(location.symbol); - pos=-1; - while((pos=location.source.find(symbol, pos+1))!=std::string::npos) { - location.source.insert(pos+symbol.size(), "</b>"); - location.source.insert(pos, "<b>"); - pos+=7+symbol.size(); + const static std::regex regex( + "^([^\t]+)\t([^\t]+)\t(?:/\\^)?([ \t]*)(.+?)(\\$/)?;\"\tline:([0-9]+)\t?[a-zA-Z]*:?(.*)$"); + std::smatch sm; + if (std::regex_match(line_fixed, sm, regex)) { + location.symbol = sm[1].str(); + //fix location.symbol for operators + if (9 < location.symbol.size() && location.symbol[8] == ' ' && location.symbol.compare(0, 8, "operator") == 0) { + auto &chr = location.symbol[9]; + if (!((chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z') || (chr >= '0' && chr <= '9') || chr == '_')) + location.symbol.erase(8, 1); } - } - } - else { - location.index=0; - location.source=location.symbol; - if(markup) - location.source="<b>"+Glib::Markup::escape_text(location.source)+"</b>"; - } - } - else - std::cerr << "Warning (ctags): please report to the juCi++ project that the following line was not parsed:\n" << line << std::endl; - - return location; + + location.file_path = sm[2].str(); + location.source = sm[4].str(); + try { + location.line = std::stoul(sm[6]) - 1; + } + catch (const std::exception &) { + location.line = 0; + } + location.scope = sm[7].str(); + if (!sm[5].str().empty()) { + location.index = sm[3].str().size(); + + size_t pos = location.source.find(location.symbol); + if (pos != std::string::npos) + location.index += pos; + + if (markup) { + location.source = Glib::Markup::escape_text(location.source); + auto symbol = Glib::Markup::escape_text(location.symbol); + pos = -1; + while ((pos = location.source.find(symbol, pos + 1)) != std::string::npos) { + location.source.insert(pos + symbol.size(), "</b>"); + location.source.insert(pos, "<b>"); + pos += 7 + symbol.size(); + } + } + } else { + location.index = 0; + location.source = location.symbol; + if (markup) + location.source = "<b>" + Glib::Markup::escape_text(location.source) + "</b>"; + } + } else + std::cerr << "Warning (ctags): please report to the juCi++ project that the following line was not parsed:\n" + << line << std::endl; + + return location; } ///Split up a type into its various significant parts std::vector<std::string> Ctags::get_type_parts(const std::string type) { - std::vector<std::string> parts; - size_t text_start=-1; - for(size_t c=0;c<type.size();++c) { - auto &chr=type[c]; - if((chr>='0' && chr<='9') || (chr>='a' && chr<='z') || (chr>='A' && chr<='Z') || chr=='_' || chr=='~') { - if(text_start==static_cast<size_t>(-1)) - text_start=c; - } - else { - if(text_start!=static_cast<size_t>(-1)) { - parts.emplace_back(type.substr(text_start, c-text_start)); - text_start=-1; - } - if(chr=='*' || chr=='&') - parts.emplace_back(std::string()+chr); + std::vector<std::string> parts; + size_t text_start = -1; + for (size_t c = 0; c < type.size(); ++c) { + auto &chr = type[c]; + if ((chr >= '0' && chr <= '9') || (chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z') || chr == '_' || + chr == '~') { + if (text_start == static_cast<size_t>(-1)) + text_start = c; + } else { + if (text_start != static_cast<size_t>(-1)) { + parts.emplace_back(type.substr(text_start, c - text_start)); + text_start = -1; + } + if (chr == '*' || chr == '&') + parts.emplace_back(std::string() + chr); + } } - } - return parts; + return parts; } -std::vector<Ctags::Location> Ctags::get_locations(const boost::filesystem::path &path, const std::string &name, const std::string &type) { - auto result=get_result(path); - result.second->seekg(0, std::ios::end); - if(result.second->tellg()==0) - return std::vector<Location>(); - result.second->seekg(0, std::ios::beg); - - //insert name into type - size_t c=0; - size_t bracket_count=0; - for(;c<type.size();++c) { - if(type[c]=='<') - ++bracket_count; - else if(type[c]=='>') - --bracket_count; - else if(bracket_count==0 && type[c]=='(') - break; - } - auto full_type=type; - full_type.insert(c, name); - - auto parts=get_type_parts(full_type); - - std::string line; - long best_score=LONG_MIN; - std::vector<Location> best_locations; - while(std::getline(*result.second, line)) { - if(line.size()>2048) - continue; - auto location=Ctags::get_location(line, false); - if(!location.scope.empty()) { - if(location.scope+"::"+location.symbol!=name) - continue; +std::vector<Ctags::Location> +Ctags::get_locations(const boost::filesystem::path &path, const std::string &name, const std::string &type) { + auto result = get_result(path); + result.second->seekg(0, std::ios::end); + if (result.second->tellg() == 0) + return std::vector<Location>(); + result.second->seekg(0, std::ios::beg); + + //insert name into type + size_t c = 0; + size_t bracket_count = 0; + for (; c < type.size(); ++c) { + if (type[c] == '<') + ++bracket_count; + else if (type[c] == '>') + --bracket_count; + else if (bracket_count == 0 && type[c] == '(') + break; } - else if(location.symbol!=name) - continue; - - location.file_path=result.first/location.file_path; - - auto source_parts=get_type_parts(location.source); - - //Find match score - long score=0; - size_t source_index=0; - for(auto &part: parts) { - bool found=false; - for(auto c=source_index;c<source_parts.size();++c) { - if(part==source_parts[c]) { - source_index=c+1; - ++score; - found=true; - break; + auto full_type = type; + full_type.insert(c, name); + + auto parts = get_type_parts(full_type); + + std::string line; + long best_score = LONG_MIN; + std::vector<Location> best_locations; + while (std::getline(*result.second, line)) { + if (line.size() > 2048) + continue; + auto location = Ctags::get_location(line, false); + if (!location.scope.empty()) { + if (location.scope + "::" + location.symbol != name) + continue; + } else if (location.symbol != name) + continue; + + location.file_path = result.first / location.file_path; + + auto source_parts = get_type_parts(location.source); + + //Find match score + long score = 0; + size_t source_index = 0; + for (auto &part: parts) { + bool found = false; + for (auto c = source_index; c < source_parts.size(); ++c) { + if (part == source_parts[c]) { + source_index = c + 1; + ++score; + found = true; + break; + } + } + if (!found) + --score; } - } - if(!found) - --score; - } - size_t index=0; - for(auto &source_part: source_parts) { - bool found=false; - for(auto c=index;c<parts.size();++c) { - if(source_part==parts[c]) { - index=c+1; - ++score; - found=true; - break; + size_t index = 0; + for (auto &source_part: source_parts) { + bool found = false; + for (auto c = index; c < parts.size(); ++c) { + if (source_part == parts[c]) { + index = c + 1; + ++score; + found = true; + break; + } + } + if (!found) + --score; } - } - if(!found) - --score; - } - - if(score>best_score) { - best_score=score; - best_locations.clear(); - best_locations.emplace_back(location); + + if (score > best_score) { + best_score = score; + best_locations.clear(); + best_locations.emplace_back(location); + } else if (score == best_score) + best_locations.emplace_back(location); } - else if(score==best_score) - best_locations.emplace_back(location); - } - - return best_locations; + + return best_locations; } diff --git a/src/ctags.h b/src/ctags.h index f39ddfb2..0d0aefad 100644 --- a/src/ctags.h +++ b/src/ctags.h @@ -1,4 +1,5 @@ #pragma once + #include <string> #include <boost/filesystem.hpp> #include <sstream> @@ -6,22 +7,26 @@ class Ctags { public: - class Location { - public: - boost::filesystem::path file_path; - unsigned long line; - unsigned long index; - std::string symbol; - std::string scope; - std::string source; - operator bool() const { return !file_path.empty(); } - }; - - static std::pair<boost::filesystem::path, std::unique_ptr<std::stringstream> > get_result(const boost::filesystem::path &path); - - static Location get_location(const std::string &line, bool markup); - - static std::vector<Location> get_locations(const boost::filesystem::path &path, const std::string &name, const std::string &type); + class Location { + public: + boost::filesystem::path file_path; + unsigned long line; + unsigned long index; + std::string symbol; + std::string scope; + std::string source; + + operator bool() const { return !file_path.empty(); } + }; + + static std::pair<boost::filesystem::path, std::unique_ptr<std::stringstream> > + get_result(const boost::filesystem::path &path); + + static Location get_location(const std::string &line, bool markup); + + static std::vector<Location> + get_locations(const boost::filesystem::path &path, const std::string &name, const std::string &type); + private: - static std::vector<std::string> get_type_parts(const std::string type); + static std::vector<std::string> get_type_parts(const std::string type); }; diff --git a/src/debug_lldb.cc b/src/debug_lldb.cc index f70b3aaa..7bfa7172 100644 --- a/src/debug_lldb.cc +++ b/src/debug_lldb.cc @@ -1,8 +1,10 @@ #include "debug_lldb.h" #include <stdio.h> + #ifdef __APPLE__ #include <stdlib.h> #endif + #include <boost/filesystem.hpp> #include <iostream> #include "terminal.h" @@ -13,519 +15,529 @@ extern char **environ; void log(const char *msg, void *) { - std::cout << "debugger log: " << msg << std::endl; + std::cout << "debugger log: " << msg << std::endl; } -Debug::LLDB::LLDB(): state(lldb::StateType::eStateInvalid), buffer_size(131072) { - if(!getenv("LLDB_DEBUGSERVER_PATH")) { +Debug::LLDB::LLDB() : state(lldb::StateType::eStateInvalid), buffer_size(131072) { + if (!getenv("LLDB_DEBUGSERVER_PATH")) { #ifdef __APPLE__ - std::string debug_server_path("/usr/local/opt/llvm/bin/debugserver"); - if(boost::filesystem::exists(debug_server_path)) - setenv("LLDB_DEBUGSERVER_PATH", debug_server_path.c_str(), 0); + std::string debug_server_path("/usr/local/opt/llvm/bin/debugserver"); + if(boost::filesystem::exists(debug_server_path)) + setenv("LLDB_DEBUGSERVER_PATH", debug_server_path.c_str(), 0); #else - auto debug_server_path = filesystem::get_executable("lldb-server").string(); - if(debug_server_path != "lldb-server") - setenv("LLDB_DEBUGSERVER_PATH", debug_server_path.c_str(), 0); + auto debug_server_path = filesystem::get_executable("lldb-server").string(); + if (debug_server_path != "lldb-server") + setenv("LLDB_DEBUGSERVER_PATH", debug_server_path.c_str(), 0); #endif - } + } } -std::tuple<std::vector<std::string>, std::string, std::vector<std::string> > Debug::LLDB::parse_run_arguments(const std::string &command) { - std::vector<std::string> environment; - std::string executable; - std::vector<std::string> arguments; - - size_t start_pos=std::string::npos; - bool quote=false; - bool double_quote=false; - size_t backslash_count=0; - for(size_t c=0;c<=command.size();c++) { - if(c==command.size() || (!quote && !double_quote && backslash_count%2==0 && command[c]==' ')) { - if(c>0 && start_pos!=std::string::npos) { - auto argument=command.substr(start_pos, c-start_pos); - if(executable.empty()) { - //Check for environment variable - bool env_arg=false; - for(size_t c=0;c<argument.size();++c) { - if((argument[c]>='a' && argument[c]<='z') || (argument[c]>='A' && argument[c]<='Z') || - (argument[c]>='0' && argument[c]<='9') || argument[c]=='_') - continue; - else if(argument[c]=='=' && c+1<argument.size()) { - environment.emplace_back(argument.substr(0, c+1)+filesystem::unescape_argument(argument.substr(c+1))); - env_arg=true; - break; - } - else - break; - } - - if(!env_arg) { - executable=filesystem::unescape_argument(argument); +std::tuple<std::vector<std::string>, std::string, std::vector<std::string> > +Debug::LLDB::parse_run_arguments(const std::string &command) { + std::vector<std::string> environment; + std::string executable; + std::vector<std::string> arguments; + + size_t start_pos = std::string::npos; + bool quote = false; + bool double_quote = false; + size_t backslash_count = 0; + for (size_t c = 0; c <= command.size(); c++) { + if (c == command.size() || (!quote && !double_quote && backslash_count % 2 == 0 && command[c] == ' ')) { + if (c > 0 && start_pos != std::string::npos) { + auto argument = command.substr(start_pos, c - start_pos); + if (executable.empty()) { + //Check for environment variable + bool env_arg = false; + for (size_t c = 0; c < argument.size(); ++c) { + if ((argument[c] >= 'a' && argument[c] <= 'z') || (argument[c] >= 'A' && argument[c] <= 'Z') || + (argument[c] >= '0' && argument[c] <= '9') || argument[c] == '_') + continue; + else if (argument[c] == '=' && c + 1 < argument.size()) { + environment.emplace_back( + argument.substr(0, c + 1) + filesystem::unescape_argument(argument.substr(c + 1))); + env_arg = true; + break; + } else + break; + } + + if (!env_arg) { + executable = filesystem::unescape_argument(argument); #ifdef _WIN32 - if(remote_host.empty()) - executable+=".exe"; + if (remote_host.empty()) + executable += ".exe"; #endif - } - } + } + } else + arguments.emplace_back(filesystem::unescape_argument(argument)); + start_pos = std::string::npos; + } + } else if (command[c] == '\'' && backslash_count % 2 == 0 && !double_quote) + quote = !quote; + else if (command[c] == '"' && backslash_count % 2 == 0 && !quote) + double_quote = !double_quote; + else if (command[c] == '\\' && !quote && !double_quote) + ++backslash_count; else - arguments.emplace_back(filesystem::unescape_argument(argument)); - start_pos=std::string::npos; - } + backslash_count = 0; + if (c < command.size() && start_pos == std::string::npos && command[c] != ' ') + start_pos = c; } - else if(command[c]=='\'' && backslash_count%2==0 && !double_quote) - quote=!quote; - else if(command[c]=='"' && backslash_count%2==0 && !quote) - double_quote=!double_quote; - else if(command[c]=='\\' && !quote && !double_quote) - ++backslash_count; - else - backslash_count=0; - if(c<command.size() && start_pos==std::string::npos && command[c]!=' ') - start_pos=c; - } - - return std::make_tuple(environment, executable, arguments); + + return std::make_tuple(environment, executable, arguments); } void Debug::LLDB::start(const std::string &command, const boost::filesystem::path &path, - const std::vector<std::pair<boost::filesystem::path, int> > &breakpoints, - const std::vector<std::string> &startup_commands, const std::string &remote_host) { - if(!debugger) { - lldb::SBDebugger::Initialize(); - debugger=std::make_unique<lldb::SBDebugger>(lldb::SBDebugger::Create(true, log, nullptr)); - listener=std::make_unique<lldb::SBListener>("juCi++ lldb listener"); - } - - //Create executable string and argument array - auto parsed_run_arguments=parse_run_arguments(command); - auto &environment_from_arguments=std::get<0>(parsed_run_arguments); - auto &executable=std::get<1>(parsed_run_arguments); - auto &arguments=std::get<2>(parsed_run_arguments); - - std::vector<const char*> argv; - for(size_t c=0;c<arguments.size();c++) - argv.emplace_back(arguments[c].c_str()); - argv.emplace_back(nullptr); - - auto target=debugger->CreateTarget(executable.c_str()); - if(!target.IsValid()) { - Terminal::get().async_print("Error (debug): Could not create debug target to: "+executable+'\n', true); - for(auto &handler: on_exit) - handler(-1); - return; - } - - //Set 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); - for(auto &handler: on_exit) - handler(-1); - return; + const std::vector<std::pair<boost::filesystem::path, int> > &breakpoints, + const std::vector<std::string> &startup_commands, const std::string &remote_host) { + if (!debugger) { + lldb::SBDebugger::Initialize(); + debugger = std::make_unique<lldb::SBDebugger>(lldb::SBDebugger::Create(true, log, nullptr)); + listener = std::make_unique<lldb::SBListener>("juCi++ lldb listener"); } - } - - lldb::SBError error; - if(!remote_host.empty()) { - auto connect_string="connect://"+remote_host; - process = std::make_unique<lldb::SBProcess>(target.ConnectRemote(*listener, connect_string.c_str(), "gdb-remote", error)); - if(error.Fail()) { - Terminal::get().async_print(std::string("Error (debug): ")+error.GetCString()+'\n', true); - for(auto &handler: on_exit) - handler(-1); - return; + + //Create executable string and argument array + auto parsed_run_arguments = parse_run_arguments(command); + auto &environment_from_arguments = std::get<0>(parsed_run_arguments); + auto &executable = std::get<1>(parsed_run_arguments); + auto &arguments = std::get<2>(parsed_run_arguments); + + std::vector<const char *> argv; + for (size_t c = 0; c < arguments.size(); c++) + argv.emplace_back(arguments[c].c_str()); + argv.emplace_back(nullptr); + + auto target = debugger->CreateTarget(executable.c_str()); + if (!target.IsValid()) { + Terminal::get().async_print("Error (debug): Could not create debug target to: " + executable + '\n', true); + for (auto &handler: on_exit) + handler(-1); + return; } - lldb::SBEvent event; - while(true) { - if(listener->GetNextEvent(event)) { - if((event.GetType() & lldb::SBProcess::eBroadcastBitStateChanged)>0) { - auto state=process->GetStateFromEvent(event); - this->state=state; - if(state==lldb::StateType::eStateConnected) - break; + + //Set 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); + for (auto &handler: on_exit) + handler(-1); + return; } - } } - - // Create environment array - std::vector<const char*> environment; - for(auto &e: environment_from_arguments) - environment.emplace_back(e.c_str()); - environment.emplace_back(nullptr); - - process->RemoteLaunch(argv.data(), environment.data(), nullptr, nullptr, nullptr, nullptr, lldb::eLaunchFlagNone, false, error); - if(!error.Fail()) - process->Continue(); - } - else { - // Create environment array - std::vector<const char*> environment; - for(auto &e: environment_from_arguments) - environment.emplace_back(e.c_str()); - size_t environ_size=0; - while(environ[environ_size]!=nullptr) - ++environ_size; - for(size_t c=0;c<environ_size;++c) - environment.emplace_back(environ[c]); - environment.emplace_back(nullptr); - - process = std::make_unique<lldb::SBProcess>(target.Launch(*listener, argv.data(), environment.data(), nullptr, nullptr, nullptr, path.string().c_str(), lldb::eLaunchFlagNone, false, error)); - } - if(error.Fail()) { - Terminal::get().async_print(std::string("Error (debug): ")+error.GetCString()+'\n', true); - for(auto &handler: on_exit) - handler(-1); - return; - } - if(debug_thread.joinable()) - debug_thread.join(); - for(auto &handler: on_start) - handler(*process); - - for(auto &command: startup_commands) { - lldb::SBCommandReturnObject command_return_object; - debugger->GetCommandInterpreter().HandleCommand(command.c_str(), command_return_object, false); - } - - debug_thread=std::thread([this]() { - lldb::SBEvent event; - while(true) { - std::unique_lock<std::mutex> lock(mutex); - if(listener->GetNextEvent(event)) { - if((event.GetType() & lldb::SBProcess::eBroadcastBitStateChanged)>0) { - auto state=process->GetStateFromEvent(event); - this->state=state; - - if(state==lldb::StateType::eStateStopped) { - for(uint32_t c=0;c<process->GetNumThreads();c++) { - auto thread=process->GetThreadAtIndex(c); - if(thread.GetStopReason()>=2) { - process->SetSelectedThreadByIndexID(thread.GetIndexID()); - break; - } - } - } - - lock.unlock(); - for(auto &handler: on_event) - handler(event); - lock.lock(); - - if(state==lldb::StateType::eStateExited || state==lldb::StateType::eStateCrashed) { - auto exit_status=state==lldb::StateType::eStateCrashed?-1:process->GetExitStatus(); - lock.unlock(); - for(auto &handler: on_exit) - handler(exit_status); - lock.lock(); - process.reset(); - this->state=lldb::StateType::eStateInvalid; + + lldb::SBError error; + if (!remote_host.empty()) { + auto connect_string = "connect://" + remote_host; + process = std::make_unique<lldb::SBProcess>( + target.ConnectRemote(*listener, connect_string.c_str(), "gdb-remote", error)); + if (error.Fail()) { + Terminal::get().async_print(std::string("Error (debug): ") + error.GetCString() + '\n', true); + for (auto &handler: on_exit) + handler(-1); return; - } } - if((event.GetType() & lldb::SBProcess::eBroadcastBitSTDOUT)>0) { - char buffer[buffer_size]; - size_t n; - while((n=process->GetSTDOUT(buffer, buffer_size))!=0) - Terminal::get().async_print(std::string(buffer, n)); - } - //TODO: for some reason stderr is redirected to stdout - if((event.GetType() & lldb::SBProcess::eBroadcastBitSTDERR)>0) { - char buffer[buffer_size]; - size_t n; - while((n=process->GetSTDERR(buffer, buffer_size))!=0) - Terminal::get().async_print(std::string(buffer, n), true); + lldb::SBEvent event; + while (true) { + if (listener->GetNextEvent(event)) { + if ((event.GetType() & lldb::SBProcess::eBroadcastBitStateChanged) > 0) { + auto state = process->GetStateFromEvent(event); + this->state = state; + if (state == lldb::StateType::eStateConnected) + break; + } + } } - } - lock.unlock(); - std::this_thread::sleep_for(std::chrono::milliseconds(200)); + + // Create environment array + std::vector<const char *> environment; + for (auto &e: environment_from_arguments) + environment.emplace_back(e.c_str()); + environment.emplace_back(nullptr); + + process->RemoteLaunch(argv.data(), environment.data(), nullptr, nullptr, nullptr, nullptr, + lldb::eLaunchFlagNone, false, error); + if (!error.Fail()) + process->Continue(); + } else { + // Create environment array + std::vector<const char *> environment; + for (auto &e: environment_from_arguments) + environment.emplace_back(e.c_str()); + size_t environ_size = 0; + while (environ[environ_size] != nullptr) + ++environ_size; + for (size_t c = 0; c < environ_size; ++c) + environment.emplace_back(environ[c]); + environment.emplace_back(nullptr); + + process = std::make_unique<lldb::SBProcess>( + target.Launch(*listener, argv.data(), environment.data(), nullptr, nullptr, nullptr, + path.string().c_str(), lldb::eLaunchFlagNone, false, error)); } - }); + if (error.Fail()) { + Terminal::get().async_print(std::string("Error (debug): ") + error.GetCString() + '\n', true); + for (auto &handler: on_exit) + handler(-1); + return; + } + if (debug_thread.joinable()) + debug_thread.join(); + for (auto &handler: on_start) + handler(*process); + + for (auto &command: startup_commands) { + lldb::SBCommandReturnObject command_return_object; + debugger->GetCommandInterpreter().HandleCommand(command.c_str(), command_return_object, false); + } + + debug_thread = std::thread([this]() { + lldb::SBEvent event; + while (true) { + std::unique_lock<std::mutex> lock(mutex); + if (listener->GetNextEvent(event)) { + if ((event.GetType() & lldb::SBProcess::eBroadcastBitStateChanged) > 0) { + auto state = process->GetStateFromEvent(event); + this->state = state; + + if (state == lldb::StateType::eStateStopped) { + for (uint32_t c = 0; c < process->GetNumThreads(); c++) { + auto thread = process->GetThreadAtIndex(c); + if (thread.GetStopReason() >= 2) { + process->SetSelectedThreadByIndexID(thread.GetIndexID()); + break; + } + } + } + + lock.unlock(); + for (auto &handler: on_event) + handler(event); + lock.lock(); + + if (state == lldb::StateType::eStateExited || state == lldb::StateType::eStateCrashed) { + auto exit_status = state == lldb::StateType::eStateCrashed ? -1 : process->GetExitStatus(); + lock.unlock(); + for (auto &handler: on_exit) + handler(exit_status); + lock.lock(); + process.reset(); + this->state = lldb::StateType::eStateInvalid; + return; + } + } + if ((event.GetType() & lldb::SBProcess::eBroadcastBitSTDOUT) > 0) { + char buffer[buffer_size]; + size_t n; + while ((n = process->GetSTDOUT(buffer, buffer_size)) != 0) + Terminal::get().async_print(std::string(buffer, n)); + } + //TODO: for some reason stderr is redirected to stdout + if ((event.GetType() & lldb::SBProcess::eBroadcastBitSTDERR) > 0) { + char buffer[buffer_size]; + size_t n; + while ((n = process->GetSTDERR(buffer, buffer_size)) != 0) + Terminal::get().async_print(std::string(buffer, n), true); + } + } + lock.unlock(); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + }); } void Debug::LLDB::continue_debug() { - std::unique_lock<std::mutex> lock(mutex); - if(state==lldb::StateType::eStateStopped) - process->Continue(); + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::StateType::eStateStopped) + process->Continue(); } void Debug::LLDB::stop() { - std::unique_lock<std::mutex> lock(mutex); - if(state==lldb::StateType::eStateRunning) { - auto error=process->Stop(); - if(error.Fail()) - Terminal::get().async_print(std::string("Error (debug): ")+error.GetCString()+'\n', true); - } + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::StateType::eStateRunning) { + auto error = process->Stop(); + if (error.Fail()) + Terminal::get().async_print(std::string("Error (debug): ") + error.GetCString() + '\n', true); + } } void Debug::LLDB::kill() { - std::unique_lock<std::mutex> lock(mutex); - if(process) { - auto error=process->Kill(); - if(error.Fail()) - Terminal::get().async_print(std::string("Error (debug): ")+error.GetCString()+'\n', true); - } + std::unique_lock<std::mutex> lock(mutex); + if (process) { + auto error = process->Kill(); + if (error.Fail()) + Terminal::get().async_print(std::string("Error (debug): ") + error.GetCString() + '\n', true); + } } void Debug::LLDB::step_over() { - std::unique_lock<std::mutex> lock(mutex); - if(state==lldb::StateType::eStateStopped) { - process->GetSelectedThread().StepOver(); - } + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::StateType::eStateStopped) { + process->GetSelectedThread().StepOver(); + } } void Debug::LLDB::step_into() { - std::unique_lock<std::mutex> lock(mutex); - if(state==lldb::StateType::eStateStopped) { - process->GetSelectedThread().StepInto(); - } + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::StateType::eStateStopped) { + process->GetSelectedThread().StepInto(); + } } void Debug::LLDB::step_out() { - std::unique_lock<std::mutex> lock(mutex); - if(state==lldb::StateType::eStateStopped) { - process->GetSelectedThread().StepOut(); - } + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::StateType::eStateStopped) { + process->GetSelectedThread().StepOut(); + } } std::pair<std::string, std::string> Debug::LLDB::run_command(const std::string &command) { - std::pair<std::string, std::string> command_return; - std::unique_lock<std::mutex> lock(mutex); - if(state==lldb::StateType::eStateStopped || state==lldb::StateType::eStateRunning) { - lldb::SBCommandReturnObject command_return_object; - debugger->GetCommandInterpreter().HandleCommand(command.c_str(), command_return_object, true); - auto output=command_return_object.GetOutput(); - if(output) - command_return.first=output; - auto error=command_return_object.GetError(); - if(error) - command_return.second=error; - } - return command_return; + std::pair<std::string, std::string> command_return; + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::StateType::eStateStopped || state == lldb::StateType::eStateRunning) { + lldb::SBCommandReturnObject command_return_object; + debugger->GetCommandInterpreter().HandleCommand(command.c_str(), command_return_object, true); + auto output = command_return_object.GetOutput(); + if (output) + command_return.first = output; + auto error = command_return_object.GetError(); + if (error) + command_return.second = error; + } + return command_return; } std::vector<Debug::LLDB::Frame> Debug::LLDB::get_backtrace() { - std::vector<Frame> backtrace; - std::unique_lock<std::mutex> lock(mutex); - if(state==lldb::StateType::eStateStopped) { - auto thread=process->GetSelectedThread(); - for(uint32_t c_f=0;c_f<thread.GetNumFrames();c_f++) { - Frame backtrace_frame; - auto frame=thread.GetFrameAtIndex(c_f); - - backtrace_frame.index=c_f; - - if(frame.GetFunctionName()!=nullptr) - backtrace_frame.function_name=frame.GetFunctionName(); - - auto module_filename=frame.GetModule().GetFileSpec().GetFilename(); - if(module_filename!=nullptr) { - backtrace_frame.module_filename=module_filename; - } - - auto line_entry=frame.GetLineEntry(); - if(line_entry.IsValid()) { - lldb::SBStream stream; - line_entry.GetFileSpec().GetDescription(stream); - auto column=line_entry.GetColumn(); - if(column==0) - column=1; - backtrace_frame.file_path=filesystem::get_normal_path(stream.GetData()); - backtrace_frame.line_nr=line_entry.GetLine(); - backtrace_frame.line_index=column; - } - backtrace.emplace_back(backtrace_frame); + std::vector<Frame> backtrace; + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::StateType::eStateStopped) { + auto thread = process->GetSelectedThread(); + for (uint32_t c_f = 0; c_f < thread.GetNumFrames(); c_f++) { + Frame backtrace_frame; + auto frame = thread.GetFrameAtIndex(c_f); + + backtrace_frame.index = c_f; + + if (frame.GetFunctionName() != nullptr) + backtrace_frame.function_name = frame.GetFunctionName(); + + auto module_filename = frame.GetModule().GetFileSpec().GetFilename(); + if (module_filename != nullptr) { + backtrace_frame.module_filename = module_filename; + } + + auto line_entry = frame.GetLineEntry(); + if (line_entry.IsValid()) { + lldb::SBStream stream; + line_entry.GetFileSpec().GetDescription(stream); + auto column = line_entry.GetColumn(); + if (column == 0) + column = 1; + backtrace_frame.file_path = filesystem::get_normal_path(stream.GetData()); + backtrace_frame.line_nr = line_entry.GetLine(); + backtrace_frame.line_index = column; + } + backtrace.emplace_back(backtrace_frame); + } } - } - return backtrace; + return backtrace; } std::vector<Debug::LLDB::Variable> Debug::LLDB::get_variables() { - std::vector<Debug::LLDB::Variable> variables; - std::unique_lock<std::mutex> lock(mutex); - if(state==lldb::StateType::eStateStopped) { - for(uint32_t c_t=0;c_t<process->GetNumThreads();c_t++) { - auto thread=process->GetThreadAtIndex(c_t); - for(uint32_t c_f=0;c_f<thread.GetNumFrames();c_f++) { - auto frame=thread.GetFrameAtIndex(c_f); - auto values=frame.GetVariables(true, true, true, false); - for(uint32_t value_index=0;value_index<values.GetSize();value_index++) { - lldb::SBStream stream; - auto value=values.GetValueAtIndex(value_index); - - Debug::LLDB::Variable variable; - variable.thread_index_id=thread.GetIndexID(); - variable.frame_index=c_f; - if(value.GetName()!=nullptr) - variable.name=value.GetName(); - value.GetDescription(stream); - variable.value=stream.GetData(); - - auto declaration=value.GetDeclaration(); - if(declaration.IsValid()) { - variable.declaration_found=true; - variable.line_nr=declaration.GetLine(); - variable.line_index=declaration.GetColumn(); - if(variable.line_index==0) - variable.line_index=1; - - auto file_spec=declaration.GetFileSpec(); - variable.file_path=filesystem::get_normal_path(file_spec.GetDirectory()); - variable.file_path/=file_spec.GetFilename(); - } - else { - variable.declaration_found=false; - auto line_entry=frame.GetLineEntry(); - if(line_entry.IsValid()) { - variable.line_nr=line_entry.GetLine(); - variable.line_index=line_entry.GetColumn(); - if(variable.line_index==0) - variable.line_index=1; - - auto file_spec=line_entry.GetFileSpec(); - variable.file_path=filesystem::get_normal_path(file_spec.GetDirectory()); - variable.file_path/=file_spec.GetFilename(); + std::vector<Debug::LLDB::Variable> variables; + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::StateType::eStateStopped) { + for (uint32_t c_t = 0; c_t < process->GetNumThreads(); c_t++) { + auto thread = process->GetThreadAtIndex(c_t); + for (uint32_t c_f = 0; c_f < thread.GetNumFrames(); c_f++) { + auto frame = thread.GetFrameAtIndex(c_f); + auto values = frame.GetVariables(true, true, true, false); + for (uint32_t value_index = 0; value_index < values.GetSize(); value_index++) { + lldb::SBStream stream; + auto value = values.GetValueAtIndex(value_index); + + Debug::LLDB::Variable variable; + variable.thread_index_id = thread.GetIndexID(); + variable.frame_index = c_f; + if (value.GetName() != nullptr) + variable.name = value.GetName(); + value.GetDescription(stream); + variable.value = stream.GetData(); + + auto declaration = value.GetDeclaration(); + if (declaration.IsValid()) { + variable.declaration_found = true; + variable.line_nr = declaration.GetLine(); + variable.line_index = declaration.GetColumn(); + if (variable.line_index == 0) + variable.line_index = 1; + + auto file_spec = declaration.GetFileSpec(); + variable.file_path = filesystem::get_normal_path(file_spec.GetDirectory()); + variable.file_path /= file_spec.GetFilename(); + } else { + variable.declaration_found = false; + auto line_entry = frame.GetLineEntry(); + if (line_entry.IsValid()) { + variable.line_nr = line_entry.GetLine(); + variable.line_index = line_entry.GetColumn(); + if (variable.line_index == 0) + variable.line_index = 1; + + auto file_spec = line_entry.GetFileSpec(); + variable.file_path = filesystem::get_normal_path(file_spec.GetDirectory()); + variable.file_path /= file_spec.GetFilename(); + } + } + variables.emplace_back(variable); + } } - } - variables.emplace_back(variable); } - } } - } - return variables; + return variables; } void Debug::LLDB::select_frame(uint32_t frame_index, uint32_t thread_index_id) { - std::unique_lock<std::mutex> lock(mutex); - if(state==lldb::StateType::eStateStopped) { - if(thread_index_id!=0) - process->SetSelectedThreadByIndexID(thread_index_id); - process->GetSelectedThread().SetSelectedFrame(frame_index);; - } + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::StateType::eStateStopped) { + if (thread_index_id != 0) + process->SetSelectedThreadByIndexID(thread_index_id); + process->GetSelectedThread().SetSelectedFrame(frame_index);; + } } void Debug::LLDB::cancel() { - kill(); - if(debug_thread.joinable()) - debug_thread.join(); + kill(); + if (debug_thread.joinable()) + debug_thread.join(); } -std::string Debug::LLDB::get_value(const std::string &variable, const boost::filesystem::path &file_path, unsigned int line_nr, unsigned int line_index) { - std::string variable_value; - std::unique_lock<std::mutex> lock(mutex); - if(state==lldb::StateType::eStateStopped) { - auto frame=process->GetSelectedThread().GetSelectedFrame(); - - auto values=frame.GetVariables(true, true, true, false); - //First try to find variable based on name, file and line number - if(!file_path.empty()) { - for(uint32_t value_index=0;value_index<values.GetSize();value_index++) { - lldb::SBStream stream; - auto value=values.GetValueAtIndex(value_index); - - if(value.GetName()!=nullptr && value.GetName()==variable) { - auto declaration=value.GetDeclaration(); - if(declaration.IsValid()) { - if(declaration.GetLine()==line_nr && (declaration.GetColumn()==0 || declaration.GetColumn()==line_index)) { - auto file_spec=declaration.GetFileSpec(); - auto value_decl_path=filesystem::get_normal_path(file_spec.GetDirectory()); - value_decl_path/=file_spec.GetFilename(); - if(value_decl_path==file_path) { +std::string +Debug::LLDB::get_value(const std::string &variable, const boost::filesystem::path &file_path, unsigned int line_nr, + unsigned int line_index) { + std::string variable_value; + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::StateType::eStateStopped) { + auto frame = process->GetSelectedThread().GetSelectedFrame(); + + auto values = frame.GetVariables(true, true, true, false); + //First try to find variable based on name, file and line number + if (!file_path.empty()) { + for (uint32_t value_index = 0; value_index < values.GetSize(); value_index++) { + lldb::SBStream stream; + auto value = values.GetValueAtIndex(value_index); + + if (value.GetName() != nullptr && value.GetName() == variable) { + auto declaration = value.GetDeclaration(); + if (declaration.IsValid()) { + if (declaration.GetLine() == line_nr && + (declaration.GetColumn() == 0 || declaration.GetColumn() == line_index)) { + auto file_spec = declaration.GetFileSpec(); + auto value_decl_path = filesystem::get_normal_path(file_spec.GetDirectory()); + value_decl_path /= file_spec.GetFilename(); + if (value_decl_path == file_path) { + value.GetDescription(stream); + variable_value = stream.GetData(); + break; + } + } + } + } + } + } + if (variable_value.empty()) { + //In case a variable is missing file and line number, only do check on name + auto value = frame.GetValueForVariablePath(variable.c_str()); + if (value.IsValid()) { + lldb::SBStream stream; value.GetDescription(stream); - variable_value=stream.GetData(); - break; - } + variable_value = stream.GetData(); } - } } - } - } - if(variable_value.empty()) { - //In case a variable is missing file and line number, only do check on name - auto value=frame.GetValueForVariablePath(variable.c_str()); - if(value.IsValid()) { - lldb::SBStream stream; - value.GetDescription(stream); - variable_value=stream.GetData(); - } } - } - return variable_value; + return variable_value; } -std::string Debug::LLDB::get_return_value(const boost::filesystem::path &file_path, unsigned int line_nr, unsigned int line_index) { - std::string return_value; - std::unique_lock<std::mutex> lock(mutex); - if(state==lldb::StateType::eStateStopped) { - auto thread=process->GetSelectedThread(); - auto thread_return_value=thread.GetStopReturnValue(); - if(thread_return_value.IsValid()) { - auto line_entry=thread.GetSelectedFrame().GetLineEntry(); - if(line_entry.IsValid()) { - lldb::SBStream stream; - line_entry.GetFileSpec().GetDescription(stream); - if(filesystem::get_normal_path(stream.GetData())==file_path && line_entry.GetLine()==line_nr && - (line_entry.GetColumn()==0 || line_entry.GetColumn()==line_index)) { - lldb::SBStream stream; - thread_return_value.GetDescription(stream); - return_value=stream.GetData(); +std::string +Debug::LLDB::get_return_value(const boost::filesystem::path &file_path, unsigned int line_nr, unsigned int line_index) { + std::string return_value; + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::StateType::eStateStopped) { + auto thread = process->GetSelectedThread(); + auto thread_return_value = thread.GetStopReturnValue(); + if (thread_return_value.IsValid()) { + auto line_entry = thread.GetSelectedFrame().GetLineEntry(); + if (line_entry.IsValid()) { + lldb::SBStream stream; + line_entry.GetFileSpec().GetDescription(stream); + if (filesystem::get_normal_path(stream.GetData()) == file_path && line_entry.GetLine() == line_nr && + (line_entry.GetColumn() == 0 || line_entry.GetColumn() == line_index)) { + lldb::SBStream stream; + thread_return_value.GetDescription(stream); + return_value = stream.GetData(); + } + } } - } } - } - return return_value; + return return_value; } bool Debug::LLDB::is_invalid() { - std::unique_lock<std::mutex> lock(mutex); - return state==lldb::StateType::eStateInvalid; + std::unique_lock<std::mutex> lock(mutex); + return state == lldb::StateType::eStateInvalid; } bool Debug::LLDB::is_stopped() { - std::unique_lock<std::mutex> lock(mutex); - return state==lldb::StateType::eStateStopped; + std::unique_lock<std::mutex> lock(mutex); + return state == lldb::StateType::eStateStopped; } bool Debug::LLDB::is_running() { - std::unique_lock<std::mutex> lock(mutex); - return state==lldb::StateType::eStateRunning; + std::unique_lock<std::mutex> lock(mutex); + return state == lldb::StateType::eStateRunning; } void Debug::LLDB::add_breakpoint(const boost::filesystem::path &file_path, int line_nr) { - std::unique_lock<std::mutex> lock(mutex); - if(state==lldb::eStateStopped || state==lldb::eStateRunning) { - if(!(process->GetTarget().BreakpointCreateByLocation(file_path.string().c_str(), line_nr)).IsValid()) - Terminal::get().async_print("Error (debug): Could not create breakpoint at: "+file_path.string()+":"+std::to_string(line_nr)+'\n', true); - } + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::eStateStopped || state == lldb::eStateRunning) { + if (!(process->GetTarget().BreakpointCreateByLocation(file_path.string().c_str(), line_nr)).IsValid()) + Terminal::get().async_print("Error (debug): Could not create breakpoint at: " + file_path.string() + ":" + + std::to_string(line_nr) + '\n', true); + } } void Debug::LLDB::remove_breakpoint(const boost::filesystem::path &file_path, int line_nr, int line_count) { - std::unique_lock<std::mutex> lock(mutex); - if(state==lldb::eStateStopped || state==lldb::eStateRunning) { - auto target=process->GetTarget(); - for(int line_nr_try=line_nr;line_nr_try<line_count;line_nr_try++) { - for(uint32_t b_index=0;b_index<target.GetNumBreakpoints();b_index++) { - auto breakpoint=target.GetBreakpointAtIndex(b_index); - for(uint32_t l_index=0;l_index<breakpoint.GetNumLocations();l_index++) { - auto line_entry=breakpoint.GetLocationAtIndex(l_index).GetAddress().GetLineEntry(); - if(line_entry.GetLine()==static_cast<uint32_t>(line_nr_try)) { - auto file_spec=line_entry.GetFileSpec(); - auto breakpoint_path=filesystem::get_normal_path(file_spec.GetDirectory()); - breakpoint_path/=file_spec.GetFilename(); - if(breakpoint_path==file_path) { - if(!target.BreakpointDelete(breakpoint.GetID())) - Terminal::get().async_print("Error (debug): Could not delete breakpoint at: "+file_path.string()+":"+std::to_string(line_nr)+'\n', true); - return; + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::eStateStopped || state == lldb::eStateRunning) { + auto target = process->GetTarget(); + for (int line_nr_try = line_nr; line_nr_try < line_count; line_nr_try++) { + for (uint32_t b_index = 0; b_index < target.GetNumBreakpoints(); b_index++) { + auto breakpoint = target.GetBreakpointAtIndex(b_index); + for (uint32_t l_index = 0; l_index < breakpoint.GetNumLocations(); l_index++) { + auto line_entry = breakpoint.GetLocationAtIndex(l_index).GetAddress().GetLineEntry(); + if (line_entry.GetLine() == static_cast<uint32_t>(line_nr_try)) { + auto file_spec = line_entry.GetFileSpec(); + auto breakpoint_path = filesystem::get_normal_path(file_spec.GetDirectory()); + breakpoint_path /= file_spec.GetFilename(); + if (breakpoint_path == file_path) { + if (!target.BreakpointDelete(breakpoint.GetID())) + Terminal::get().async_print( + "Error (debug): Could not delete breakpoint at: " + file_path.string() + ":" + + std::to_string(line_nr) + '\n', true); + return; + } + } + } } - } } - } } - } } void Debug::LLDB::write(const std::string &buffer) { - std::unique_lock<std::mutex> lock(mutex); - if(state==lldb::StateType::eStateRunning) { - process->PutSTDIN(buffer.c_str(), buffer.size()); - } + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::StateType::eStateRunning) { + process->PutSTDIN(buffer.c_str(), buffer.size()); + } } diff --git a/src/debug_lldb.h b/src/debug_lldb.h index 42560c3f..1b948a7b 100644 --- a/src/debug_lldb.h +++ b/src/debug_lldb.h @@ -1,4 +1,5 @@ #pragma once + #include <boost/filesystem.hpp> #include <list> #include <lldb/API/LLDB.h> @@ -7,82 +8,102 @@ #include <tuple> namespace Debug { - class LLDB { - public: - class Frame { + class LLDB { public: - uint32_t index; - std::string module_filename; - boost::filesystem::path file_path; - std::string function_name; - int line_nr; - int line_index; - }; - class Variable { + class Frame { + public: + uint32_t index; + std::string module_filename; + boost::filesystem::path 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; + bool declaration_found; + boost::filesystem::path file_path; + int line_nr; + int line_index; + }; + + private: + LLDB(); + public: - uint32_t thread_index_id; - uint32_t frame_index; - std::string name; - std::string value; - bool declaration_found; - boost::filesystem::path file_path; - int line_nr; - int line_index; + static LLDB &get() { + static LLDB singleton; + return singleton; + } + + std::list<std::function<void(const lldb::SBProcess &)>> on_start; + /// The handlers are not run in the main loop. + std::list<std::function<void(int exit_status)>> on_exit; + /// The handlers are not run in the main loop. + std::list<std::function<void(const lldb::SBEvent &)>> on_event; + + std::mutex mutex; + + void start(const std::string &command, const boost::filesystem::path &path = "", + const std::vector<std::pair<boost::filesystem::path, int> > &breakpoints = {}, + const std::vector<std::string> &startup_commands = {}, const std::string &remote_host = ""); + + 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 cancel(); + + 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::tuple<std::vector<std::string>, std::string, std::vector<std::string>> + parse_run_arguments(const std::string &command); + + 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; + + size_t buffer_size; }; - private: - LLDB(); - public: - static LLDB &get() { - static LLDB singleton; - return singleton; - } - - std::list<std::function<void(const lldb::SBProcess &)>> on_start; - /// The handlers are not run in the main loop. - std::list<std::function<void(int exit_status)>> on_exit; - /// The handlers are not run in the main loop. - std::list<std::function<void(const lldb::SBEvent &)>> on_event; - - std::mutex mutex; - - void start(const std::string &command, const boost::filesystem::path &path="", - const std::vector<std::pair<boost::filesystem::path, int> > &breakpoints={}, - const std::vector<std::string> &startup_commands={}, const std::string &remote_host=""); - 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 cancel(); - - 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::tuple<std::vector<std::string>, std::string, std::vector<std::string>> parse_run_arguments(const std::string &command); - - 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; - - size_t buffer_size; - }; } diff --git a/src/dialogs.cc b/src/dialogs.cc index ab5bc550..f4194848 100644 --- a/src/dialogs.cc +++ b/src/dialogs.cc @@ -1,79 +1,80 @@ #include "dialogs.h" #include <cmath> -Dialog::Message::Message(const std::string &text): Gtk::Window(Gtk::WindowType::WINDOW_POPUP) { - auto g_application=g_application_get_default(); - auto gio_application=Glib::wrap(g_application, true); - auto application=Glib::RefPtr<Gtk::Application>::cast_static(gio_application); - set_transient_for(*application->get_active_window()); - - set_position(Gtk::WindowPosition::WIN_POS_CENTER_ON_PARENT); - set_modal(true); - set_type_hint(Gdk::WindowTypeHint::WINDOW_TYPE_HINT_NOTIFICATION); - property_decorated()=false; - set_skip_taskbar_hint(true); - - auto box=Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL)); - auto label=Gtk::manage(new Gtk::Label(text)); - label->set_padding(10, 10); - box->pack_start(*label); - add(*box); - - show_all_children(); - show_now(); - - while(Gtk::Main::events_pending()) - Gtk::Main::iteration(false); +Dialog::Message::Message(const std::string &text) : Gtk::Window(Gtk::WindowType::WINDOW_POPUP) { + auto g_application = g_application_get_default(); + auto gio_application = Glib::wrap(g_application, true); + auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application); + set_transient_for(*application->get_active_window()); + + set_position(Gtk::WindowPosition::WIN_POS_CENTER_ON_PARENT); + set_modal(true); + set_type_hint(Gdk::WindowTypeHint::WINDOW_TYPE_HINT_NOTIFICATION); + property_decorated() = false; + set_skip_taskbar_hint(true); + + auto box = Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL)); + auto label = Gtk::manage(new Gtk::Label(text)); + label->set_padding(10, 10); + box->pack_start(*label); + add(*box); + + show_all_children(); + show_now(); + + while (Gtk::Main::events_pending()) + Gtk::Main::iteration(false); } bool Dialog::Message::on_delete_event(GdkEventAny *event) { - return true; + return true; } std::string Dialog::gtk_dialog(const boost::filesystem::path &path, const std::string &title, - const std::vector<std::pair<std::string, Gtk::ResponseType>> &buttons, - Gtk::FileChooserAction action) { - // Workaround for crash on MacOS when filtering files in file/folder dialogs. - // See also https://github.com/cppit/jucipp/issues/259. - // TODO 2018: check if this bug has been fixed + const std::vector<std::pair<std::string, Gtk::ResponseType>> &buttons, + Gtk::FileChooserAction action) { + // Workaround for crash on MacOS when filtering files in file/folder dialogs. + // See also https://github.com/cppit/jucipp/issues/259. + // TODO 2018: check if this bug has been fixed #ifdef __APPLE__ - class FileChooserDialog : public Gtk::FileChooserDialog { - Gtk::FileChooserAction action; - public: - FileChooserDialog(const Glib::ustring& title, Gtk::FileChooserAction action) : Gtk::FileChooserDialog(title, action), action(action) {} - protected: - bool on_key_press_event(GdkEventKey *key_event) override { - if(action==Gtk::FileChooserAction::FILE_CHOOSER_ACTION_OPEN || action==Gtk::FileChooserAction::FILE_CHOOSER_ACTION_SELECT_FOLDER) { - auto unicode=gdk_keyval_to_unicode(key_event->keyval); - if(unicode>31 && unicode!=127) - return true; + class FileChooserDialog : public Gtk::FileChooserDialog { + Gtk::FileChooserAction action; + public: + FileChooserDialog(const Glib::ustring& title, Gtk::FileChooserAction action) : Gtk::FileChooserDialog(title, action), action(action) {} + protected: + bool on_key_press_event(GdkEventKey *key_event) override { + if(action==Gtk::FileChooserAction::FILE_CHOOSER_ACTION_OPEN || action==Gtk::FileChooserAction::FILE_CHOOSER_ACTION_SELECT_FOLDER) { + auto unicode=gdk_keyval_to_unicode(key_event->keyval); + if(unicode>31 && unicode!=127) + return true; + } + return Gtk::FileChooserDialog::on_key_press_event(key_event); } - return Gtk::FileChooserDialog::on_key_press_event(key_event); - } - }; - FileChooserDialog dialog(title, action); + }; + FileChooserDialog dialog(title, action); #else - Gtk::FileChooserDialog dialog(title, action); + Gtk::FileChooserDialog dialog(title, action); #endif - - auto g_application=g_application_get_default(); - auto gio_application=Glib::wrap(g_application, true); - auto application=Glib::RefPtr<Gtk::Application>::cast_static(gio_application); - dialog.set_transient_for(*application->get_active_window()); - dialog.set_position(Gtk::WindowPosition::WIN_POS_CENTER_ON_PARENT); - - if(title=="Save File As") - gtk_file_chooser_set_filename(reinterpret_cast<GtkFileChooser*>(dialog.gobj()), path.string().c_str()); - else if(!path.empty()) - gtk_file_chooser_set_current_folder(reinterpret_cast<GtkFileChooser*>(dialog.gobj()), path.string().c_str()); - else { - boost::system::error_code ec; - auto current_path=boost::filesystem::current_path(ec); - if(!ec) - gtk_file_chooser_set_current_folder(reinterpret_cast<GtkFileChooser*>(dialog.gobj()), current_path.string().c_str()); - } - - for (auto &button : buttons) - dialog.add_button(button.first, button.second); - return dialog.run() == Gtk::RESPONSE_OK ? dialog.get_filename() : ""; + + auto g_application = g_application_get_default(); + auto gio_application = Glib::wrap(g_application, true); + auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application); + dialog.set_transient_for(*application->get_active_window()); + dialog.set_position(Gtk::WindowPosition::WIN_POS_CENTER_ON_PARENT); + + if (title == "Save File As") + gtk_file_chooser_set_filename(reinterpret_cast<GtkFileChooser *>(dialog.gobj()), path.string().c_str()); + else if (!path.empty()) + gtk_file_chooser_set_current_folder(reinterpret_cast<GtkFileChooser *>(dialog.gobj()), path.string().c_str()); + else { + boost::system::error_code ec; + auto current_path = boost::filesystem::current_path(ec); + if (!ec) + gtk_file_chooser_set_current_folder(reinterpret_cast<GtkFileChooser *>(dialog.gobj()), + current_path.string().c_str()); + } + + for (auto &button : buttons) + dialog.add_button(button.first, button.second); + return dialog.run() == Gtk::RESPONSE_OK ? dialog.get_filename() : ""; } diff --git a/src/dialogs.h b/src/dialogs.h index e1deeb53..b3e57730 100644 --- a/src/dialogs.h +++ b/src/dialogs.h @@ -1,4 +1,5 @@ #pragma once + #include <string> #include <boost/filesystem.hpp> #include <vector> @@ -6,21 +7,26 @@ class Dialog { public: - static std::string open_folder(const boost::filesystem::path &path); - static std::string open_file(const boost::filesystem::path &path); - static std::string new_file(const boost::filesystem::path &path); - static std::string new_folder(const boost::filesystem::path &path); - static std::string save_file_as(const boost::filesystem::path &path); - - class Message : public Gtk::Window { - public: - Message(const std::string &text); - protected: - bool on_delete_event(GdkEventAny *event) override; - }; - + static std::string open_folder(const boost::filesystem::path &path); + + static std::string open_file(const boost::filesystem::path &path); + + static std::string new_file(const boost::filesystem::path &path); + + static std::string new_folder(const boost::filesystem::path &path); + + static std::string save_file_as(const boost::filesystem::path &path); + + class Message : public Gtk::Window { + public: + Message(const std::string &text); + + protected: + bool on_delete_event(GdkEventAny *event) override; + }; + private: - static std::string gtk_dialog(const boost::filesystem::path &path, const std::string &title, - const std::vector<std::pair<std::string, Gtk::ResponseType>> &buttons, - Gtk::FileChooserAction gtk_options); + static std::string gtk_dialog(const boost::filesystem::path &path, const std::string &title, + const std::vector<std::pair<std::string, Gtk::ResponseType>> &buttons, + Gtk::FileChooserAction gtk_options); }; diff --git a/src/dialogs_unix.cc b/src/dialogs_unix.cc index 2512ce8a..865bf3d9 100644 --- a/src/dialogs_unix.cc +++ b/src/dialogs_unix.cc @@ -1,32 +1,32 @@ #include "dialogs.h" std::string Dialog::open_folder(const boost::filesystem::path &path) { - return gtk_dialog(path, "Open Folder", - {std::make_pair("Cancel", Gtk::RESPONSE_CANCEL),std::make_pair("Open", Gtk::RESPONSE_OK)}, - Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER); + return gtk_dialog(path, "Open Folder", + {std::make_pair("Cancel", Gtk::RESPONSE_CANCEL), std::make_pair("Open", Gtk::RESPONSE_OK)}, + Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER); } std::string Dialog::new_file(const boost::filesystem::path &path) { - return gtk_dialog(path, "New File", - {std::make_pair("Cancel", Gtk::RESPONSE_CANCEL), std::make_pair("Save", Gtk::RESPONSE_OK)}, - Gtk::FILE_CHOOSER_ACTION_SAVE); + return gtk_dialog(path, "New File", + {std::make_pair("Cancel", Gtk::RESPONSE_CANCEL), std::make_pair("Save", Gtk::RESPONSE_OK)}, + Gtk::FILE_CHOOSER_ACTION_SAVE); } std::string Dialog::new_folder(const boost::filesystem::path &path) { - return gtk_dialog(path, "New Folder", - {std::make_pair("Cancel", Gtk::RESPONSE_CANCEL),std::make_pair("Create", Gtk::RESPONSE_OK)}, - Gtk::FILE_CHOOSER_ACTION_CREATE_FOLDER); + return gtk_dialog(path, "New Folder", + {std::make_pair("Cancel", Gtk::RESPONSE_CANCEL), std::make_pair("Create", Gtk::RESPONSE_OK)}, + Gtk::FILE_CHOOSER_ACTION_CREATE_FOLDER); } std::string Dialog::open_file(const boost::filesystem::path &path) { - return gtk_dialog(path, "Open File", - {std::make_pair("Cancel", Gtk::RESPONSE_CANCEL),std::make_pair("Select", Gtk::RESPONSE_OK)}, - Gtk::FILE_CHOOSER_ACTION_OPEN); + return gtk_dialog(path, "Open File", + {std::make_pair("Cancel", Gtk::RESPONSE_CANCEL), std::make_pair("Select", Gtk::RESPONSE_OK)}, + Gtk::FILE_CHOOSER_ACTION_OPEN); } std::string Dialog::save_file_as(const boost::filesystem::path &path) { - return gtk_dialog(path, "Save File As", - {std::make_pair("Cancel", Gtk::RESPONSE_CANCEL),std::make_pair("Save", Gtk::RESPONSE_OK)}, - Gtk::FILE_CHOOSER_ACTION_SAVE); + return gtk_dialog(path, "Save File As", + {std::make_pair("Cancel", Gtk::RESPONSE_CANCEL), std::make_pair("Save", Gtk::RESPONSE_OK)}, + Gtk::FILE_CHOOSER_ACTION_SAVE); } diff --git a/src/dialogs_win.cc b/src/dialogs_win.cc index 72d18691..6322a9ae 100644 --- a/src/dialogs_win.cc +++ b/src/dialogs_win.cc @@ -12,152 +12,154 @@ #include <vector> #include <iostream> //TODO: remove + using namespace std; //TODO: remove class Win32Dialog { public: - Win32Dialog() {}; - - ~Win32Dialog() { - if(dialog!=nullptr) - dialog->Release(); - } - - /** Returns the selected item's path as a string */ - std::string open(const std::wstring &title, unsigned option=0) { - if(!init(CLSID_FileOpenDialog)) - return ""; - - if(!set_title(title) || !add_option(option)) - return ""; - if(!set_folder()) - return ""; - - return show(); - } - - std::string save(const std::wstring &title, const boost::filesystem::path &file_path="", unsigned option=0) { - if(!init(CLSID_FileSaveDialog)) - return ""; - - if(!set_title(title) || !add_option(option)) - return ""; - if(!set_folder()) - return ""; - std::vector<COMDLG_FILTERSPEC> extensions; - if(!file_path.empty()) { - if(file_path.has_extension() && file_path.filename()!=file_path.extension()) { - auto extension=(L"*"+file_path.extension().native()).c_str(); - extensions.emplace_back(COMDLG_FILTERSPEC{extension, extension}); - if(!set_default_file_extension(extension)) - return ""; - } + Win32Dialog() {}; + + ~Win32Dialog() { + if (dialog != nullptr) + dialog->Release(); + } + + /** Returns the selected item's path as a string */ + std::string open(const std::wstring &title, unsigned option = 0) { + if (!init(CLSID_FileOpenDialog)) + return ""; + + if (!set_title(title) || !add_option(option)) + return ""; + if (!set_folder()) + return ""; + + return show(); + } + + std::string save(const std::wstring &title, const boost::filesystem::path &file_path = "", unsigned option = 0) { + if (!init(CLSID_FileSaveDialog)) + return ""; + + if (!set_title(title) || !add_option(option)) + return ""; + if (!set_folder()) + return ""; + std::vector<COMDLG_FILTERSPEC> extensions; + if (!file_path.empty()) { + if (file_path.has_extension() && file_path.filename() != file_path.extension()) { + auto extension = (L"*" + file_path.extension().native()).c_str(); + extensions.emplace_back(COMDLG_FILTERSPEC{extension, extension}); + if (!set_default_file_extension(extension)) + return ""; + } + } + extensions.emplace_back(COMDLG_FILTERSPEC{L"All files", L"*.*"}); + if (dialog->SetFileTypes(extensions.size(), extensions.data()) != S_OK) + return ""; + + return show(); } - extensions.emplace_back(COMDLG_FILTERSPEC{L"All files", L"*.*"}); - if(dialog->SetFileTypes(extensions.size(), extensions.data())!=S_OK) - return ""; - - return show(); - } private: - IFileDialog *dialog=nullptr; - DWORD options; - - bool init(CLSID type) { - if(CoCreateInstance(type, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&dialog))!=S_OK) - return false; - if(dialog->GetOptions(&options)!=S_OK) - return false; - return true; - } - - /** available options are listed at https://msdn.microsoft.com/en-gb/library/windows/desktop/dn457282(v=vs.85).aspx */ - bool add_option(unsigned option) { - if(dialog->SetOptions(options | option)!=S_OK) - return false; - return true; - } - - bool set_title(const std::wstring &title) { - if(dialog->SetTitle(title.c_str())!=S_OK) - return false; - return true; - } - - /** Sets the extensions the browser can find */ - bool set_default_file_extension(const std::wstring &file_extension) { - if(dialog->SetDefaultExtension(file_extension.c_str())!=S_OK) - return false; - return true; - } - - /** Sets the directory to start browsing */ - bool set_folder() { - auto g_application=g_application_get_default(); //TODO: Post issue that Gio::Application::get_default should return pointer and not Glib::RefPtr - auto gio_application=Glib::wrap(g_application, true); - auto application=Glib::RefPtr<Application>::cast_static(gio_application); - - auto current_path=application->window->notebook.get_current_folder(); - boost::system::error_code ec; - if(current_path.empty()) - current_path=boost::filesystem::current_path(ec); - if(ec) - return false; - - std::wstring path=current_path.native(); - size_t pos=0; - while((pos=path.find(L'/', pos))!=std::wstring::npos) {//TODO: issue bug report on boost::filesystem::path::native on MSYS2 - path.replace(pos, 1, L"\\"); - pos++; + IFileDialog *dialog = nullptr; + DWORD options; + + bool init(CLSID type) { + if (CoCreateInstance(type, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&dialog)) != S_OK) + return false; + if (dialog->GetOptions(&options) != S_OK) + return false; + return true; + } + + /** available options are listed at https://msdn.microsoft.com/en-gb/library/windows/desktop/dn457282(v=vs.85).aspx */ + bool add_option(unsigned option) { + if (dialog->SetOptions(options | option) != S_OK) + return false; + return true; + } + + bool set_title(const std::wstring &title) { + if (dialog->SetTitle(title.c_str()) != S_OK) + return false; + return true; + } + + /** Sets the extensions the browser can find */ + bool set_default_file_extension(const std::wstring &file_extension) { + if (dialog->SetDefaultExtension(file_extension.c_str()) != S_OK) + return false; + return true; + } + + /** Sets the directory to start browsing */ + bool set_folder() { + auto g_application = g_application_get_default(); //TODO: Post issue that Gio::Application::get_default should return pointer and not Glib::RefPtr + auto gio_application = Glib::wrap(g_application, true); + auto application = Glib::RefPtr<Application>::cast_static(gio_application); + + auto current_path = application->window->notebook.get_current_folder(); + boost::system::error_code ec; + if (current_path.empty()) + current_path = boost::filesystem::current_path(ec); + if (ec) + return false; + + std::wstring path = current_path.native(); + size_t pos = 0; + while ((pos = path.find(L'/', pos)) != + std::wstring::npos) {//TODO: issue bug report on boost::filesystem::path::native on MSYS2 + path.replace(pos, 1, L"\\"); + pos++; + } + + IShellItem *folder = nullptr; + if (SHCreateItemFromParsingName(path.c_str(), nullptr, IID_PPV_ARGS(&folder)) != S_OK) + return false; + if (dialog->SetFolder(folder) != S_OK) + return false; + folder->Release(); + return true; + } + + std::string show() { + if (dialog->Show(nullptr) != S_OK) + return ""; + IShellItem *result = nullptr; + if (dialog->GetResult(&result) != S_OK) + return ""; + LPWSTR file_path = nullptr; + auto hresult = result->GetDisplayName(SIGDN_FILESYSPATH, &file_path); + result->Release(); + if (hresult != S_OK) + return ""; + std::wstring file_path_wstring(file_path); + std::string file_path_string(file_path_wstring.begin(), file_path_wstring.end()); + CoTaskMemFree(file_path); + return file_path_string; } - - IShellItem *folder = nullptr; - if(SHCreateItemFromParsingName(path.c_str(), nullptr, IID_PPV_ARGS(&folder))!=S_OK) - return false; - if(dialog->SetFolder(folder)!=S_OK) - return false; - folder->Release(); - return true; - } - - std::string show() { - if(dialog->Show(nullptr)!=S_OK) - return ""; - IShellItem *result = nullptr; - if(dialog->GetResult(&result)!=S_OK) - return ""; - LPWSTR file_path = nullptr; - auto hresult=result->GetDisplayName(SIGDN_FILESYSPATH, &file_path); - result->Release(); - if(hresult!=S_OK) - return ""; - std::wstring file_path_wstring(file_path); - std::string file_path_string(file_path_wstring.begin(), file_path_wstring.end()); - CoTaskMemFree(file_path); - return file_path_string; - } }; std::string Dialog::open_folder() { - return Win32Dialog().open(L"Open Folder", FOS_PICKFOLDERS); + return Win32Dialog().open(L"Open Folder", FOS_PICKFOLDERS); } std::string Dialog::new_file() { - return Win32Dialog().save(L"New File"); + return Win32Dialog().save(L"New File"); } std::string Dialog::new_folder() { - //Win32 (IFileDialog) does not support create folder... - return gtk_dialog("New Folder", - {std::make_pair("Cancel", Gtk::RESPONSE_CANCEL),std::make_pair("Create", Gtk::RESPONSE_OK)}, - Gtk::FILE_CHOOSER_ACTION_CREATE_FOLDER); + //Win32 (IFileDialog) does not support create folder... + return gtk_dialog("New Folder", + {std::make_pair("Cancel", Gtk::RESPONSE_CANCEL), std::make_pair("Create", Gtk::RESPONSE_OK)}, + Gtk::FILE_CHOOSER_ACTION_CREATE_FOLDER); } std::string Dialog::open_file() { - return Win32Dialog().open(L"Open File"); + return Win32Dialog().open(L"Open File"); } std::string Dialog::save_file_as(const boost::filesystem::path &file_path) { - return Win32Dialog().save(L"Save File As", file_path); + return Win32Dialog().save(L"Save File As", file_path); } diff --git a/src/directories.cc b/src/directories.cc index 0ceb032c..299f379c 100644 --- a/src/directories.cc +++ b/src/directories.cc @@ -6,717 +6,726 @@ #include "filesystem.h" #include "entrybox.h" -bool Directories::TreeStore::row_drop_possible_vfunc(const Gtk::TreeModel::Path &path, const Gtk::SelectionData &selection_data) const { - return true; +bool Directories::TreeStore::row_drop_possible_vfunc(const Gtk::TreeModel::Path &path, + const Gtk::SelectionData &selection_data) const { + return true; } -bool Directories::TreeStore::drag_data_received_vfunc(const TreeModel::Path &path, const Gtk::SelectionData &selection_data) { - auto &directories=Directories::get(); - - auto get_target_folder=[this, &directories](const TreeModel::Path &path) { - if(path.size()==1) - return directories.path; - else { - auto it=get_iter(path); - if(it) { - auto prev_path=path; - prev_path.up(); - it=get_iter(prev_path); - if(it) - return it->get_value(directories.column_record.path); - } - else { - auto prev_path=path; - prev_path.up(); - if(prev_path.size()==1) - return directories.path; +bool Directories::TreeStore::drag_data_received_vfunc(const TreeModel::Path &path, + const Gtk::SelectionData &selection_data) { + auto &directories = Directories::get(); + + auto get_target_folder = [this, &directories](const TreeModel::Path &path) { + if (path.size() == 1) + return directories.path; else { - prev_path.up(); - it=get_iter(prev_path); - if(it) - return it->get_value(directories.column_record.path); + auto it = get_iter(path); + if (it) { + auto prev_path = path; + prev_path.up(); + it = get_iter(prev_path); + if (it) + return it->get_value(directories.column_record.path); + } else { + auto prev_path = path; + prev_path.up(); + if (prev_path.size() == 1) + return directories.path; + else { + prev_path.up(); + it = get_iter(prev_path); + if (it) + return it->get_value(directories.column_record.path); + } + } } - } - } - return boost::filesystem::path(); - }; - - auto it=directories.get_selection()->get_selected(); - if(it) { - auto source_path=it->get_value(directories.column_record.path); - if(source_path.empty()) - return false; - - auto target_path=get_target_folder(path); - target_path/=source_path.filename(); - - if(source_path==target_path) - return false; - - if(boost::filesystem::exists(target_path)) { - Terminal::get().print("Error: could not move file: "+target_path.string()+" already exists\n", true); - return false; - } - - bool is_directory=boost::filesystem::is_directory(source_path); - - if(is_directory) - Directories::get().remove_path(source_path); - - boost::system::error_code ec; - boost::filesystem::rename(source_path, target_path, ec); - if(ec) { - Terminal::get().print("Error: could not move file: "+ec.message()+'\n', true); - return false; - } - - for(size_t c=0;c<Notebook::get().size();c++) { - auto view=Notebook::get().get_view(c); - if(is_directory) { - if(filesystem::file_in_path(view->file_path, source_path)) { - auto file_it=view->file_path.begin(); - for(auto source_it=source_path.begin();source_it!=source_path.end();source_it++) - file_it++; - auto new_file_path=target_path; - for(;file_it!=view->file_path.end();file_it++) - new_file_path/=*file_it; - view->rename(new_file_path); + return boost::filesystem::path(); + }; + + auto it = directories.get_selection()->get_selected(); + if (it) { + auto source_path = it->get_value(directories.column_record.path); + if (source_path.empty()) + return false; + + auto target_path = get_target_folder(path); + target_path /= source_path.filename(); + + if (source_path == target_path) + return false; + + if (boost::filesystem::exists(target_path)) { + Terminal::get().print("Error: could not move file: " + target_path.string() + " already exists\n", true); + return false; + } + + bool is_directory = boost::filesystem::is_directory(source_path); + + if (is_directory) + Directories::get().remove_path(source_path); + + boost::system::error_code ec; + boost::filesystem::rename(source_path, target_path, ec); + if (ec) { + Terminal::get().print("Error: could not move file: " + ec.message() + '\n', true); + return false; + } + + for (size_t c = 0; c < Notebook::get().size(); c++) { + auto view = Notebook::get().get_view(c); + if (is_directory) { + if (filesystem::file_in_path(view->file_path, source_path)) { + auto file_it = view->file_path.begin(); + for (auto source_it = source_path.begin(); source_it != source_path.end(); source_it++) + file_it++; + auto new_file_path = target_path; + for (; file_it != view->file_path.end(); file_it++) + new_file_path /= *file_it; + view->rename(new_file_path); + } + } else if (view->file_path == source_path) { + view->rename(target_path); + break; + } } - } - else if(view->file_path==source_path) { - view->rename(target_path); - break; - } + + Directories::get().update(); + Directories::get().on_save_file(target_path); + directories.select(target_path); } - - Directories::get().update(); - Directories::get().on_save_file(target_path); - directories.select(target_path); - } - - EntryBox::get().hide(); - return false; + + EntryBox::get().hide(); + return false; } -bool Directories::TreeStore::drag_data_delete_vfunc (const Gtk::TreeModel::Path &path) { - return false; +bool Directories::TreeStore::drag_data_delete_vfunc(const Gtk::TreeModel::Path &path) { + return false; } Directories::Directories() : Gtk::ListViewText(1) { - set_enable_tree_lines(true); - - tree_store = TreeStore::create(); - tree_store->set_column_types(column_record); - set_model(tree_store); - - get_column(0)->set_title(""); - - auto renderer=dynamic_cast<Gtk::CellRendererText*>(get_column(0)->get_first_cell()); - get_column(0)->set_cell_data_func(*renderer, [this] (Gtk::CellRenderer *renderer, const Gtk::TreeModel::iterator &iter) { - if(auto renderer_text=dynamic_cast<Gtk::CellRendererText*>(renderer)) - renderer_text->property_markup()=iter->get_value(column_record.markup); - }); - - get_style_context()->add_class("juci_directories"); - - tree_store->set_sort_column(column_record.id, Gtk::SortType::SORT_ASCENDING); - set_enable_search(true); //TODO: why does this not work in OS X? - set_search_column(column_record.name); - - signal_row_activated().connect([this](const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *column){ - auto iter = tree_store->get_iter(path); - if (iter) { - auto filesystem_path=iter->get_value(column_record.path); - if(filesystem_path!="") { - if (boost::filesystem::is_directory(boost::filesystem::path(filesystem_path))) - row_expanded(path) ? collapse_row(path) : expand_row(path, false); - else - Notebook::get().open(filesystem_path); - } - } - }); - - signal_test_expand_row().connect([this](const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path){ - if(iter->children().begin()->get_value(column_record.path)=="") - add_or_update_path(iter->get_value(column_record.path), *iter, true); - return false; - }); - signal_row_collapsed().connect([this](const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path){ - this->remove_path(iter->get_value(column_record.path)); - }); - - enable_model_drag_source(); - enable_model_drag_dest(); - - auto new_file_label = "New File"; - auto new_file_function = [this] { - if(menu_popup_row_path.empty()) - return; - EntryBox::get().clear(); - EntryBox::get().entries.emplace_back("", [this, source_path=menu_popup_row_path](const std::string &content) { - bool is_directory=boost::filesystem::is_directory(source_path); - auto target_path = (is_directory ? source_path : source_path.parent_path())/content; - if(!boost::filesystem::exists(target_path)) { - if(filesystem::write(target_path, "")) { - update(); - Notebook::get().open(target_path); - on_save_file(target_path); - } - else { - Terminal::get().print("Error: could not create "+target_path.string()+'\n', true); - return; + set_enable_tree_lines(true); + + tree_store = TreeStore::create(); + tree_store->set_column_types(column_record); + set_model(tree_store); + + get_column(0)->set_title(""); + + auto renderer = dynamic_cast<Gtk::CellRendererText *>(get_column(0)->get_first_cell()); + get_column(0)->set_cell_data_func(*renderer, + [this](Gtk::CellRenderer *renderer, const Gtk::TreeModel::iterator &iter) { + if (auto renderer_text = dynamic_cast<Gtk::CellRendererText *>(renderer)) + renderer_text->property_markup() = iter->get_value(column_record.markup); + }); + + get_style_context()->add_class("juci_directories"); + + tree_store->set_sort_column(column_record.id, Gtk::SortType::SORT_ASCENDING); + set_enable_search(true); //TODO: why does this not work in OS X? + set_search_column(column_record.name); + + signal_row_activated().connect([this](const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *column) { + auto iter = tree_store->get_iter(path); + if (iter) { + auto filesystem_path = iter->get_value(column_record.path); + if (filesystem_path != "") { + if (boost::filesystem::is_directory(boost::filesystem::path(filesystem_path))) + row_expanded(path) ? collapse_row(path) : expand_row(path, false); + else + Notebook::get().open(filesystem_path); + } } - } - else { - Terminal::get().print("Error: could not create "+target_path.string()+": already exists\n", true); - return; - } - - EntryBox::get().hide(); }); - auto entry_it=EntryBox::get().entries.begin(); - entry_it->set_placeholder_text("Filename"); - EntryBox::get().buttons.emplace_back("Create New File", [entry_it](){ - entry_it->activate(); + + signal_test_expand_row().connect([this](const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path) { + if (iter->children().begin()->get_value(column_record.path) == "") + add_or_update_path(iter->get_value(column_record.path), *iter, true); + return false; }); - EntryBox::get().show(); - }; - - menu_item_new_file.set_label(new_file_label); - menu_item_new_file.signal_activate().connect(new_file_function); - menu.append(menu_item_new_file); - - menu_root_item_new_file.set_label(new_file_label); - menu_root_item_new_file.signal_activate().connect(new_file_function); - menu_root.append(menu_root_item_new_file); - - auto new_folder_label = "New Folder"; - auto new_folder_function = [this] { - if(menu_popup_row_path.empty()) - return; - EntryBox::get().clear(); - EntryBox::get().entries.emplace_back("", [this, source_path=menu_popup_row_path](const std::string &content) { - bool is_directory=boost::filesystem::is_directory(source_path); - auto target_path = (is_directory ? source_path : source_path.parent_path())/content; - if(!boost::filesystem::exists(target_path)) { - boost::system::error_code ec; - boost::filesystem::create_directory(target_path, ec); - if(!ec) { - update(); - select(target_path); - } - else { - Terminal::get().print("Error: could not create "+target_path.string()+": "+ec.message(), true); - return; - } - } - else { - Terminal::get().print("Error: could not create "+target_path.string()+": already exists\n", true); - return; - } - - EntryBox::get().hide(); + signal_row_collapsed().connect([this](const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path) { + this->remove_path(iter->get_value(column_record.path)); }); - auto entry_it=EntryBox::get().entries.begin(); - entry_it->set_placeholder_text("Folder name"); - EntryBox::get().buttons.emplace_back("Create New Folder", [entry_it](){ - entry_it->activate(); + + enable_model_drag_source(); + enable_model_drag_dest(); + + auto new_file_label = "New File"; + auto new_file_function = [this] { + if (menu_popup_row_path.empty()) + return; + EntryBox::get().clear(); + EntryBox::get().entries.emplace_back("", [this, source_path = menu_popup_row_path](const std::string &content) { + bool is_directory = boost::filesystem::is_directory(source_path); + auto target_path = (is_directory ? source_path : source_path.parent_path()) / content; + if (!boost::filesystem::exists(target_path)) { + if (filesystem::write(target_path, "")) { + update(); + Notebook::get().open(target_path); + on_save_file(target_path); + } else { + Terminal::get().print("Error: could not create " + target_path.string() + '\n', true); + return; + } + } else { + Terminal::get().print("Error: could not create " + target_path.string() + ": already exists\n", true); + return; + } + + EntryBox::get().hide(); + }); + auto entry_it = EntryBox::get().entries.begin(); + entry_it->set_placeholder_text("Filename"); + EntryBox::get().buttons.emplace_back("Create New File", [entry_it]() { + entry_it->activate(); + }); + EntryBox::get().show(); + }; + + menu_item_new_file.set_label(new_file_label); + menu_item_new_file.signal_activate().connect(new_file_function); + menu.append(menu_item_new_file); + + menu_root_item_new_file.set_label(new_file_label); + menu_root_item_new_file.signal_activate().connect(new_file_function); + menu_root.append(menu_root_item_new_file); + + auto new_folder_label = "New Folder"; + auto new_folder_function = [this] { + if (menu_popup_row_path.empty()) + return; + EntryBox::get().clear(); + EntryBox::get().entries.emplace_back("", [this, source_path = menu_popup_row_path](const std::string &content) { + bool is_directory = boost::filesystem::is_directory(source_path); + auto target_path = (is_directory ? source_path : source_path.parent_path()) / content; + if (!boost::filesystem::exists(target_path)) { + boost::system::error_code ec; + boost::filesystem::create_directory(target_path, ec); + if (!ec) { + update(); + select(target_path); + } else { + Terminal::get().print("Error: could not create " + target_path.string() + ": " + ec.message(), + true); + return; + } + } else { + Terminal::get().print("Error: could not create " + target_path.string() + ": already exists\n", true); + return; + } + + EntryBox::get().hide(); + }); + auto entry_it = EntryBox::get().entries.begin(); + entry_it->set_placeholder_text("Folder name"); + EntryBox::get().buttons.emplace_back("Create New Folder", [entry_it]() { + entry_it->activate(); + }); + EntryBox::get().show(); + }; + + menu_item_new_folder.set_label(new_folder_label); + menu_item_new_folder.signal_activate().connect(new_folder_function); + menu.append(menu_item_new_folder); + + menu_root_item_new_folder.set_label(new_folder_label); + menu_root_item_new_folder.signal_activate().connect(new_folder_function); + menu_root.append(menu_root_item_new_folder); + + menu.append(menu_item_separator); + + menu_item_rename.set_label("Rename"); + menu_item_rename.signal_activate().connect([this] { + if (menu_popup_row_path.empty()) + return; + EntryBox::get().clear(); + EntryBox::get().entries.emplace_back(menu_popup_row_path.filename().string(), + [this, source_path = menu_popup_row_path](const std::string &content) { + bool is_directory = boost::filesystem::is_directory(source_path); + + auto target_path = source_path.parent_path() / content; + + if (boost::filesystem::exists(target_path)) { + Terminal::get().print( + "Error: could not rename to " + target_path.string() + + ": already exists\n", true); + return; + } + + if (is_directory) + this->remove_path(source_path); + + boost::system::error_code ec; + boost::filesystem::rename(source_path, target_path, ec); + if (ec) { + Terminal::get().print( + "Error: could not rename " + source_path.string() + ": " + + ec.message() + '\n', true); + return; + } + update(); + on_save_file(target_path); + select(target_path); + + for (size_t c = 0; c < Notebook::get().size(); c++) { + auto view = Notebook::get().get_view(c); + if (is_directory) { + if (filesystem::file_in_path(view->file_path, source_path)) { + auto file_it = view->file_path.begin(); + for (auto source_it = source_path.begin(); + source_it != source_path.end(); source_it++) + file_it++; + auto new_file_path = target_path; + for (; file_it != view->file_path.end(); file_it++) + new_file_path /= *file_it; + view->rename(new_file_path); + } + } else if (view->file_path == source_path) { + view->rename(target_path); + + std::string old_language_id; + if (view->language) + old_language_id = view->language->get_id(); + view->language = Source::guess_language(target_path); + std::string new_language_id; + if (view->language) + new_language_id = view->language->get_id(); + if (new_language_id != old_language_id) + Terminal::get().print( + "Warning: language for " + target_path.string() + + " has changed. Please reopen the file\n"); + } + } + + EntryBox::get().hide(); + }); + auto entry_it = EntryBox::get().entries.begin(); + entry_it->set_placeholder_text("Filename"); + EntryBox::get().buttons.emplace_back("Rename file", [entry_it]() { + entry_it->activate(); + }); + EntryBox::get().show(); }); - EntryBox::get().show(); - }; - - menu_item_new_folder.set_label(new_folder_label); - menu_item_new_folder.signal_activate().connect(new_folder_function); - menu.append(menu_item_new_folder); - - menu_root_item_new_folder.set_label(new_folder_label); - menu_root_item_new_folder.signal_activate().connect(new_folder_function); - menu_root.append(menu_root_item_new_folder); - - menu.append(menu_item_separator); - - menu_item_rename.set_label("Rename"); - menu_item_rename.signal_activate().connect([this] { - if(menu_popup_row_path.empty()) - return; - EntryBox::get().clear(); - EntryBox::get().entries.emplace_back(menu_popup_row_path.filename().string(), [this, source_path=menu_popup_row_path](const std::string &content){ - bool is_directory=boost::filesystem::is_directory(source_path); - - auto target_path=source_path.parent_path()/content; - - if(boost::filesystem::exists(target_path)) { - Terminal::get().print("Error: could not rename to "+target_path.string()+": already exists\n", true); - return; - } - - if(is_directory) - this->remove_path(source_path); - - boost::system::error_code ec; - boost::filesystem::rename(source_path, target_path, ec); - if(ec) { - Terminal::get().print("Error: could not rename "+source_path.string()+": "+ec.message()+'\n', true); - return; - } - update(); - on_save_file(target_path); - select(target_path); - - for(size_t c=0;c<Notebook::get().size();c++) { - auto view=Notebook::get().get_view(c); - if(is_directory) { - if(filesystem::file_in_path(view->file_path, source_path)) { - auto file_it=view->file_path.begin(); - for(auto source_it=source_path.begin();source_it!=source_path.end();source_it++) - file_it++; - auto new_file_path=target_path; - for(;file_it!=view->file_path.end();file_it++) - new_file_path/=*file_it; - view->rename(new_file_path); - } - } - else if(view->file_path==source_path) { - view->rename(target_path); - - std::string old_language_id; - if(view->language) - old_language_id=view->language->get_id(); - view->language=Source::guess_language(target_path); - std::string new_language_id; - if(view->language) - new_language_id=view->language->get_id(); - if(new_language_id!=old_language_id) - Terminal::get().print("Warning: language for "+target_path.string()+" has changed. Please reopen the file\n"); + menu.append(menu_item_rename); + + menu_item_delete.set_label("Delete"); + menu_item_delete.signal_activate().connect([this] { + if (menu_popup_row_path.empty()) + return; + Gtk::MessageDialog dialog(*static_cast<Gtk::Window *>(get_toplevel()), "Delete!", false, Gtk::MESSAGE_QUESTION, + Gtk::BUTTONS_YES_NO); + dialog.set_default_response(Gtk::RESPONSE_NO); + dialog.set_secondary_text("Are you sure you want to delete " + menu_popup_row_path.string() + "?"); + int result = dialog.run(); + if (result == Gtk::RESPONSE_YES) { + bool is_directory = boost::filesystem::is_directory(menu_popup_row_path); + + boost::system::error_code ec; + boost::filesystem::remove_all(menu_popup_row_path, ec); + if (ec) + Terminal::get().print( + "Error: could not delete " + menu_popup_row_path.string() + ": " + ec.message() + "\n", true); + else { + update(); + + for (size_t c = 0; c < Notebook::get().size(); c++) { + auto view = Notebook::get().get_view(c); + + if (is_directory) { + if (filesystem::file_in_path(view->file_path, menu_popup_row_path)) + view->get_buffer()->set_modified(); + } else if (view->file_path == menu_popup_row_path) + view->get_buffer()->set_modified(); + } + } } - } - - EntryBox::get().hide(); }); - auto entry_it=EntryBox::get().entries.begin(); - entry_it->set_placeholder_text("Filename"); - EntryBox::get().buttons.emplace_back("Rename file", [entry_it](){ - entry_it->activate(); - }); - EntryBox::get().show(); - }); - menu.append(menu_item_rename); - - menu_item_delete.set_label("Delete"); - menu_item_delete.signal_activate().connect([this] { - if(menu_popup_row_path.empty()) - return; - Gtk::MessageDialog dialog(*static_cast<Gtk::Window*>(get_toplevel()), "Delete!", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO); - dialog.set_default_response(Gtk::RESPONSE_NO); - dialog.set_secondary_text("Are you sure you want to delete "+menu_popup_row_path.string()+"?"); - int result = dialog.run(); - if(result==Gtk::RESPONSE_YES) { - bool is_directory=boost::filesystem::is_directory(menu_popup_row_path); - - boost::system::error_code ec; - boost::filesystem::remove_all(menu_popup_row_path, ec); - if(ec) - Terminal::get().print("Error: could not delete "+menu_popup_row_path.string()+": "+ec.message()+"\n", true); - else { - update(); - - for(size_t c=0;c<Notebook::get().size();c++) { - auto view=Notebook::get().get_view(c); - - if(is_directory) { - if(filesystem::file_in_path(view->file_path, menu_popup_row_path)) - view->get_buffer()->set_modified(); - } - else if(view->file_path==menu_popup_row_path) - view->get_buffer()->set_modified(); - } - } - } - }); - menu.append(menu_item_delete); - - menu.show_all(); - menu.accelerate(*this); - - menu_root.show_all(); - menu_root.accelerate(*this); - - set_headers_clickable(); - forall([this](Gtk::Widget &widget) { - if(widget.get_name()=="GtkButton") { - widget.signal_button_press_event().connect([this](GdkEventButton *event) { - if(event->type==GDK_BUTTON_PRESS && event->button==GDK_BUTTON_SECONDARY && !path.empty()) { - menu_popup_row_path=this->path; - menu_root.popup(event->button, event->time); + menu.append(menu_item_delete); + + menu.show_all(); + menu.accelerate(*this); + + menu_root.show_all(); + menu_root.accelerate(*this); + + set_headers_clickable(); + forall([this](Gtk::Widget &widget) { + if (widget.get_name() == "GtkButton") { + widget.signal_button_press_event().connect([this](GdkEventButton *event) { + if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_SECONDARY && !path.empty()) { + menu_popup_row_path = this->path; + menu_root.popup(event->button, event->time); + } + return true; + }); } - return true; - }); - } - }); + }); } Directories::~Directories() { - dispatcher.disconnect(); + dispatcher.disconnect(); } void Directories::open(const boost::filesystem::path &dir_path) { - boost::system::error_code ec; - if(dir_path.empty() || !boost::filesystem::exists(dir_path, ec) || ec) - return; - - tree_store->clear(); - - path=filesystem::get_normal_path(dir_path); - - //TODO: report that set_title does not handle '_' correctly? - auto title=path.filename().string(); - size_t pos=0; - while((pos=title.find('_', pos))!=std::string::npos) { - title.replace(pos, 1, "__"); - pos+=2; - } - get_column(0)->set_title(title); - - for(auto &directory: directories) { - if(directory.second.repository) - directory.second.repository->clear_saved_status(); - } - directories.clear(); - - add_or_update_path(path, Gtk::TreeModel::Row(), true); + boost::system::error_code ec; + if (dir_path.empty() || !boost::filesystem::exists(dir_path, ec) || ec) + return; + + tree_store->clear(); + + path = filesystem::get_normal_path(dir_path); + + //TODO: report that set_title does not handle '_' correctly? + auto title = path.filename().string(); + size_t pos = 0; + while ((pos = title.find('_', pos)) != std::string::npos) { + title.replace(pos, 1, "__"); + pos += 2; + } + get_column(0)->set_title(title); + + for (auto &directory: directories) { + if (directory.second.repository) + directory.second.repository->clear_saved_status(); + } + directories.clear(); + + add_or_update_path(path, Gtk::TreeModel::Row(), true); } void Directories::update() { - std::vector<std::pair<std::string, Gtk::TreeModel::Row> > saved_directories; - for(auto &directory: directories) - saved_directories.emplace_back(directory.first, directory.second.row); - for(auto &directory: saved_directories) - add_or_update_path(directory.first, directory.second, false); + std::vector<std::pair<std::string, Gtk::TreeModel::Row> > saved_directories; + for (auto &directory: directories) + saved_directories.emplace_back(directory.first, directory.second.row); + for (auto &directory: saved_directories) + add_or_update_path(directory.first, directory.second, false); } void Directories::on_save_file(boost::filesystem::path file_path) { - auto it=directories.find(file_path.parent_path().string()); - if(it!=directories.end()) { - if(it->second.repository) - it->second.repository->clear_saved_status(); - colorize_path(it->first, true); - } + auto it = directories.find(file_path.parent_path().string()); + if (it != directories.end()) { + if (it->second.repository) + it->second.repository->clear_saved_status(); + colorize_path(it->first, true); + } } void Directories::select(const boost::filesystem::path &select_path) { - if(path=="") - return; - - if(!filesystem::file_in_path(select_path, path)) - return; - - //return if the select_path is already selected - auto iter=get_selection()->get_selected(); - if(iter) { - if(iter->get_value(column_record.path)==select_path) - return; - } - - std::list<boost::filesystem::path> paths; - boost::filesystem::path parent_path; - if(boost::filesystem::is_directory(select_path)) - parent_path=select_path; - else - parent_path=select_path.parent_path(); - - //check if select_path is already expanded - if(directories.find(parent_path.string())!=directories.end()) { - //set cursor at select_path and return - tree_store->foreach_iter([this, &select_path](const Gtk::TreeModel::iterator &iter){ - if(iter->get_value(column_record.path)==select_path) { - auto tree_path=Gtk::TreePath(iter); - expand_to_path(tree_path); - set_cursor(tree_path); - return true; - } - return false; - }); - return; - } - - paths.emplace_front(parent_path); - while(parent_path!=path) { - parent_path=parent_path.parent_path(); + if (path == "") + return; + + if (!filesystem::file_in_path(select_path, path)) + return; + + //return if the select_path is already selected + auto iter = get_selection()->get_selected(); + if (iter) { + if (iter->get_value(column_record.path) == select_path) + return; + } + + std::list<boost::filesystem::path> paths; + boost::filesystem::path parent_path; + if (boost::filesystem::is_directory(select_path)) + parent_path = select_path; + else + parent_path = select_path.parent_path(); + + //check if select_path is already expanded + if (directories.find(parent_path.string()) != directories.end()) { + //set cursor at select_path and return + tree_store->foreach_iter([this, &select_path](const Gtk::TreeModel::iterator &iter) { + if (iter->get_value(column_record.path) == select_path) { + auto tree_path = Gtk::TreePath(iter); + expand_to_path(tree_path); + set_cursor(tree_path); + return true; + } + return false; + }); + return; + } + paths.emplace_front(parent_path); - } - - //expand to select_path - for(auto &a_path: paths) { - tree_store->foreach_iter([this, &a_path](const Gtk::TreeModel::iterator &iter){ - if(iter->get_value(column_record.path)==a_path) { - add_or_update_path(a_path, *iter, true); - return true; - } - return false; - }); - } - - //set cursor at select_path - tree_store->foreach_iter([this, &select_path](const Gtk::TreeModel::iterator &iter){ - if(iter->get_value(column_record.path)==select_path) { - auto tree_path=Gtk::TreePath(iter); - expand_to_path(tree_path); - set_cursor(tree_path); - return true; + while (parent_path != path) { + parent_path = parent_path.parent_path(); + paths.emplace_front(parent_path); } - return false; - }); + + //expand to select_path + for (auto &a_path: paths) { + tree_store->foreach_iter([this, &a_path](const Gtk::TreeModel::iterator &iter) { + if (iter->get_value(column_record.path) == a_path) { + add_or_update_path(a_path, *iter, true); + return true; + } + return false; + }); + } + + //set cursor at select_path + tree_store->foreach_iter([this, &select_path](const Gtk::TreeModel::iterator &iter) { + if (iter->get_value(column_record.path) == select_path) { + auto tree_path = Gtk::TreePath(iter); + expand_to_path(tree_path); + set_cursor(tree_path); + return true; + } + return false; + }); } -bool Directories::on_button_press_event(GdkEventButton* event) { - if(event->type==GDK_BUTTON_PRESS && event->button==GDK_BUTTON_SECONDARY) { - EntryBox::get().hide(); - Gtk::TreeModel::Path path; - if(get_path_at_pos(static_cast<int>(event->x), static_cast<int>(event->y), path)) { - menu_popup_row_path=get_model()->get_iter(path)->get_value(column_record.path); - if(menu_popup_row_path.empty()) { - auto parent=get_model()->get_iter(path)->parent(); - if(parent) - menu_popup_row_path=parent->get_value(column_record.path); - else { - menu_popup_row_path=this->path; - menu_root.popup(event->button, event->time); - return true; +bool Directories::on_button_press_event(GdkEventButton *event) { + if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_SECONDARY) { + EntryBox::get().hide(); + Gtk::TreeModel::Path path; + if (get_path_at_pos(static_cast<int>(event->x), static_cast<int>(event->y), path)) { + menu_popup_row_path = get_model()->get_iter(path)->get_value(column_record.path); + if (menu_popup_row_path.empty()) { + auto parent = get_model()->get_iter(path)->parent(); + if (parent) + menu_popup_row_path = parent->get_value(column_record.path); + else { + menu_popup_row_path = this->path; + menu_root.popup(event->button, event->time); + return true; + } + } + menu.popup(event->button, event->time); + return true; + } else if (!this->path.empty()) { + menu_popup_row_path = this->path; + menu_root.popup(event->button, event->time); + return true; } - } - menu.popup(event->button, event->time); - return true; } - else if(!this->path.empty()) { - menu_popup_row_path=this->path; - menu_root.popup(event->button, event->time); - return true; - } - } - - return Gtk::TreeView::on_button_press_event(event); + + return Gtk::TreeView::on_button_press_event(event); } -void Directories::add_or_update_path(const boost::filesystem::path &dir_path, const Gtk::TreeModel::Row &row, bool include_parent_paths) { - auto path_it=directories.find(dir_path.string()); - if(!boost::filesystem::exists(dir_path)) { - if(path_it!=directories.end()) - directories.erase(path_it); - return; - } - - if(path_it==directories.end()) { - auto g_file=Gio::File::create_for_path(dir_path.string()); - auto monitor=g_file->monitor_directory(Gio::FileMonitorFlags::FILE_MONITOR_WATCH_MOVES); - auto path_and_row=std::make_shared<std::pair<boost::filesystem::path, Gtk::TreeModel::Row> >(dir_path, row); - auto connection=std::make_shared<sigc::connection>(); - - std::shared_ptr<Git::Repository> repository; - try { - repository=Git::get_repository(dir_path); +void Directories::add_or_update_path(const boost::filesystem::path &dir_path, const Gtk::TreeModel::Row &row, + bool include_parent_paths) { + auto path_it = directories.find(dir_path.string()); + if (!boost::filesystem::exists(dir_path)) { + if (path_it != directories.end()) + directories.erase(path_it); + return; } - catch(const std::exception &) {} - - monitor->signal_changed().connect([this, connection, path_and_row, repository] (const Glib::RefPtr<Gio::File> &file, - const Glib::RefPtr<Gio::File>&, - Gio::FileMonitorEvent monitor_event) { - if(monitor_event!=Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { - if(repository) - repository->clear_saved_status(); - connection->disconnect(); - *connection=Glib::signal_timeout().connect([path_and_row, this]() { - if(directories.find(path_and_row->first.string())!=directories.end()) - add_or_update_path(path_and_row->first, path_and_row->second, true); - return false; - }, 500); - } - }); - - std::shared_ptr<sigc::connection> repository_connection(new sigc::connection(), [](sigc::connection *connection) { - connection->disconnect(); - delete connection; - }); - - if(repository) { - auto connection=std::make_shared<sigc::connection>(); - *repository_connection=repository->monitor->signal_changed().connect([this, connection, path_and_row](const Glib::RefPtr<Gio::File> &file, - const Glib::RefPtr<Gio::File>&, - Gio::FileMonitorEvent monitor_event) { - if(monitor_event!=Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { - connection->disconnect(); - *connection=Glib::signal_timeout().connect([this, path_and_row] { - if(directories.find(path_and_row->first.string())!=directories.end()) - colorize_path(path_and_row->first, false); - return false; - }, 500); + + if (path_it == directories.end()) { + auto g_file = Gio::File::create_for_path(dir_path.string()); + auto monitor = g_file->monitor_directory(Gio::FileMonitorFlags::FILE_MONITOR_WATCH_MOVES); + auto path_and_row = std::make_shared<std::pair<boost::filesystem::path, Gtk::TreeModel::Row> >(dir_path, row); + auto connection = std::make_shared<sigc::connection>(); + + std::shared_ptr<Git::Repository> repository; + try { + repository = Git::get_repository(dir_path); } - }); + catch (const std::exception &) {} + + monitor->signal_changed().connect( + [this, connection, path_and_row, repository](const Glib::RefPtr<Gio::File> &file, + const Glib::RefPtr<Gio::File> &, + Gio::FileMonitorEvent monitor_event) { + if (monitor_event != Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { + if (repository) + repository->clear_saved_status(); + connection->disconnect(); + *connection = Glib::signal_timeout().connect([path_and_row, this]() { + if (directories.find(path_and_row->first.string()) != directories.end()) + add_or_update_path(path_and_row->first, path_and_row->second, true); + return false; + }, 500); + } + }); + + std::shared_ptr<sigc::connection> repository_connection(new sigc::connection(), + [](sigc::connection *connection) { + connection->disconnect(); + delete connection; + }); + + if (repository) { + auto connection = std::make_shared<sigc::connection>(); + *repository_connection = repository->monitor->signal_changed().connect( + [this, connection, path_and_row](const Glib::RefPtr<Gio::File> &file, + const Glib::RefPtr<Gio::File> &, + Gio::FileMonitorEvent monitor_event) { + if (monitor_event != Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { + connection->disconnect(); + *connection = Glib::signal_timeout().connect([this, path_and_row] { + if (directories.find(path_and_row->first.string()) != directories.end()) + colorize_path(path_and_row->first, false); + return false; + }, 500); + } + }); + } + directories[dir_path.string()] = {row, monitor, repository, repository_connection}; } - directories[dir_path.string()]={row, monitor, repository, repository_connection}; - } - - Gtk::TreeNodeChildren children(row?row.children():tree_store->children()); - if(children) { - if(children.begin()->get_value(column_record.path)=="") - tree_store->erase(children.begin()); - } - std::unordered_set<std::string> not_deleted; - boost::filesystem::directory_iterator end_it; - for(boost::filesystem::directory_iterator it(dir_path);it!=end_it;it++) { - auto filename=it->path().filename().string(); - bool already_added=false; - if(children) { - for(auto &child: children) { - if(child->get_value(column_record.name)==filename) { - not_deleted.emplace(filename); - already_added=true; - break; + + Gtk::TreeNodeChildren children(row ? row.children() : tree_store->children()); + if (children) { + if (children.begin()->get_value(column_record.path) == "") + tree_store->erase(children.begin()); + } + std::unordered_set<std::string> not_deleted; + boost::filesystem::directory_iterator end_it; + for (boost::filesystem::directory_iterator it(dir_path); it != end_it; it++) { + auto filename = it->path().filename().string(); + bool already_added = false; + if (children) { + for (auto &child: children) { + if (child->get_value(column_record.name) == filename) { + not_deleted.emplace(filename); + already_added = true; + break; + } + } + } + if (!already_added) { + auto child = tree_store->append(children); + not_deleted.emplace(filename); + child->set_value(column_record.name, filename); + child->set_value(column_record.markup, Glib::Markup::escape_text(filename)); + child->set_value(column_record.path, it->path()); + if (boost::filesystem::is_directory(it->path())) { + child->set_value(column_record.id, '1' + filename); + auto grandchild = tree_store->append(child->children()); + grandchild->set_value(column_record.name, std::string("(empty)")); + grandchild->set_value(column_record.markup, Glib::Markup::escape_text("(empty)")); + grandchild->set_value(column_record.type, PathType::UNKNOWN); + } else { + child->set_value(column_record.id, '2' + filename); + + auto language = Source::guess_language(it->path().filename()); + if (!language) + child->set_value(column_record.type, PathType::UNKNOWN); + } } - } } - if(!already_added) { - auto child = tree_store->append(children); - not_deleted.emplace(filename); - child->set_value(column_record.name, filename); - child->set_value(column_record.markup, Glib::Markup::escape_text(filename)); - child->set_value(column_record.path, it->path()); - if (boost::filesystem::is_directory(it->path())) { - child->set_value(column_record.id, '1'+filename); - auto grandchild=tree_store->append(child->children()); - grandchild->set_value(column_record.name, std::string("(empty)")); - grandchild->set_value(column_record.markup, Glib::Markup::escape_text("(empty)")); - grandchild->set_value(column_record.type, PathType::UNKNOWN); - } - else { - child->set_value(column_record.id, '2'+filename); - - auto language=Source::guess_language(it->path().filename()); - if(!language) - child->set_value(column_record.type, PathType::UNKNOWN); - } + if (children) { + for (auto it = children.begin(); it != children.end();) { + if (not_deleted.count(it->get_value(column_record.name)) == 0) { + it = tree_store->erase(it); + } else + it++; + } } - } - if(children) { - for(auto it=children.begin();it!=children.end();) { - if(not_deleted.count(it->get_value(column_record.name))==0) { - it=tree_store->erase(it); - } - else - it++; + if (!children) { + auto child = tree_store->append(children); + child->set_value(column_record.name, std::string("(empty)")); + child->set_value(column_record.markup, Glib::Markup::escape_text("(empty)")); + child->set_value(column_record.type, PathType::UNKNOWN); } - } - if(!children) { - auto child=tree_store->append(children); - child->set_value(column_record.name, std::string("(empty)")); - child->set_value(column_record.markup, Glib::Markup::escape_text("(empty)")); - child->set_value(column_record.type, PathType::UNKNOWN); - } - - colorize_path(dir_path, include_parent_paths); + + colorize_path(dir_path, include_parent_paths); } void Directories::remove_path(const boost::filesystem::path &dir_path) { - auto it=directories.find(dir_path.string()); - if(it==directories.end()) - return; - auto children=it->second.row->children(); - - for(auto it=directories.begin();it!=directories.end();) { - if(filesystem::file_in_path(it->first, dir_path)) - it=directories.erase(it); - else - it++; - } - - if(children) { - while(children) { - tree_store->erase(children.begin()); + auto it = directories.find(dir_path.string()); + if (it == directories.end()) + return; + auto children = it->second.row->children(); + + for (auto it = directories.begin(); it != directories.end();) { + if (filesystem::file_in_path(it->first, dir_path)) + it = directories.erase(it); + else + it++; + } + + if (children) { + while (children) { + tree_store->erase(children.begin()); + } + auto child = tree_store->append(children); + child->set_value(column_record.name, std::string("(empty)")); + child->set_value(column_record.markup, Glib::Markup::escape_text("(empty)")); + child->set_value(column_record.type, PathType::UNKNOWN); } - auto child=tree_store->append(children); - child->set_value(column_record.name, std::string("(empty)")); - child->set_value(column_record.markup, Glib::Markup::escape_text("(empty)")); - child->set_value(column_record.type, PathType::UNKNOWN); - } } void Directories::colorize_path(const boost::filesystem::path &dir_path, bool include_parent_paths) { - auto it=directories.find(dir_path.string()); - if(it==directories.end()) - return; - - if(it!=directories.end() && it->second.repository) { - auto repository=it->second.repository; - std::thread git_status_thread([this, dir_path, repository, include_parent_paths] { - Git::Repository::Status status; - try { - status=repository->get_status(); - } - catch(const std::exception &e) { - Terminal::get().async_print(std::string("Error (git): ")+e.what()+'\n', true); - } - - dispatcher.post([this, dir_path=std::move(dir_path), include_parent_paths, status=std::move(status)] { - auto it=directories.find(dir_path.string()); - if(it==directories.end()) - return; - - auto normal_color=get_style_context()->get_color(Gtk::StateFlags::STATE_FLAG_NORMAL); - Gdk::RGBA gray; - gray.set_rgba(0.5, 0.5, 0.5); - Gdk::RGBA yellow; - yellow.set_rgba(1.0, 1.0, 0.2); - double factor=0.5; - yellow.set_red(normal_color.get_red()+factor*(yellow.get_red()-normal_color.get_red())); - yellow.set_green(normal_color.get_green()+factor*(yellow.get_green()-normal_color.get_green())); - yellow.set_blue(normal_color.get_blue()+factor*(yellow.get_blue()-normal_color.get_blue())); - Gdk::RGBA green; - green.set_rgba(0.0, 1.0, 0.0); - factor=0.4; - green.set_red(normal_color.get_red()+factor*(green.get_red()-normal_color.get_red())); - green.set_green(normal_color.get_green()+factor*(green.get_green()-normal_color.get_green())); - green.set_blue(normal_color.get_blue()+factor*(green.get_blue()-normal_color.get_blue())); - - do { - Gtk::TreeNodeChildren children(it->second.row?it->second.row.children():tree_store->children()); - if(!children) - return; - - for(auto &child: children) { - auto name=Glib::Markup::escape_text(child.get_value(column_record.name)); - auto path=child.get_value(column_record.path); - Gdk::RGBA *color; - if(status.modified.find(path.generic_string())!=status.modified.end()) - color=&yellow; - else if(status.added.find(path.generic_string())!=status.added.end()) - color=&green; - else - color=&normal_color; - - std::stringstream ss; - ss << '#' << std::setfill('0') << std::hex; - ss << std::setw(2) << std::hex << (color->get_red_u()>>8); - ss << std::setw(2) << std::hex << (color->get_green_u()>>8); - ss << std::setw(2) << std::hex << (color->get_blue_u()>>8); - child.set_value(column_record.markup, "<span foreground=\""+ss.str()+"\">"+name+"</span>"); - - auto type=child.get_value(column_record.type); - if(type==PathType::UNKNOWN) - child.set_value(column_record.markup, "<i>"+child.get_value(column_record.markup)+"</i>"); - } - - if(!include_parent_paths) - break; - - auto path=boost::filesystem::path(it->first); - if(boost::filesystem::exists(path/".git")) - break; - if(path==path.root_directory()) - break; - auto parent_path=boost::filesystem::path(it->first).parent_path(); - it=directories.find(parent_path.string()); - } while(it!=directories.end()); - }); - }); - git_status_thread.detach(); - } + auto it = directories.find(dir_path.string()); + if (it == directories.end()) + return; + + if (it != directories.end() && it->second.repository) { + auto repository = it->second.repository; + std::thread git_status_thread([this, dir_path, repository, include_parent_paths] { + Git::Repository::Status status; + try { + status = repository->get_status(); + } + catch (const std::exception &e) { + Terminal::get().async_print(std::string("Error (git): ") + e.what() + '\n', true); + } + + dispatcher.post([this, dir_path = std::move(dir_path), include_parent_paths, status = std::move(status)] { + auto it = directories.find(dir_path.string()); + if (it == directories.end()) + return; + + auto normal_color = get_style_context()->get_color(Gtk::StateFlags::STATE_FLAG_NORMAL); + Gdk::RGBA gray; + gray.set_rgba(0.5, 0.5, 0.5); + Gdk::RGBA yellow; + yellow.set_rgba(1.0, 1.0, 0.2); + double factor = 0.5; + yellow.set_red(normal_color.get_red() + factor * (yellow.get_red() - normal_color.get_red())); + yellow.set_green(normal_color.get_green() + factor * (yellow.get_green() - normal_color.get_green())); + yellow.set_blue(normal_color.get_blue() + factor * (yellow.get_blue() - normal_color.get_blue())); + Gdk::RGBA green; + green.set_rgba(0.0, 1.0, 0.0); + factor = 0.4; + green.set_red(normal_color.get_red() + factor * (green.get_red() - normal_color.get_red())); + green.set_green(normal_color.get_green() + factor * (green.get_green() - normal_color.get_green())); + green.set_blue(normal_color.get_blue() + factor * (green.get_blue() - normal_color.get_blue())); + + do { + Gtk::TreeNodeChildren children(it->second.row ? it->second.row.children() : tree_store->children()); + if (!children) + return; + + for (auto &child: children) { + auto name = Glib::Markup::escape_text(child.get_value(column_record.name)); + auto path = child.get_value(column_record.path); + Gdk::RGBA *color; + if (status.modified.find(path.generic_string()) != status.modified.end()) + color = &yellow; + else if (status.added.find(path.generic_string()) != status.added.end()) + color = &green; + else + color = &normal_color; + + std::stringstream ss; + ss << '#' << std::setfill('0') << std::hex; + ss << std::setw(2) << std::hex << (color->get_red_u() >> 8); + ss << std::setw(2) << std::hex << (color->get_green_u() >> 8); + ss << std::setw(2) << std::hex << (color->get_blue_u() >> 8); + child.set_value(column_record.markup, + "<span foreground=\"" + ss.str() + "\">" + name + "</span>"); + + auto type = child.get_value(column_record.type); + if (type == PathType::UNKNOWN) + child.set_value(column_record.markup, + "<i>" + child.get_value(column_record.markup) + "</i>"); + } + + if (!include_parent_paths) + break; + + auto path = boost::filesystem::path(it->first); + if (boost::filesystem::exists(path / ".git")) + break; + if (path == path.root_directory()) + break; + auto parent_path = boost::filesystem::path(it->first).parent_path(); + it = directories.find(parent_path.string()); + } while (it != directories.end()); + }); + }); + git_status_thread.detach(); + } } diff --git a/src/directories.h b/src/directories.h index da6191fb..6caaea48 100644 --- a/src/directories.h +++ b/src/directories.h @@ -1,4 +1,5 @@ #pragma once + #include <gtkmm.h> #include <vector> #include <string> @@ -12,82 +13,96 @@ #include "dispatcher.h" class Directories : public Gtk::ListViewText { - class DirectoryData { - public: - Gtk::TreeModel::Row row; - Glib::RefPtr<Gio::FileMonitor> monitor; - std::shared_ptr<Git::Repository> repository; - std::shared_ptr<sigc::connection> connection; - }; - - enum class PathType {KNOWN, UNKNOWN}; - - class TreeStore : public Gtk::TreeStore { - protected: - TreeStore() {} - - bool row_drop_possible_vfunc(const Gtk::TreeModel::Path &path, const Gtk::SelectionData &selection_data) const override; - bool drag_data_received_vfunc(const TreeModel::Path &path, const Gtk::SelectionData &selection_data) override; - bool drag_data_delete_vfunc (const Gtk::TreeModel::Path &path) override; - - public: - class ColumnRecord : public Gtk::TreeModel::ColumnRecord { + class DirectoryData { + public: + Gtk::TreeModel::Row row; + Glib::RefPtr<Gio::FileMonitor> monitor; + std::shared_ptr<Git::Repository> repository; + std::shared_ptr<sigc::connection> connection; + }; + + enum class PathType { + KNOWN, UNKNOWN + }; + + class TreeStore : public Gtk::TreeStore { + protected: + TreeStore() {} + + bool row_drop_possible_vfunc(const Gtk::TreeModel::Path &path, + const Gtk::SelectionData &selection_data) const override; + + bool drag_data_received_vfunc(const TreeModel::Path &path, const Gtk::SelectionData &selection_data) override; + + bool drag_data_delete_vfunc(const Gtk::TreeModel::Path &path) override; + public: - ColumnRecord() { - add(id); - add(name); - add(markup); - add(path); - add(type); - } - Gtk::TreeModelColumn<std::string> id; - Gtk::TreeModelColumn<std::string> name; - Gtk::TreeModelColumn<Glib::ustring> markup; - Gtk::TreeModelColumn<boost::filesystem::path> path; - Gtk::TreeModelColumn<PathType> type; + class ColumnRecord : public Gtk::TreeModel::ColumnRecord { + public: + ColumnRecord() { + add(id); + add(name); + add(markup); + add(path); + add(type); + } + + Gtk::TreeModelColumn<std::string> id; + Gtk::TreeModelColumn<std::string> name; + Gtk::TreeModelColumn<Glib::ustring> markup; + Gtk::TreeModelColumn<boost::filesystem::path> path; + Gtk::TreeModelColumn<PathType> type; + }; + + static Glib::RefPtr<TreeStore> create() { return Glib::RefPtr<TreeStore>(new TreeStore()); } }; - - static Glib::RefPtr<TreeStore> create() {return Glib::RefPtr<TreeStore>(new TreeStore());} - }; - Directories(); + Directories(); + public: - static Directories &get() { - static Directories singleton; - return singleton; - } - ~Directories(); - - void open(const boost::filesystem::path &dir_path=""); - void update(); - void on_save_file(boost::filesystem::path file_path); - void select(const boost::filesystem::path &path); - - boost::filesystem::path path; - + static Directories &get() { + static Directories singleton; + return singleton; + } + + ~Directories(); + + void open(const boost::filesystem::path &dir_path = ""); + + void update(); + + void on_save_file(boost::filesystem::path file_path); + + void select(const boost::filesystem::path &path); + + boost::filesystem::path path; + protected: - bool on_button_press_event(GdkEventButton *event) override; - + bool on_button_press_event(GdkEventButton *event) override; + private: - void add_or_update_path(const boost::filesystem::path &dir_path, const Gtk::TreeModel::Row &row, bool include_parent_paths); - void remove_path(const boost::filesystem::path &dir_path); - void colorize_path(const boost::filesystem::path &dir_path, bool include_parent_paths); - - Glib::RefPtr<Gtk::TreeStore> tree_store; - TreeStore::ColumnRecord column_record; - - std::unordered_map<std::string, DirectoryData> directories; - - Dispatcher dispatcher; - - Gtk::Menu menu; - Gtk::MenuItem menu_item_new_file; - Gtk::MenuItem menu_item_new_folder; - Gtk::SeparatorMenuItem menu_item_separator; - Gtk::MenuItem menu_item_rename; - Gtk::MenuItem menu_item_delete; - Gtk::Menu menu_root; - Gtk::MenuItem menu_root_item_new_file; - Gtk::MenuItem menu_root_item_new_folder; - boost::filesystem::path menu_popup_row_path; + void add_or_update_path(const boost::filesystem::path &dir_path, const Gtk::TreeModel::Row &row, + bool include_parent_paths); + + void remove_path(const boost::filesystem::path &dir_path); + + void colorize_path(const boost::filesystem::path &dir_path, bool include_parent_paths); + + Glib::RefPtr<Gtk::TreeStore> tree_store; + TreeStore::ColumnRecord column_record; + + std::unordered_map<std::string, DirectoryData> directories; + + Dispatcher dispatcher; + + Gtk::Menu menu; + Gtk::MenuItem menu_item_new_file; + Gtk::MenuItem menu_item_new_folder; + Gtk::SeparatorMenuItem menu_item_separator; + Gtk::MenuItem menu_item_rename; + Gtk::MenuItem menu_item_delete; + Gtk::Menu menu_root; + Gtk::MenuItem menu_root_item_new_file; + Gtk::MenuItem menu_root_item_new_folder; + boost::filesystem::path menu_popup_row_path; }; diff --git a/src/dispatcher.cc b/src/dispatcher.cc index 5ad2d602..1a0651de 100644 --- a/src/dispatcher.cc +++ b/src/dispatcher.cc @@ -2,30 +2,30 @@ #include <vector> Dispatcher::Dispatcher() { - connection=dispatcher.connect([this] { - std::vector<std::list<std::function<void()>>::iterator> its; - { - std::unique_lock<std::mutex> lock(functions_mutex); - if(functions.empty()) - return; - its.reserve(functions.size()); - for(auto it=functions.begin();it!=functions.end();++it) - its.emplace_back(it); - } - for(auto &it: its) - (*it)(); - { - std::unique_lock<std::mutex> lock(functions_mutex); - for(auto &it: its) - functions.erase(it); - } - }); + connection = dispatcher.connect([this] { + std::vector<std::list<std::function<void()>>::iterator> its; + { + std::unique_lock<std::mutex> lock(functions_mutex); + if (functions.empty()) + return; + its.reserve(functions.size()); + for (auto it = functions.begin(); it != functions.end(); ++it) + its.emplace_back(it); + } + for (auto &it: its) + (*it)(); + { + std::unique_lock<std::mutex> lock(functions_mutex); + for (auto &it: its) + functions.erase(it); + } + }); } Dispatcher::~Dispatcher() { - disconnect(); + disconnect(); } void Dispatcher::disconnect() { - connection.disconnect(); + connection.disconnect(); } diff --git a/src/dispatcher.h b/src/dispatcher.h index 84766285..6fbab177 100644 --- a/src/dispatcher.h +++ b/src/dispatcher.h @@ -1,4 +1,5 @@ #pragma once + #include <gtkmm.h> #include <mutex> #include <functional> @@ -6,22 +7,23 @@ class Dispatcher { private: - std::list<std::function<void()>> functions; - std::mutex functions_mutex; - Glib::Dispatcher dispatcher; - sigc::connection connection; + std::list<std::function<void()>> functions; + std::mutex functions_mutex; + Glib::Dispatcher dispatcher; + sigc::connection connection; public: - Dispatcher(); - ~Dispatcher(); - - template<typename T> - void post(T &&function) { - { - std::unique_lock<std::mutex> lock(functions_mutex); - functions.emplace_back(std::forward<T>(function)); + Dispatcher(); + + ~Dispatcher(); + + template<typename T> + void post(T &&function) { + { + std::unique_lock<std::mutex> lock(functions_mutex); + functions.emplace_back(std::forward<T>(function)); + } + dispatcher(); } - dispatcher(); - } - - void disconnect(); + + void disconnect(); }; diff --git a/src/documentation_cppreference.cc b/src/documentation_cppreference.cc index 060e791d..ef7365bd 100644 --- a/src/documentation_cppreference.cc +++ b/src/documentation_cppreference.cc @@ -1,10 +1,12 @@ -#include "documentation_cppreference.h" -#include <unordered_map> +#include +"documentation_cppreference.h" +#include +<unordered_map> std::string Documentation::CppReference::get_url(const std::string symbol) noexcept { - // Copied from http://upload.cppreference.com/mwiki/images/d/df/html_book_20170409.zip/reference/cppreference-export-ns0,4,8,10.xml - // Using raw string instead of map to reduce compile time - const static std::string symbol_urls = R"(size_t c/types/size_t +// Copied from http://upload.cppreference.com/mwiki/images/d/df/html_book_20170409.zip/reference/cppreference-export-ns0,4,8,10.xml +// Using raw string instead of map to reduce compile time +const static std::string symbol_urls = R"(size_t c/types/size_t ptrdiff_t c/types/ptrdiff_t nullptr_t c/types/nullptr_t NULL c/types/NULL @@ -12173,34 +12175,34 @@ std::experimental::filesystem::is_socket cpp/experimental/fs/is_socket std::experimental::filesystem::is_symlink cpp/experimental/fs/is_symlink std::experimental::filesystem::status_known cpp/experimental/fs/status_known"} )"; - - class SymbolToUrl { - public: - SymbolToUrl(const std::string &symbol_urls) { - size_t symbol_start=0; - size_t symbol_end=std::string::npos; - size_t url_start=std::string::npos; - for(size_t c=0;c<symbol_urls.size();++c) { - auto &chr=symbol_urls[c]; - if(chr=='\t') { - symbol_end=c; - url_start=c+1; - } - else if(chr=='\n') { - if(symbol_end!=std::string::npos && url_start!=std::string::npos) - map.emplace(symbol_urls.substr(symbol_start, symbol_end-symbol_start), symbol_urls.substr(url_start, c-url_start)); - symbol_start=c+1; - symbol_end=std::string::npos; - url_start=std::string::npos; - } - } - } - std::unordered_map<std::string, std::string> map; - }; - - static SymbolToUrl symbol_to_url(symbol_urls); - auto it=symbol_to_url.map.find(symbol); - if(it==symbol_to_url.map.end()) - return std::string(); - return "http://en.cppreference.com/w/"+it->second; + +class SymbolToUrl { +public: +SymbolToUrl(const std::string &symbol_urls) { +size_t symbol_start=0; +size_t symbol_end=std::string::npos; +size_t url_start=std::string::npos; +for (size_t c=0;c<symbol_urls.size();++c) { +auto &chr=symbol_urls[c]; +if (chr=='\t') { +symbol_end=c; +url_start=c+1; +} +else if (chr=='\n') { +if (symbol_end!=std::string::npos && url_start!=std::string::npos) +map.emplace(symbol_urls.substr(symbol_start, symbol_end-symbol_start), symbol_urls.substr(url_start, c-url_start)); +symbol_start=c+1; +symbol_end=std::string::npos; +url_start=std::string::npos; +} +} +} +std::unordered_map<std::string, std::string> map; +}; + +static SymbolToUrl symbol_to_url(symbol_urls); +auto it=symbol_to_url.map.find(symbol); +if (it==symbol_to_url.map.end()) +return std::string(); +return "http://en.cppreference.com/w/"+it->second; } diff --git a/src/documentation_cppreference.h b/src/documentation_cppreference.h index 7a61f837..db9bf26e 100644 --- a/src/documentation_cppreference.h +++ b/src/documentation_cppreference.h @@ -1,9 +1,10 @@ #pragma once + #include <string> namespace Documentation { - class CppReference { - public: - static std::string get_url(const std::string symbol) noexcept; - }; + class CppReference { + public: + static std::string get_url(const std::string symbol) noexcept; + }; } diff --git a/src/entrybox.cc b/src/entrybox.cc index b5cd6638..5a81ef1e 100644 --- a/src/entrybox.cc +++ b/src/entrybox.cc @@ -2,96 +2,101 @@ std::unordered_map<std::string, std::vector<std::string> > EntryBox::entry_histories; -EntryBox::Entry::Entry(const std::string& content, std::function<void(const std::string& content)> on_activate, unsigned width_chars) : Gtk::Entry(), on_activate(on_activate) { - set_max_length(0); - set_width_chars(width_chars); - set_text(content); - selected_history=0; - signal_activate().connect([this](){ - if(this->on_activate) { - auto &history=EntryBox::entry_histories[get_placeholder_text()]; - auto text=get_text(); - if(history.size()==0 || (history.size()>0 && *history.begin()!=text)) - history.emplace(history.begin(), text); - selected_history=0; - this->on_activate(text); - } - }); - signal_key_press_event().connect([this](GdkEventKey* key){ - if(key->keyval==GDK_KEY_Up || key->keyval==GDK_KEY_KP_Up) { - auto &history=entry_histories[get_placeholder_text()]; - if(history.size()>0) { - selected_history++; - if(selected_history>=history.size()) - selected_history=history.size()-1; - set_text(history[selected_history]); - set_position(-1); - } - } - if(key->keyval==GDK_KEY_Down || key->keyval==GDK_KEY_KP_Down) { - auto &history=entry_histories[get_placeholder_text()]; - if(history.size()>0) { - if(selected_history!=0) - selected_history--; - set_text(history[selected_history]); - set_position(-1); - } - } - return false; - }); +EntryBox::Entry::Entry(const std::string &content, std::function<void(const std::string &content)> on_activate, + unsigned width_chars) : Gtk::Entry(), on_activate(on_activate) { + set_max_length(0); + set_width_chars(width_chars); + set_text(content); + selected_history = 0; + signal_activate().connect([this]() { + if (this->on_activate) { + auto &history = EntryBox::entry_histories[get_placeholder_text()]; + auto text = get_text(); + if (history.size() == 0 || (history.size() > 0 && *history.begin() != text)) + history.emplace(history.begin(), text); + selected_history = 0; + this->on_activate(text); + } + }); + signal_key_press_event().connect([this](GdkEventKey *key) { + if (key->keyval == GDK_KEY_Up || key->keyval == GDK_KEY_KP_Up) { + auto &history = entry_histories[get_placeholder_text()]; + if (history.size() > 0) { + selected_history++; + if (selected_history >= history.size()) + selected_history = history.size() - 1; + set_text(history[selected_history]); + set_position(-1); + } + } + if (key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down) { + auto &history = entry_histories[get_placeholder_text()]; + if (history.size() > 0) { + if (selected_history != 0) + selected_history--; + set_text(history[selected_history]); + set_position(-1); + } + } + return false; + }); } -EntryBox::Button::Button(const std::string& label, std::function<void()> on_activate) : Gtk::Button(label), on_activate(on_activate) { - set_focus_on_click(false); - signal_clicked().connect([this](){ - if(this->on_activate) - this->on_activate(); - }); +EntryBox::Button::Button(const std::string &label, std::function<void()> on_activate) : Gtk::Button(label), + on_activate(on_activate) { + set_focus_on_click(false); + signal_clicked().connect([this]() { + if (this->on_activate) + this->on_activate(); + }); } -EntryBox::ToggleButton::ToggleButton(const std::string& label, std::function<void()> on_activate) : Gtk::ToggleButton(label), on_activate(on_activate) { - set_focus_on_click(false); - signal_clicked().connect([this](){ - if(this->on_activate) - this->on_activate(); - }); +EntryBox::ToggleButton::ToggleButton(const std::string &label, std::function<void()> on_activate) : Gtk::ToggleButton( + label), on_activate(on_activate) { + set_focus_on_click(false); + signal_clicked().connect([this]() { + if (this->on_activate) + this->on_activate(); + }); } -EntryBox::Label::Label(std::function<void(int state, const std::string& message)> update) : Gtk::Label(), update(update) { - if(this->update) - this->update(-1, ""); +EntryBox::Label::Label(std::function<void(int state, const std::string &message)> update) + : Gtk::Label(), update(update) { + if (this->update) + this->update(-1, ""); } -EntryBox::EntryBox() : Gtk::Box(Gtk::ORIENTATION_VERTICAL), upper_box(Gtk::ORIENTATION_HORIZONTAL), lower_box(Gtk::ORIENTATION_HORIZONTAL) { - pack_start(upper_box, Gtk::PACK_SHRINK); - pack_start(lower_box, Gtk::PACK_SHRINK); - this->set_focus_chain({&lower_box}); +EntryBox::EntryBox() : Gtk::Box(Gtk::ORIENTATION_VERTICAL), upper_box(Gtk::ORIENTATION_HORIZONTAL), + lower_box(Gtk::ORIENTATION_HORIZONTAL) { + pack_start(upper_box, Gtk::PACK_SHRINK); + pack_start(lower_box, Gtk::PACK_SHRINK); + this->set_focus_chain({&lower_box}); } void EntryBox::clear() { - Gtk::Box::hide(); - entries.clear(); - buttons.clear(); - toggle_buttons.clear(); - labels.clear(); + Gtk::Box::hide(); + entries.clear(); + buttons.clear(); + toggle_buttons.clear(); + labels.clear(); } void EntryBox::show() { - std::vector<Gtk::Widget*> focus_chain; - for(auto& entry: entries) { - lower_box.pack_start(entry, Gtk::PACK_SHRINK); - focus_chain.emplace_back(&entry); - } - for(auto& button: buttons) - lower_box.pack_start(button, Gtk::PACK_SHRINK); - for(auto& toggle_button: toggle_buttons) - lower_box.pack_start(toggle_button, Gtk::PACK_SHRINK); - for(auto& label: labels) - upper_box.pack_start(label, Gtk::PACK_SHRINK); - lower_box.set_focus_chain(focus_chain); - show_all(); - if(entries.size()>0) { - entries.begin()->grab_focus(); - entries.begin()->select_region(0, entries.begin()->get_text_length()); - } + std::vector<Gtk::Widget *> focus_chain; + for (auto &entry: entries) { + lower_box.pack_start(entry, Gtk::PACK_SHRINK); + focus_chain.emplace_back(&entry); + } + for (auto &button: buttons) + lower_box.pack_start(button, Gtk::PACK_SHRINK); + for (auto &toggle_button: toggle_buttons) + lower_box.pack_start(toggle_button, Gtk::PACK_SHRINK); + for (auto &label: labels) + upper_box.pack_start(label, Gtk::PACK_SHRINK); + lower_box.set_focus_chain(focus_chain); + show_all(); + if (entries.size() > 0) { + entries.begin()->grab_focus(); + entries.begin()->select_region(0, entries.begin()->get_text_length()); + } } diff --git a/src/entrybox.h b/src/entrybox.h index 3a722be9..107ba085 100644 --- a/src/entrybox.h +++ b/src/entrybox.h @@ -1,4 +1,5 @@ #pragma once + #include <list> #include <functional> #include "gtkmm.h" @@ -8,47 +9,60 @@ class EntryBox : public Gtk::Box { public: - class Entry : public Gtk::Entry { - public: - Entry(const std::string& content="", std::function<void(const std::string& content)> on_activate=nullptr, unsigned width_chars=-1); - std::function<void(const std::string& content)> on_activate; - private: - size_t selected_history; - }; - class Button : public Gtk::Button { - public: - Button(const std::string& label, std::function<void()> on_activate=nullptr); - std::function<void()> on_activate; - }; - class ToggleButton : public Gtk::ToggleButton { - public: - ToggleButton(const std::string& label, std::function<void()> on_activate=nullptr); - std::function<void()> on_activate; - }; - class Label : public Gtk::Label { - public: - Label(std::function<void(int state, const std::string& message)> update=nullptr); - std::function<void(int state, const std::string& message)> update; - }; - + class Entry : public Gtk::Entry { + public: + Entry(const std::string &content = "", std::function<void(const std::string &content)> on_activate = nullptr, + unsigned width_chars = -1); + + std::function<void(const std::string &content)> on_activate; + private: + size_t selected_history; + }; + + class Button : public Gtk::Button { + public: + Button(const std::string &label, std::function<void()> on_activate = nullptr); + + std::function<void()> on_activate; + }; + + class ToggleButton : public Gtk::ToggleButton { + public: + ToggleButton(const std::string &label, std::function<void()> on_activate = nullptr); + + std::function<void()> on_activate; + }; + + class Label : public Gtk::Label { + public: + Label(std::function<void(int state, const std::string &message)> update = nullptr); + + std::function<void(int state, const std::string &message)> update; + }; + private: - EntryBox(); + EntryBox(); + public: - static EntryBox &get() { - static EntryBox singleton; - return singleton; - } - - Gtk::Box upper_box; - Gtk::Box lower_box; - void clear(); - void hide() {clear();} - void show(); - std::list<Entry> entries; - std::list<Button> buttons; - std::list<ToggleButton> toggle_buttons; - std::list<Label> labels; - + static EntryBox &get() { + static EntryBox singleton; + return singleton; + } + + Gtk::Box upper_box; + Gtk::Box lower_box; + + void clear(); + + void hide() { clear(); } + + void show(); + + std::list<Entry> entries; + std::list<Button> buttons; + std::list<ToggleButton> toggle_buttons; + std::list<Label> labels; + private: - static std::unordered_map<std::string, std::vector<std::string> > entry_histories; + static std::unordered_map<std::string, std::vector<std::string> > entry_histories; }; diff --git a/src/files.h b/src/files.h index a98215f6..d72bbcf8 100644 --- a/src/files.h +++ b/src/files.h @@ -1,11 +1,12 @@ #pragma once + #include <string> /// If you add or remove nodes from the default_config_file, increase the juci /// version number (JUCI_VERSION) in ../CMakeLists.txt to automatically apply /// the changes to user's ~/.juci/config/config.json files const std::string default_config_file = R"RAW({ - "version": ")RAW"+std::string(JUCI_VERSION)+R"RAW(", + "version": ")RAW" + std::string(JUCI_VERSION) + R"RAW(", "gtk_theme": { "name_comment": "Use \"\" for default theme, At least these two exist on all systems: Adwaita, Raleigh", "name": "", @@ -16,19 +17,19 @@ const std::string default_config_file = R"RAW({ "style_comment": "Use \"\" for default style, and for instance juci-dark or juci-dark-blue together with dark gtk_theme variant. Styles from normal gtksourceview install: classic, cobalt, kate, oblivion, solarized-dark, solarized-light, tango", "style": "juci-light", "font_comment": "Use \"\" for default font, and for instance \"Monospace 12\" to also set size",)RAW" -#ifdef __APPLE__ -R"RAW( + #ifdef __APPLE__ + R"RAW( "font": "Menlo",)RAW" -#else -#ifdef _WIN32 -R"RAW( + #else + #ifdef _WIN32 + R"RAW( "font": "Consolas",)RAW" -#else -R"RAW( + #else + R"RAW( "font": "Monospace",)RAW" -#endif -#endif -R"RAW( + #endif + #endif + R"RAW( "cleanup_whitespace_characters_comment": "Remove trailing whitespace characters on save, and add trailing newline if missing", "cleanup_whitespace_characters": false, "show_whitespace_characters_comment": "Determines what kind of whitespaces should be drawn. Use comma-separated list of: space, tab, newline, nbsp, leading, text, trailing or all", @@ -132,26 +133,26 @@ R"RAW( "debug_run_command": "<alt><shift>Return", "debug_toggle_breakpoint": "<primary>b", "debug_goto_stop": "<primary><shift>l",)RAW" -#ifdef __linux -R"RAW( + #ifdef __linux + R"RAW( "window_next_tab": "<primary>Tab", "window_previous_tab": "<primary><shift>Tab",)RAW" -#else -R"RAW( + #else + R"RAW( "window_next_tab": "<primary><alt>Right", "window_previous_tab": "<primary><alt>Left",)RAW" -#endif -R"RAW( + #endif + R"RAW( "window_close_tab": "<primary>w", "window_toggle_split": "",)RAW" -#ifdef __APPLE__ -R"RAW( + #ifdef __APPLE__ + R"RAW( "window_toggle_full_screen": "<primary><control>f",)RAW" -#else -R"RAW( + #else + R"RAW( "window_toggle_full_screen": "F11",)RAW" -#endif -R"RAW( + #endif + R"RAW( "window_toggle_tabs": "", "window_clear_terminal": "" }, @@ -161,14 +162,14 @@ R"RAW( "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.", "debug_build_path": "<default_build_path>/debug", "cmake": {)RAW" -#ifdef _WIN32 -R"RAW( + #ifdef _WIN32 + R"RAW( "command": "cmake -G\"MSYS Makefiles\"",)RAW" -#else -R"RAW( + #else + R"RAW( "command": "cmake",)RAW" -#endif -R"RAW( + #endif + R"RAW( "compile_command": "cmake --build ." }, "meson": { diff --git a/src/filesystem.cc b/src/filesystem.cc index d8b8505e..a7289137 100644 --- a/src/filesystem.cc +++ b/src/filesystem.cc @@ -7,240 +7,243 @@ //Only use on small files std::string filesystem::read(const std::string &path) { - std::stringstream ss; - std::ifstream input(path, std::ofstream::binary); - if(input) { - ss << input.rdbuf(); - input.close(); - } - return ss.str(); + std::stringstream ss; + std::ifstream input(path, std::ofstream::binary); + if (input) { + ss << input.rdbuf(); + input.close(); + } + return ss.str(); } //Only use on small files std::vector<std::string> filesystem::read_lines(const std::string &path) { - std::vector<std::string> res; - std::ifstream input(path, std::ofstream::binary); - if (input) { - do { res.emplace_back(); } while(getline(input, res.back())); - } - input.close(); - return res; + std::vector<std::string> res; + std::ifstream input(path, std::ofstream::binary); + if (input) { + do { res.emplace_back(); } while (getline(input, res.back())); + } + input.close(); + return res; } //Only use on small files bool filesystem::write(const std::string &path, const std::string &new_content) { - std::ofstream output(path, std::ofstream::binary); - if(output) - output << new_content; - else - return false; - output.close(); - return true; + std::ofstream output(path, std::ofstream::binary); + if (output) + output << new_content; + else + return false; + output.close(); + return true; } std::string filesystem::escape_argument(const std::string &argument) { - auto escaped=argument; - for(size_t pos=0;pos<escaped.size();++pos) { - if(escaped[pos]==' ' || escaped[pos]=='(' || escaped[pos]==')' || escaped[pos]=='\'' || escaped[pos]=='"') { - escaped.insert(pos, "\\"); - ++pos; + auto escaped = argument; + for (size_t pos = 0; pos < escaped.size(); ++pos) { + if (escaped[pos] == ' ' || escaped[pos] == '(' || escaped[pos] == ')' || escaped[pos] == '\'' || + escaped[pos] == '"') { + escaped.insert(pos, "\\"); + ++pos; + } } - } - return escaped; + return escaped; } std::string filesystem::unescape_argument(const std::string &argument) { - auto unescaped=argument; - - if(unescaped.size()>=2) { - if((unescaped[0]=='\'' && unescaped[unescaped.size()-1]=='\'') || - (unescaped[0]=='"' && unescaped[unescaped.size()-1]=='"')) { - char quotation_mark=unescaped[0]; - unescaped=unescaped.substr(1, unescaped.size()-2); - size_t backslash_count=0; - for(size_t pos=0;pos<unescaped.size();++pos) { - if(backslash_count%2==1 && (unescaped[pos]=='\\' || unescaped[pos]==quotation_mark)) { - unescaped.erase(pos-1, 1); - --pos; - backslash_count=0; + auto unescaped = argument; + + if (unescaped.size() >= 2) { + if ((unescaped[0] == '\'' && unescaped[unescaped.size() - 1] == '\'') || + (unescaped[0] == '"' && unescaped[unescaped.size() - 1] == '"')) { + char quotation_mark = unescaped[0]; + unescaped = unescaped.substr(1, unescaped.size() - 2); + size_t backslash_count = 0; + for (size_t pos = 0; pos < unescaped.size(); ++pos) { + if (backslash_count % 2 == 1 && (unescaped[pos] == '\\' || unescaped[pos] == quotation_mark)) { + unescaped.erase(pos - 1, 1); + --pos; + backslash_count = 0; + } else if (unescaped[pos] == '\\') + ++backslash_count; + else + backslash_count = 0; + } + return unescaped; } - else if(unescaped[pos]=='\\') - ++backslash_count; - else - backslash_count=0; - } - return unescaped; } - } - - size_t backslash_count=0; - for(size_t pos=0;pos<unescaped.size();++pos) { - if(backslash_count%2==1 && (unescaped[pos]=='\\' || unescaped[pos]==' ' || unescaped[pos]=='(' || unescaped[pos]==')' || unescaped[pos]=='\'' || unescaped[pos]=='"')) { - unescaped.erase(pos-1, 1); - --pos; - backslash_count=0; + + size_t backslash_count = 0; + for (size_t pos = 0; pos < unescaped.size(); ++pos) { + if (backslash_count % 2 == 1 && + (unescaped[pos] == '\\' || unescaped[pos] == ' ' || unescaped[pos] == '(' || unescaped[pos] == ')' || + unescaped[pos] == '\'' || unescaped[pos] == '"')) { + unescaped.erase(pos - 1, 1); + --pos; + backslash_count = 0; + } else if (unescaped[pos] == '\\') + ++backslash_count; + else + backslash_count = 0; } - else if(unescaped[pos]=='\\') - ++backslash_count; - else - backslash_count=0; - } - return unescaped; + return unescaped; } boost::filesystem::path filesystem::get_home_path() noexcept { - std::vector<std::string> environment_variables = {"HOME", "AppData"}; - char *ptr = nullptr; - for (auto &variable : environment_variables) { - ptr=std::getenv(variable.c_str()); - boost::system::error_code ec; - if (ptr!=nullptr && boost::filesystem::exists(ptr, ec)) - return ptr; - } - return boost::filesystem::path(); + std::vector<std::string> environment_variables = {"HOME", "AppData"}; + char *ptr = nullptr; + for (auto &variable : environment_variables) { + ptr = std::getenv(variable.c_str()); + boost::system::error_code ec; + if (ptr != nullptr && boost::filesystem::exists(ptr, ec)) + return ptr; + } + return boost::filesystem::path(); } boost::filesystem::path filesystem::get_short_path(const boost::filesystem::path &path) noexcept { #ifdef _WIN32 - return path; + return path; #else - static auto home_path=get_home_path(); - if(!home_path.empty()) { - auto relative_path=filesystem::get_relative_path(path, home_path); - if(!relative_path.empty()) - return "~"/relative_path; - } - return path; + static auto home_path=get_home_path(); + if(!home_path.empty()) { + auto relative_path=filesystem::get_relative_path(path, home_path); + if(!relative_path.empty()) + return "~"/relative_path; + } + return path; #endif } bool filesystem::file_in_path(const boost::filesystem::path &file_path, const boost::filesystem::path &path) { - if(std::distance(file_path.begin(), file_path.end())<std::distance(path.begin(), path.end())) - return false; - return std::equal(path.begin(), path.end(), file_path.begin()); + if (std::distance(file_path.begin(), file_path.end()) < std::distance(path.begin(), path.end())) + return false; + return std::equal(path.begin(), path.end(), file_path.begin()); } -boost::filesystem::path filesystem::find_file_in_path_parents(const std::string &file_name, const boost::filesystem::path &path) { - auto current_path=path; - while(true) { - auto test_path=current_path/file_name; - if(boost::filesystem::exists(test_path)) - return test_path; - if(current_path==current_path.root_directory()) - return boost::filesystem::path(); - current_path=current_path.parent_path(); - } +boost::filesystem::path +filesystem::find_file_in_path_parents(const std::string &file_name, const boost::filesystem::path &path) { + auto current_path = path; + while (true) { + auto test_path = current_path / file_name; + if (boost::filesystem::exists(test_path)) + return test_path; + if (current_path == current_path.root_directory()) + return boost::filesystem::path(); + current_path = current_path.parent_path(); + } } boost::filesystem::path filesystem::get_normal_path(const boost::filesystem::path &path) noexcept { - boost::filesystem::path normal_path; - - for(auto &e: path) { - if(e==".") - continue; - else if(e=="..") { - auto parent_path=normal_path.parent_path(); - if(!parent_path.empty()) - normal_path=parent_path; - else - normal_path/=e; + boost::filesystem::path normal_path; + + for (auto &e: path) { + if (e == ".") + continue; + else if (e == "..") { + auto parent_path = normal_path.parent_path(); + if (!parent_path.empty()) + normal_path = parent_path; + else + normal_path /= e; + } else if (e.empty()) + continue; + else + normal_path /= e; } - else if(e.empty()) - continue; - else - normal_path/=e; - } - - return normal_path; + + return normal_path; } -boost::filesystem::path filesystem::get_relative_path(const boost::filesystem::path &path, const boost::filesystem::path &base) noexcept { - boost::filesystem::path relative_path; +boost::filesystem::path +filesystem::get_relative_path(const boost::filesystem::path &path, const boost::filesystem::path &base) noexcept { + boost::filesystem::path relative_path; - if(std::distance(path.begin(), path.end())<std::distance(base.begin(), base.end())) - return boost::filesystem::path(); - - auto base_it=base.begin(); - auto path_it=path.begin(); - while(path_it!=path.end() && base_it!=base.end()) { - if(*path_it!=*base_it) - return boost::filesystem::path(); - ++path_it; - ++base_it; - } - for(;path_it!=path.end();++path_it) - relative_path/=*path_it; - - return relative_path; + if (std::distance(path.begin(), path.end()) < std::distance(base.begin(), base.end())) + return boost::filesystem::path(); + + auto base_it = base.begin(); + auto path_it = path.begin(); + while (path_it != path.end() && base_it != base.end()) { + if (*path_it != *base_it) + return boost::filesystem::path(); + ++path_it; + ++base_it; + } + for (; path_it != path.end(); ++path_it) + relative_path /= *path_it; + + return relative_path; } boost::filesystem::path filesystem::get_executable(const boost::filesystem::path &executable_name) noexcept { #if defined(__APPLE__) || defined(_WIN32) - return executable_name; + return executable_name; #endif - static std::vector<boost::filesystem::path> bin_paths={"/usr/bin", "/usr/local/bin"}; - - try { - for(auto &path: bin_paths) { - if(boost::filesystem::exists(path/executable_name)) - return executable_name; - } - - auto executable_name_str = executable_name.string(); - for(auto &path: bin_paths) { - boost::filesystem::path executable; - for(boost::filesystem::directory_iterator it(path), end; it != end; ++it) { - auto it_path = it->path(); - auto it_path_filename_str = it_path.filename().string(); - if(!it_path_filename_str.empty() && it_path_filename_str.compare(0, executable_name_str.size(), executable_name_str)==0) { - if(it_path > executable && - ((it_path_filename_str.size() > executable_name_str.size() && - it_path_filename_str[executable_name_str.size()]>='0' && - it_path_filename_str[executable_name_str.size()]<='9') || - (it_path_filename_str.size() > executable_name_str.size()+1 && - it_path_filename_str[executable_name_str.size()]=='-' && - it_path_filename_str[executable_name_str.size()+1]>='0' && - it_path_filename_str[executable_name_str.size()+1]<='9')) && - !boost::filesystem::is_directory(it_path)) - executable=it_path; + static std::vector<boost::filesystem::path> bin_paths = {"/usr/bin", "/usr/local/bin"}; + + try { + for (auto &path: bin_paths) { + if (boost::filesystem::exists(path / executable_name)) + return executable_name; + } + + auto executable_name_str = executable_name.string(); + for (auto &path: bin_paths) { + boost::filesystem::path executable; + for (boost::filesystem::directory_iterator it(path), end; it != end; ++it) { + auto it_path = it->path(); + auto it_path_filename_str = it_path.filename().string(); + if (!it_path_filename_str.empty() && + it_path_filename_str.compare(0, executable_name_str.size(), executable_name_str) == 0) { + if (it_path > executable && + ((it_path_filename_str.size() > executable_name_str.size() && + it_path_filename_str[executable_name_str.size()] >= '0' && + it_path_filename_str[executable_name_str.size()] <= '9') || + (it_path_filename_str.size() > executable_name_str.size() + 1 && + it_path_filename_str[executable_name_str.size()] == '-' && + it_path_filename_str[executable_name_str.size() + 1] >= '0' && + it_path_filename_str[executable_name_str.size() + 1] <= '9')) && + !boost::filesystem::is_directory(it_path)) + executable = it_path; + } + } + if (!executable.empty()) + return executable; } - } - if(!executable.empty()) - return executable; } - } - catch(...) {} - - return executable_name; + catch (...) {} + + return executable_name; } // Based on https://stackoverflow.com/a/11295568 const std::vector<boost::filesystem::path> &filesystem::get_executable_search_paths() { - static std::vector<boost::filesystem::path> result; - if(!result.empty()) - return result; - - const std::string env = getenv("PATH"); - const char delimiter = ':'; - - size_t previous = 0; - size_t pos; - while((pos = env.find(delimiter, previous)) != std::string::npos) { - result.emplace_back(env.substr(previous, pos - previous)); - previous = pos + 1; - } - result.emplace_back(env.substr(previous)); + static std::vector<boost::filesystem::path> result; + if (!result.empty()) + return result; + + const std::string env = getenv("PATH"); + const char delimiter = ':'; + + size_t previous = 0; + size_t pos; + while ((pos = env.find(delimiter, previous)) != std::string::npos) { + result.emplace_back(env.substr(previous, pos - previous)); + previous = pos + 1; + } + result.emplace_back(env.substr(previous)); - return result; + return result; } boost::filesystem::path filesystem::find_executable(const std::string &executable_name) { - for(auto &path: get_executable_search_paths()) { - boost::system::error_code ec; - auto executable_path=path/executable_name; - if(boost::filesystem::exists(executable_path, ec)) - return executable_path; - } - return boost::filesystem::path(); + for (auto &path: get_executable_search_paths()) { + boost::system::error_code ec; + auto executable_path = path / executable_name; + if (boost::filesystem::exists(executable_path, ec)) + return executable_path; + } + return boost::filesystem::path(); } diff --git a/src/filesystem.h b/src/filesystem.h index 11121283..e32a18a0 100644 --- a/src/filesystem.h +++ b/src/filesystem.h @@ -1,40 +1,55 @@ #pragma once + #include <vector> #include <string> #include <boost/filesystem.hpp> class filesystem { public: - static std::string read(const std::string &path); - static std::string read(const boost::filesystem::path &path) { return read(path.string()); } - - static std::vector<std::string> read_lines(const std::string &path); - static std::vector<std::string> read_lines(const boost::filesystem::path &path) { return read_lines(path.string()); }; - - static bool write(const std::string &path, const std::string &new_content); - static bool write(const boost::filesystem::path &path, const std::string &new_content) { return write(path.string(), new_content); } - static bool write(const std::string &path) { return write(path, ""); }; - static bool write(const boost::filesystem::path &path) { return write(path, ""); }; - - static std::string escape_argument(const std::string &argument); - static std::string unescape_argument(const std::string &argument); - - static boost::filesystem::path get_home_path() noexcept; - static boost::filesystem::path get_short_path(const boost::filesystem::path &path) noexcept; - - static bool file_in_path(const boost::filesystem::path &file_path, const boost::filesystem::path &path); - static boost::filesystem::path find_file_in_path_parents(const std::string &file_name, const boost::filesystem::path &path); - - /// Return path with dot, dot-dot and directory separator elements removed - static boost::filesystem::path get_normal_path(const boost::filesystem::path &path) noexcept; - - /// Returns empty path on failure - static boost::filesystem::path get_relative_path(const boost::filesystem::path &path, const boost::filesystem::path &base) noexcept; - - /// Return executable with latest version in filename on systems that is lacking executable_name symbolic link - static boost::filesystem::path get_executable(const boost::filesystem::path &executable_name) noexcept; - - static const std::vector<boost::filesystem::path> &get_executable_search_paths(); - - static boost::filesystem::path find_executable(const std::string &executable_name); + static std::string read(const std::string &path); + + static std::string read(const boost::filesystem::path &path) { return read(path.string()); } + + static std::vector<std::string> read_lines(const std::string &path); + + static std::vector<std::string> read_lines(const boost::filesystem::path &path) { + return read_lines(path.string()); + }; + + static bool write(const std::string &path, const std::string &new_content); + + static bool write(const boost::filesystem::path &path, const std::string &new_content) { + return write(path.string(), new_content); + } + + static bool write(const std::string &path) { return write(path, ""); }; + + static bool write(const boost::filesystem::path &path) { return write(path, ""); }; + + static std::string escape_argument(const std::string &argument); + + static std::string unescape_argument(const std::string &argument); + + static boost::filesystem::path get_home_path() noexcept; + + static boost::filesystem::path get_short_path(const boost::filesystem::path &path) noexcept; + + static bool file_in_path(const boost::filesystem::path &file_path, const boost::filesystem::path &path); + + static boost::filesystem::path + find_file_in_path_parents(const std::string &file_name, const boost::filesystem::path &path); + + /// Return path with dot, dot-dot and directory separator elements removed + static boost::filesystem::path get_normal_path(const boost::filesystem::path &path) noexcept; + + /// Returns empty path on failure + static boost::filesystem::path + get_relative_path(const boost::filesystem::path &path, const boost::filesystem::path &base) noexcept; + + /// Return executable with latest version in filename on systems that is lacking executable_name symbolic link + static boost::filesystem::path get_executable(const boost::filesystem::path &executable_name) noexcept; + + static const std::vector<boost::filesystem::path> &get_executable_search_paths(); + + static boost::filesystem::path find_executable(const std::string &executable_name); }; diff --git a/src/git.cc b/src/git.cc index 0a1d1198..100ad9a1 100644 --- a/src/git.cc +++ b/src/git.cc @@ -1,300 +1,317 @@ #include "git.h" #include <cstring> -bool Git::initialized=false; +bool Git::initialized = false; std::mutex Git::mutex; std::string Git::Error::message() noexcept { - const git_error *last_error = giterr_last(); - if(last_error==nullptr) - return std::string(); - else - return last_error->message; + const git_error *last_error = giterr_last(); + if (last_error == nullptr) + return std::string(); + else + return last_error->message; } Git::Repository::Diff::Diff(const boost::filesystem::path &path, git_repository *repository) : repository(repository) { - blob=std::shared_ptr<git_blob>(nullptr, [](git_blob *blob) { - if(blob) git_blob_free(blob); - }); - Error error; - std::lock_guard<std::mutex> lock(mutex); - auto spec="HEAD:"+path.generic_string(); - error.code = git_revparse_single(reinterpret_cast<git_object**>(&blob), repository, spec.c_str()); - if(error) - throw std::runtime_error(error.message()); - - git_diff_init_options(&options, GIT_DIFF_OPTIONS_VERSION); - options.context_lines=0; + blob = std::shared_ptr<git_blob>(nullptr, [](git_blob *blob) { + if (blob) git_blob_free(blob); + }); + Error error; + std::lock_guard<std::mutex> lock(mutex); + auto spec = "HEAD:" + path.generic_string(); + error.code = git_revparse_single(reinterpret_cast<git_object **>(&blob), repository, spec.c_str()); + if (error) + throw std::runtime_error(error.message()); + + git_diff_init_options(&options, GIT_DIFF_OPTIONS_VERSION); + options.context_lines = 0; } //Based on https://github.com/atom/git-diff/blob/master/lib/git-diff-view.coffee int Git::Repository::Diff::hunk_cb(const git_diff_delta *delta, const git_diff_hunk *hunk, void *payload) noexcept { - auto lines=static_cast<Lines*>(payload); - auto start=hunk->new_start-1; - auto end=hunk->new_start+hunk->new_lines-1; - if(hunk->old_lines==0 && hunk->new_lines>0) - lines->added.emplace_back(start, end); - else if(hunk->new_lines==0 && hunk->old_lines>0) - lines->removed.emplace_back(start); - else - lines->modified.emplace_back(start, end); - - return 0; + auto lines = static_cast<Lines *>(payload); + auto start = hunk->new_start - 1; + auto end = hunk->new_start + hunk->new_lines - 1; + if (hunk->old_lines == 0 && hunk->new_lines > 0) + lines->added.emplace_back(start, end); + else if (hunk->new_lines == 0 && hunk->old_lines > 0) + lines->removed.emplace_back(start); + else + lines->modified.emplace_back(start, end); + + return 0; } Git::Repository::Diff::Lines Git::Repository::Diff::get_lines(const std::string &buffer) { - Lines lines; - Error error; - std::lock_guard<std::mutex> lock(mutex); - error.code=git_diff_blob_to_buffer(blob.get(), nullptr, buffer.c_str(), buffer.size(), nullptr, &options, nullptr, nullptr, hunk_cb, nullptr, &lines); - if(error) - throw std::runtime_error(error.message()); - return lines; + Lines lines; + Error error; + std::lock_guard<std::mutex> lock(mutex); + error.code = git_diff_blob_to_buffer(blob.get(), nullptr, buffer.c_str(), buffer.size(), nullptr, &options, nullptr, + nullptr, hunk_cb, nullptr, &lines); + if (error) + throw std::runtime_error(error.message()); + return lines; } -std::vector<Git::Repository::Diff::Hunk> Git::Repository::Diff::get_hunks(const std::string &old_buffer, const std::string &new_buffer) { - std::vector<Git::Repository::Diff::Hunk> hunks; - Error error; - git_diff_options options; - git_diff_init_options(&options, GIT_DIFF_OPTIONS_VERSION); - options.context_lines=0; - error.code=git_diff_buffers(old_buffer.c_str(), old_buffer.size(), nullptr, new_buffer.c_str(), new_buffer.size(), nullptr, &options, nullptr, nullptr, - [](const git_diff_delta *delta, const git_diff_hunk *hunk, void *payload) { - auto hunks=static_cast<std::vector<Git::Repository::Diff::Hunk>*>(payload); - hunks->emplace_back(hunk->old_start, hunk->old_lines, hunk->new_start, hunk->new_lines); - return 0; - }, nullptr, &hunks); - if(error) - throw std::runtime_error(error.message()); - return hunks; +std::vector<Git::Repository::Diff::Hunk> +Git::Repository::Diff::get_hunks(const std::string &old_buffer, const std::string &new_buffer) { + std::vector<Git::Repository::Diff::Hunk> hunks; + Error error; + git_diff_options options; + git_diff_init_options(&options, GIT_DIFF_OPTIONS_VERSION); + options.context_lines = 0; + error.code = git_diff_buffers(old_buffer.c_str(), old_buffer.size(), nullptr, new_buffer.c_str(), new_buffer.size(), + nullptr, &options, nullptr, nullptr, + [](const git_diff_delta *delta, const git_diff_hunk *hunk, void *payload) { + auto hunks = static_cast<std::vector<Git::Repository::Diff::Hunk> *>(payload); + hunks->emplace_back(hunk->old_start, hunk->old_lines, hunk->new_start, + hunk->new_lines); + return 0; + }, nullptr, &hunks); + if (error) + throw std::runtime_error(error.message()); + return hunks; } -int Git::Repository::Diff::line_cb(const git_diff_delta *delta, const git_diff_hunk *hunk, const git_diff_line *line, void *payload) noexcept { - auto details=static_cast<std::pair<std::string, int> *>(payload); - auto line_nr=details->second; - auto start=hunk->new_start-1; - auto end=hunk->new_start+hunk->new_lines-1; - if(line_nr==start || (line_nr>=start && line_nr<end)) { - if(details->first.empty()) - details->first+=std::string(hunk->header, hunk->header_len); - details->first+=line->origin+std::string(line->content, line->content_len); - } - return 0; +int Git::Repository::Diff::line_cb(const git_diff_delta *delta, const git_diff_hunk *hunk, const git_diff_line *line, + void *payload) noexcept { + auto details = static_cast<std::pair<std::string, int> *>(payload); + auto line_nr = details->second; + auto start = hunk->new_start - 1; + auto end = hunk->new_start + hunk->new_lines - 1; + if (line_nr == start || (line_nr >= start && line_nr < end)) { + if (details->first.empty()) + details->first += std::string(hunk->header, hunk->header_len); + details->first += line->origin + std::string(line->content, line->content_len); + } + return 0; } std::string Git::Repository::Diff::get_details(const std::string &buffer, int line_nr) { - std::pair<std::string, int> details; - details.second=line_nr; - Error error; - std::lock_guard<std::mutex> lock(mutex); - error.code=git_diff_blob_to_buffer(blob.get(), nullptr, buffer.c_str(), buffer.size(), nullptr, &options, nullptr, nullptr, nullptr, line_cb, &details); - if(error) - throw std::runtime_error(error.message()); - return details.first; + std::pair<std::string, int> details; + details.second = line_nr; + Error error; + std::lock_guard<std::mutex> lock(mutex); + error.code = git_diff_blob_to_buffer(blob.get(), nullptr, buffer.c_str(), buffer.size(), nullptr, &options, nullptr, + nullptr, nullptr, line_cb, &details); + if (error) + throw std::runtime_error(error.message()); + return details.first; } Git::Repository::Repository(const boost::filesystem::path &path) { - git_repository *repository_ptr; - { - Error error; - std::lock_guard<std::mutex> lock(mutex); - auto path_str=path.generic_string(); - error.code = git_repository_open_ext(&repository_ptr, path_str.c_str(), 0, nullptr); - if(error) - throw std::runtime_error(error.message()); - } - repository=std::unique_ptr<git_repository, std::function<void(git_repository *)> >(repository_ptr, [](git_repository *ptr) { - git_repository_free(ptr); - }); - - work_path=get_work_path(); - if(work_path.empty()) - throw std::runtime_error("Could not find work path"); - - auto git_directory=Gio::File::create_for_path(get_path().string()); - monitor=git_directory->monitor_directory(Gio::FileMonitorFlags::FILE_MONITOR_WATCH_MOVES); - monitor_changed_connection=monitor->signal_changed().connect([this](const Glib::RefPtr<Gio::File> &file, - const Glib::RefPtr<Gio::File>&, - Gio::FileMonitorEvent monitor_event) { - if(monitor_event!=Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { - this->clear_saved_status(); + git_repository *repository_ptr; + { + Error error; + std::lock_guard<std::mutex> lock(mutex); + auto path_str = path.generic_string(); + error.code = git_repository_open_ext(&repository_ptr, path_str.c_str(), 0, nullptr); + if (error) + throw std::runtime_error(error.message()); } - }, false); + repository = std::unique_ptr<git_repository, std::function<void(git_repository *)> >(repository_ptr, + [](git_repository *ptr) { + git_repository_free(ptr); + }); + + work_path = get_work_path(); + if (work_path.empty()) + throw std::runtime_error("Could not find work path"); + + auto git_directory = Gio::File::create_for_path(get_path().string()); + monitor = git_directory->monitor_directory(Gio::FileMonitorFlags::FILE_MONITOR_WATCH_MOVES); + monitor_changed_connection = monitor->signal_changed().connect([this](const Glib::RefPtr<Gio::File> &file, + const Glib::RefPtr<Gio::File> &, + Gio::FileMonitorEvent monitor_event) { + if (monitor_event != Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { + this->clear_saved_status(); + } + }, false); } Git::Repository::~Repository() { - monitor_changed_connection.disconnect(); + monitor_changed_connection.disconnect(); } std::string Git::Repository::status_string(STATUS status) noexcept { - switch(status) { - case STATUS::CURRENT: return "current"; - case STATUS::NEW: return "new"; - case STATUS::MODIFIED: return "modified"; - case STATUS::DELETED: return "deleted"; - case STATUS::RENAMED: return "renamed"; - case STATUS::TYPECHANGE: return "typechange"; - case STATUS::UNREADABLE: return "unreadable"; - case STATUS::IGNORED: return "ignored"; - case STATUS::CONFLICTED: return "conflicted"; - default: return ""; - } + switch (status) { + case STATUS::CURRENT: + return "current"; + case STATUS::NEW: + return "new"; + case STATUS::MODIFIED: + return "modified"; + case STATUS::DELETED: + return "deleted"; + case STATUS::RENAMED: + return "renamed"; + case STATUS::TYPECHANGE: + return "typechange"; + case STATUS::UNREADABLE: + return "unreadable"; + case STATUS::IGNORED: + return "ignored"; + case STATUS::CONFLICTED: + return "conflicted"; + default: + return ""; + } } int Git::Repository::status_callback(const char *path, unsigned int status_flags, void *data) noexcept { - auto callback=static_cast<std::function<void(const char *path, STATUS status)>*>(data); - - STATUS status; - if((status_flags&(GIT_STATUS_INDEX_NEW|GIT_STATUS_WT_NEW))>0) - status=STATUS::NEW; - else if((status_flags&(GIT_STATUS_INDEX_MODIFIED|GIT_STATUS_WT_MODIFIED))>0) - status=STATUS::MODIFIED; - else if((status_flags&(GIT_STATUS_INDEX_DELETED|GIT_STATUS_WT_DELETED))>0) - status=STATUS::DELETED; - else if((status_flags&(GIT_STATUS_INDEX_RENAMED|GIT_STATUS_WT_RENAMED))>0) - status=STATUS::RENAMED; - else if((status_flags&(GIT_STATUS_INDEX_TYPECHANGE|GIT_STATUS_WT_TYPECHANGE))>0) - status=STATUS::TYPECHANGE; - else if((status_flags&(GIT_STATUS_WT_UNREADABLE))>0) - status=STATUS::UNREADABLE; - else if((status_flags&(GIT_STATUS_IGNORED))>0) - status=STATUS::IGNORED; - else if((status_flags&(GIT_STATUS_CONFLICTED))>0) - status=STATUS::CONFLICTED; - else - status=STATUS::CURRENT; - - if(*callback) - (*callback)(path, status); - - return 0; + auto callback = static_cast<std::function<void(const char *path, STATUS status)> *>(data); + + STATUS status; + if ((status_flags & (GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_NEW)) > 0) + status = STATUS::NEW; + else if ((status_flags & (GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED)) > 0) + status = STATUS::MODIFIED; + else if ((status_flags & (GIT_STATUS_INDEX_DELETED | GIT_STATUS_WT_DELETED)) > 0) + status = STATUS::DELETED; + else if ((status_flags & (GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED)) > 0) + status = STATUS::RENAMED; + else if ((status_flags & (GIT_STATUS_INDEX_TYPECHANGE | GIT_STATUS_WT_TYPECHANGE)) > 0) + status = STATUS::TYPECHANGE; + else if ((status_flags & (GIT_STATUS_WT_UNREADABLE)) > 0) + status = STATUS::UNREADABLE; + else if ((status_flags & (GIT_STATUS_IGNORED)) > 0) + status = STATUS::IGNORED; + else if ((status_flags & (GIT_STATUS_CONFLICTED)) > 0) + status = STATUS::CONFLICTED; + else + status = STATUS::CURRENT; + + if (*callback) + (*callback)(path, status); + + return 0; } Git::Repository::Status Git::Repository::get_status() { - { - std::unique_lock<std::mutex> lock(saved_status_mutex); - if(has_saved_status) - return saved_status; - } - - Status status; - bool first=true; - std::unique_lock<std::mutex> status_saved_lock(saved_status_mutex, std::defer_lock); - std::function<void(const char *path, STATUS status)> callback=[this, &status, &first, &status_saved_lock](const char *path_cstr, STATUS status_enum) { - if(first) { - status_saved_lock.lock(); - first=false; + { + std::unique_lock<std::mutex> lock(saved_status_mutex); + if (has_saved_status) + return saved_status; } - boost::filesystem::path rel_path(path_cstr); - do { - if(status_enum==STATUS::MODIFIED) - status.modified.emplace((work_path/rel_path).generic_string()); - if(status_enum==STATUS::NEW) - status.added.emplace((work_path/rel_path).generic_string()); - rel_path=rel_path.parent_path(); - } while(!rel_path.empty()); - }; - Error error; - std::lock_guard<std::mutex> lock(mutex); - error.code = git_status_foreach(repository.get(), status_callback, &callback); - if(error) - throw std::runtime_error(error.message()); - saved_status=status; - has_saved_status=true; - if(status_saved_lock) - status_saved_lock.unlock(); - return status; + + Status status; + bool first = true; + std::unique_lock<std::mutex> status_saved_lock(saved_status_mutex, std::defer_lock); + std::function<void(const char *path, STATUS status)> callback = [this, &status, &first, &status_saved_lock]( + const char *path_cstr, STATUS status_enum) { + if (first) { + status_saved_lock.lock(); + first = false; + } + boost::filesystem::path rel_path(path_cstr); + do { + if (status_enum == STATUS::MODIFIED) + status.modified.emplace((work_path / rel_path).generic_string()); + if (status_enum == STATUS::NEW) + status.added.emplace((work_path / rel_path).generic_string()); + rel_path = rel_path.parent_path(); + } while (!rel_path.empty()); + }; + Error error; + std::lock_guard<std::mutex> lock(mutex); + error.code = git_status_foreach(repository.get(), status_callback, &callback); + if (error) + throw std::runtime_error(error.message()); + saved_status = status; + has_saved_status = true; + if (status_saved_lock) + status_saved_lock.unlock(); + return status; } void Git::Repository::clear_saved_status() { - std::unique_lock<std::mutex> lock(saved_status_mutex); - saved_status.added.clear(); - saved_status.modified.clear(); - has_saved_status=false; + std::unique_lock<std::mutex> lock(saved_status_mutex); + saved_status.added.clear(); + saved_status.modified.clear(); + has_saved_status = false; } boost::filesystem::path Git::Repository::get_work_path() noexcept { - std::lock_guard<std::mutex> lock(mutex); - return Git::path(git_repository_workdir(repository.get())); + std::lock_guard<std::mutex> lock(mutex); + return Git::path(git_repository_workdir(repository.get())); } boost::filesystem::path Git::Repository::get_path() noexcept { - std::lock_guard<std::mutex> lock(mutex); - return Git::path(git_repository_path(repository.get())); + std::lock_guard<std::mutex> lock(mutex); + return Git::path(git_repository_path(repository.get())); } boost::filesystem::path Git::Repository::get_root_path(const boost::filesystem::path &path) { - initialize(); - git_buf root = {0, 0, 0}; - { - Error error; - std::lock_guard<std::mutex> lock(mutex); - auto path_str=path.generic_string(); - error.code = git_repository_discover(&root, path_str.c_str(), 0, nullptr); - if(error) - throw std::runtime_error(error.message()); - } - auto root_path=Git::path(root.ptr, root.size); - git_buf_free(&root); - return root_path; + initialize(); + git_buf root = {0, 0, 0}; + { + Error error; + std::lock_guard<std::mutex> lock(mutex); + auto path_str = path.generic_string(); + error.code = git_repository_discover(&root, path_str.c_str(), 0, nullptr); + if (error) + throw std::runtime_error(error.message()); + } + auto root_path = Git::path(root.ptr, root.size); + git_buf_free(&root); + return root_path; } Git::Repository::Diff Git::Repository::get_diff(const boost::filesystem::path &path) { - return Diff(path, repository.get()); + return Diff(path, repository.get()); } std::string Git::Repository::get_branch() noexcept { - std::string branch; - git_reference *reference; - if(git_repository_head(&reference, repository.get())==0) { - if(auto reference_name_cstr=git_reference_name(reference)) { - std::string reference_name(reference_name_cstr); - size_t pos; - if((pos=reference_name.rfind('/'))!=std::string::npos) { - if(pos+1<reference_name.size()) - branch=reference_name.substr(pos+1); - } - else if((pos=reference_name.rfind('\\'))!=std::string::npos) { - if(pos+1<reference_name.size()) - branch=reference_name.substr(pos+1); - } + std::string branch; + git_reference *reference; + if (git_repository_head(&reference, repository.get()) == 0) { + if (auto reference_name_cstr = git_reference_name(reference)) { + std::string reference_name(reference_name_cstr); + size_t pos; + if ((pos = reference_name.rfind('/')) != std::string::npos) { + if (pos + 1 < reference_name.size()) + branch = reference_name.substr(pos + 1); + } else if ((pos = reference_name.rfind('\\')) != std::string::npos) { + if (pos + 1 < reference_name.size()) + branch = reference_name.substr(pos + 1); + } + } + git_reference_free(reference); } - git_reference_free(reference); - } - return branch; + return branch; } void Git::initialize() noexcept { - std::lock_guard<std::mutex> lock(mutex); - if(!initialized) { - git_libgit2_init(); - initialized=true; - } + std::lock_guard<std::mutex> lock(mutex); + if (!initialized) { + git_libgit2_init(); + initialized = true; + } } std::shared_ptr<Git::Repository> Git::get_repository(const boost::filesystem::path &path) { - initialize(); - static std::unordered_map<std::string, std::weak_ptr<Git::Repository> > cache; - static std::mutex mutex; - - std::lock_guard<std::mutex> lock(mutex); - auto root_path=Repository::get_root_path(path).generic_string(); - auto it=cache.find(root_path); - if(it==cache.end()) - it=cache.emplace(root_path, std::weak_ptr<Git::Repository>()).first; - auto instance=it->second.lock(); - if(!instance) - it->second=instance=std::shared_ptr<Repository>(new Repository(root_path)); - return instance; + initialize(); + static std::unordered_map<std::string, std::weak_ptr<Git::Repository> > cache; + static std::mutex mutex; + + std::lock_guard<std::mutex> lock(mutex); + auto root_path = Repository::get_root_path(path).generic_string(); + auto it = cache.find(root_path); + if (it == cache.end()) + it = cache.emplace(root_path, std::weak_ptr<Git::Repository>()).first; + auto instance = it->second.lock(); + if (!instance) + it->second = instance = std::shared_ptr<Repository>(new Repository(root_path)); + return instance; } boost::filesystem::path Git::path(const char *cpath, size_t cpath_length) noexcept { - if(cpath==nullptr) - return boost::filesystem::path(); - if(cpath_length==static_cast<size_t>(-1)) - cpath_length=strlen(cpath); - if(cpath_length>0 && (cpath[cpath_length-1]=='/' || cpath[cpath_length-1]=='\\')) - return std::string(cpath, cpath_length-1); - else - return std::string(cpath, cpath_length); + if (cpath == nullptr) + return boost::filesystem::path(); + if (cpath_length == static_cast<size_t>(-1)) + cpath_length = strlen(cpath); + if (cpath_length > 0 && (cpath[cpath_length - 1] == '/' || cpath[cpath_length - 1] == '\\')) + return std::string(cpath, cpath_length - 1); + else + return std::string(cpath, cpath_length); } diff --git a/src/git.h b/src/git.h index eaf9526a..38545c92 100644 --- a/src/git.h +++ b/src/git.h @@ -1,4 +1,5 @@ #pragma once + #include <git2.h> #include <mutex> #include <memory> @@ -10,98 +11,125 @@ #include <boost/filesystem.hpp> class Git { - friend class Repository; + friend class Repository; + public: - class Error { - friend class Git; - std::string message() noexcept; - public: - int code=0; - Error() {} - operator bool() noexcept {return code<0;} - }; - - class Repository { - public: - class Diff { - public: - class Lines { - public: - std::vector<std::pair<int, int> > added; - std::vector<std::pair<int, int> > modified; - std::vector<int> removed; - }; - class Hunk { - public: - Hunk(int old_start, int old_size, int new_start, int new_size): old_lines(old_start, old_size), new_lines(new_start, new_size) {} - /// Start and size - std::pair<int, int> old_lines; - /// Start and size - std::pair<int, int> new_lines; - }; - private: - friend class Repository; - Diff(const boost::filesystem::path &path, git_repository *repository); - git_repository *repository; - std::shared_ptr<git_blob> blob; - git_diff_options options; - static int hunk_cb(const git_diff_delta *delta, const git_diff_hunk *hunk, void *payload) noexcept; - static int line_cb(const git_diff_delta *delta, const git_diff_hunk *hunk, const git_diff_line *line, void *payload) noexcept; + class Error { + friend class Git; + + std::string message() noexcept; + public: - Diff() : repository(nullptr), blob(nullptr) {} - Lines get_lines(const std::string &buffer); - static std::vector<Hunk> get_hunks(const std::string &old_buffer, const std::string &new_buffer); - std::string get_details(const std::string &buffer, int line_nr); + int code = 0; + + Error() {} + + operator bool() noexcept { return code < 0; } }; - - enum class STATUS {CURRENT, NEW, MODIFIED, DELETED, RENAMED, TYPECHANGE, UNREADABLE, IGNORED, CONFLICTED}; - class Status { + + class Repository { public: - std::unordered_set<std::string> added; - std::unordered_set<std::string> modified; + class Diff { + public: + class Lines { + public: + std::vector<std::pair<int, int> > added; + std::vector<std::pair<int, int> > modified; + std::vector<int> removed; + }; + + class Hunk { + public: + Hunk(int old_start, int old_size, int new_start, int new_size) : old_lines(old_start, old_size), + new_lines(new_start, new_size) {} + + /// Start and size + std::pair<int, int> old_lines; + /// Start and size + std::pair<int, int> new_lines; + }; + + private: + friend class Repository; + + Diff(const boost::filesystem::path &path, git_repository *repository); + + git_repository *repository; + std::shared_ptr<git_blob> blob; + git_diff_options options; + + static int hunk_cb(const git_diff_delta *delta, const git_diff_hunk *hunk, void *payload) noexcept; + + static int line_cb(const git_diff_delta *delta, const git_diff_hunk *hunk, const git_diff_line *line, + void *payload) noexcept; + + public: + Diff() : repository(nullptr), blob(nullptr) {} + + Lines get_lines(const std::string &buffer); + + static std::vector<Hunk> get_hunks(const std::string &old_buffer, const std::string &new_buffer); + + std::string get_details(const std::string &buffer, int line_nr); + }; + + enum class STATUS { + CURRENT, NEW, MODIFIED, DELETED, RENAMED, TYPECHANGE, UNREADABLE, IGNORED, CONFLICTED + }; + + class Status { + public: + std::unordered_set<std::string> added; + std::unordered_set<std::string> modified; + }; + + private: + friend class Git; + + Repository(const boost::filesystem::path &path); + + static int status_callback(const char *path, unsigned int status_flags, void *data) noexcept; + + std::unique_ptr<git_repository, std::function<void(git_repository *)> > repository; + + boost::filesystem::path work_path; + sigc::connection monitor_changed_connection; + Status saved_status; + bool has_saved_status = false; + std::mutex saved_status_mutex; + public: + ~Repository(); + + static std::string status_string(STATUS status) noexcept; + + Status get_status(); + + void clear_saved_status(); + + boost::filesystem::path get_work_path() noexcept; + + boost::filesystem::path get_path() noexcept; + + static boost::filesystem::path get_root_path(const boost::filesystem::path &path); + + Diff get_diff(const boost::filesystem::path &path); + + std::string get_branch() noexcept; + + Glib::RefPtr<Gio::FileMonitor> monitor; }; - private: - friend class Git; - Repository(const boost::filesystem::path &path); - - static int status_callback(const char *path, unsigned int status_flags, void *data) noexcept; - - std::unique_ptr<git_repository, std::function<void(git_repository *)> > repository; - - boost::filesystem::path work_path; - sigc::connection monitor_changed_connection; - Status saved_status; - bool has_saved_status=false; - std::mutex saved_status_mutex; - public: - ~Repository(); - - static std::string status_string(STATUS status) noexcept; - - Status get_status(); - void clear_saved_status(); - - boost::filesystem::path get_work_path() noexcept; - boost::filesystem::path get_path() noexcept; - static boost::filesystem::path get_root_path(const boost::filesystem::path &path); - - Diff get_diff(const boost::filesystem::path &path); - - std::string get_branch() noexcept; - - Glib::RefPtr<Gio::FileMonitor> monitor; - }; - + private: - static bool initialized; - - ///Mutex for thread safe operations - static std::mutex mutex; - - ///Call initialize in public static methods - static void initialize() noexcept; - - static boost::filesystem::path path(const char *cpath, size_t cpath_length=static_cast<size_t>(-1)) noexcept; + static bool initialized; + + ///Mutex for thread safe operations + static std::mutex mutex; + + ///Call initialize in public static methods + static void initialize() noexcept; + + static boost::filesystem::path path(const char *cpath, size_t cpath_length = static_cast<size_t>(-1)) noexcept; + public: - static std::shared_ptr<Repository> get_repository(const boost::filesystem::path &path); + static std::shared_ptr<Repository> get_repository(const boost::filesystem::path &path); }; diff --git a/src/info.cc b/src/info.cc index 22029a75..a4723a4c 100644 --- a/src/info.cc +++ b/src/info.cc @@ -1,36 +1,36 @@ #include "info.h" Info::Info() { - set_hexpand(false); - set_halign(Gtk::Align::ALIGN_END); - - auto content_area=dynamic_cast<Gtk::Container*>(get_content_area()); - label.set_max_width_chars(40); - label.set_line_wrap(true); - content_area->add(label); - - get_style_context()->add_class("juci_info"); - - //Workaround from https://bugzilla.gnome.org/show_bug.cgi?id=710888 - //Issue described at the same issue report - //TODO: remove later - auto revealer = gtk_widget_get_template_child (GTK_WIDGET (gobj()), GTK_TYPE_INFO_BAR, "revealer"); - if (revealer) { - gtk_revealer_set_transition_type (GTK_REVEALER (revealer), GTK_REVEALER_TRANSITION_TYPE_NONE); - gtk_revealer_set_transition_duration (GTK_REVEALER (revealer), 0); - } + set_hexpand(false); + set_halign(Gtk::Align::ALIGN_END); + + auto content_area = dynamic_cast<Gtk::Container *>(get_content_area()); + label.set_max_width_chars(40); + label.set_line_wrap(true); + content_area->add(label); + + get_style_context()->add_class("juci_info"); + + //Workaround from https://bugzilla.gnome.org/show_bug.cgi?id=710888 + //Issue described at the same issue report + //TODO: remove later + auto revealer = gtk_widget_get_template_child(GTK_WIDGET (gobj()), GTK_TYPE_INFO_BAR, "revealer"); + if (revealer) { + gtk_revealer_set_transition_type(GTK_REVEALER (revealer), GTK_REVEALER_TRANSITION_TYPE_NONE); + gtk_revealer_set_transition_duration(GTK_REVEALER (revealer), 0); + } } void Info::print(const std::string &text) { - timeout_connection.disconnect(); - //Timeout based on https://en.wikipedia.org/wiki/Words_per_minute - //(average_words_per_minute*average_letters_per_word)/60 => (228*4.5)/60 = 17.1 - double timeout=1000.0*std::max(3.0, 1.0+text.size()/17.1); - timeout_connection=Glib::signal_timeout().connect([this]() { - hide(); - return false; - }, timeout); - - label.set_text(text); - show(); + timeout_connection.disconnect(); + //Timeout based on https://en.wikipedia.org/wiki/Words_per_minute + //(average_words_per_minute*average_letters_per_word)/60 => (228*4.5)/60 = 17.1 + double timeout = 1000.0 * std::max(3.0, 1.0 + text.size() / 17.1); + timeout_connection = Glib::signal_timeout().connect([this]() { + hide(); + return false; + }, timeout); + + label.set_text(text); + show(); } diff --git a/src/info.h b/src/info.h index 38bc841a..d1ad78ab 100644 --- a/src/info.h +++ b/src/info.h @@ -1,17 +1,19 @@ #pragma once + #include <gtkmm.h> class Info : public Gtk::InfoBar { - Info(); + Info(); + public: - static Info &get() { - static Info instance; - return instance; - } - - void print(const std::string &text); - + static Info &get() { + static Info instance; + return instance; + } + + void print(const std::string &text); + private: - Gtk::Label label; - sigc::connection timeout_connection; + Gtk::Label label; + sigc::connection timeout_connection; }; diff --git a/src/juci.cc b/src/juci.cc index a063a1df..29881803 100644 --- a/src/juci.cc +++ b/src/juci.cc @@ -5,133 +5,133 @@ #include "menu.h" #include "config.h" #include "terminal.h" + #ifndef _WIN32 #include <signal.h> #endif int Application::on_command_line(const Glib::RefPtr<Gio::ApplicationCommandLine> &cmd) { - Glib::set_prgname("juci"); - Glib::OptionContext ctx("[PATH ...]"); - Glib::OptionGroup gtk_group(gtk_get_option_group(true)); - ctx.add_group(gtk_group); - int argc; - char **argv = cmd->get_arguments(argc); - ctx.parse(argc, argv); - if(argc>=2) { - boost::system::error_code current_path_ec; - auto current_path=boost::filesystem::current_path(current_path_ec); - if(current_path_ec) - errors.emplace_back("Error: could not find current path\n"); - for(int c=1;c<argc;c++) { - boost::filesystem::path path(argv[c]); - if(path.is_relative() && !current_path_ec) - path=current_path/path; - if(boost::filesystem::exists(path)) { - if(boost::filesystem::is_regular_file(path)) - files.emplace_back(path, 0); - else if(boost::filesystem::is_directory(path)) - directories.emplace_back(path); - } - //Open new file if parent path exists - else { - if(path.is_absolute() && boost::filesystem::is_directory(path.parent_path())) - files.emplace_back(path, 0); - else - errors.emplace_back("Error: could not create "+path.string()+".\n"); - } + Glib::set_prgname("juci"); + Glib::OptionContext ctx("[PATH ...]"); + Glib::OptionGroup gtk_group(gtk_get_option_group(true)); + ctx.add_group(gtk_group); + int argc; + char **argv = cmd->get_arguments(argc); + ctx.parse(argc, argv); + if (argc >= 2) { + boost::system::error_code current_path_ec; + auto current_path = boost::filesystem::current_path(current_path_ec); + if (current_path_ec) + errors.emplace_back("Error: could not find current path\n"); + for (int c = 1; c < argc; c++) { + boost::filesystem::path path(argv[c]); + if (path.is_relative() && !current_path_ec) + path = current_path / path; + if (boost::filesystem::exists(path)) { + if (boost::filesystem::is_regular_file(path)) + files.emplace_back(path, 0); + else if (boost::filesystem::is_directory(path)) + directories.emplace_back(path); + } + //Open new file if parent path exists + else { + if (path.is_absolute() && boost::filesystem::is_directory(path.parent_path())) + files.emplace_back(path, 0); + else + errors.emplace_back("Error: could not create " + path.string() + ".\n"); + } + } } - } - activate(); - return 0; + activate(); + return 0; } void Application::on_activate() { - std::vector<std::pair<int, int>> file_offsets; - std::string current_file; - Window::get().load_session(directories, files, file_offsets, current_file, directories.empty() && files.empty()); - - Window::get().add_widgets(); - - add_window(Window::get()); - Window::get().show(); - - bool first_directory=true; - for(auto &directory: directories) { - if(first_directory) { - Directories::get().open(directory); - first_directory=false; - } - else { - std::string files_in_directory; - for(auto it=files.begin();it!=files.end();) { - if(it->first.generic_string().compare(0, directory.generic_string().size()+1, directory.generic_string()+'/')==0) { - files_in_directory+=" "+it->first.string(); - it=files.erase(it); + std::vector<std::pair<int, int>> file_offsets; + std::string current_file; + Window::get().load_session(directories, files, file_offsets, current_file, directories.empty() && files.empty()); + + Window::get().add_widgets(); + + add_window(Window::get()); + Window::get().show(); + + bool first_directory = true; + for (auto &directory: directories) { + if (first_directory) { + Directories::get().open(directory); + first_directory = false; + } else { + std::string files_in_directory; + for (auto it = files.begin(); it != files.end();) { + if (it->first.generic_string().compare(0, directory.generic_string().size() + 1, + directory.generic_string() + '/') == 0) { + files_in_directory += " " + it->first.string(); + it = files.erase(it); + } else + it++; + } + std::thread another_juci_app([directory, files_in_directory]() { + Terminal::get().async_print("Executing: juci " + directory.string() + files_in_directory + "\n"); + Terminal::get().process("juci " + directory.string() + files_in_directory, "", false); + }); + another_juci_app.detach(); } - else - it++; - } - std::thread another_juci_app([directory, files_in_directory](){ - Terminal::get().async_print("Executing: juci "+directory.string()+files_in_directory+"\n"); - Terminal::get().process("juci "+directory.string()+files_in_directory, "", false); - }); - another_juci_app.detach(); } - } - - for(size_t i=0;i<files.size();++i) { - Notebook::get().open(files[i].first, files[i].second); - if(i<file_offsets.size()) { - if(auto view=Notebook::get().get_current_view()) { - view->place_cursor_at_line_offset(file_offsets[i].first, file_offsets[i].second); - view->hide_tooltips(); - } + + for (size_t i = 0; i < files.size(); ++i) { + Notebook::get().open(files[i].first, files[i].second); + if (i < file_offsets.size()) { + if (auto view = Notebook::get().get_current_view()) { + view->place_cursor_at_line_offset(file_offsets[i].first, file_offsets[i].second); + view->hide_tooltips(); + } + } } - } - - for(auto &error: errors) - Terminal::get().print(error, true); - - if(!current_file.empty()) { - Notebook::get().open(current_file); - if(auto view=Notebook::get().get_current_view()) { - auto iter=view->get_buffer()->get_insert()->get_iter(); - // To update cursor history - view->place_cursor_at_line_offset(iter.get_line(), iter.get_line_offset()); - view->hide_tooltips(); + + for (auto &error: errors) + Terminal::get().print(error, true); + + if (!current_file.empty()) { + Notebook::get().open(current_file); + if (auto view = Notebook::get().get_current_view()) { + auto iter = view->get_buffer()->get_insert()->get_iter(); + // To update cursor history + view->place_cursor_at_line_offset(iter.get_line(), iter.get_line_offset()); + view->hide_tooltips(); + } } - } - - while(Gtk::Main::events_pending()) - Gtk::Main::iteration(false); - for(auto view: Notebook::get().get_views()) - view->scroll_to(view->get_buffer()->get_insert(), 0.0, 1.0, 0.5); + + while (Gtk::Main::events_pending()) + Gtk::Main::iteration(false); + for (auto view: Notebook::get().get_views()) + view->scroll_to(view->get_buffer()->get_insert(), 0.0, 1.0, 0.5); } void Application::on_startup() { - Gtk::Application::on_startup(); - - Menu::get().build(); - - if (!Menu::get().juci_menu || !Menu::get().window_menu) { - std::cerr << "Menu not found." << std::endl; - } - else { - set_app_menu(Menu::get().juci_menu); - set_menubar(Menu::get().window_menu); - } + Gtk::Application::on_startup(); + + Menu::get().build(); + + if (!Menu::get().juci_menu || !Menu::get().window_menu) { + std::cerr << "Menu not found." << std::endl; + } else { + set_app_menu(Menu::get().juci_menu); + set_menubar(Menu::get().window_menu); + } } -Application::Application() : Gtk::Application("no.sout.juci", Gio::APPLICATION_NON_UNIQUE | Gio::APPLICATION_HANDLES_COMMAND_LINE) { - Glib::set_application_name("juCi++"); - - //Gtk::MessageDialog without buttons caused text to be selected, this prevents that - Gtk::Settings::get_default()->property_gtk_label_select_on_focus()=false; +Application::Application() : Gtk::Application("no.sout.juci", + Gio::APPLICATION_NON_UNIQUE | Gio::APPLICATION_HANDLES_COMMAND_LINE) { + Glib::set_application_name("juCi++"); + + //Gtk::MessageDialog without buttons caused text to be selected, this prevents that + Gtk::Settings::get_default()->property_gtk_label_select_on_focus() = false; } int main(int argc, char *argv[]) { #ifndef _WIN32 - signal(SIGPIPE, SIG_IGN); // Do not terminate application when writing to a process fails + signal(SIGPIPE, SIG_IGN); // Do not terminate application when writing to a process fails #endif - return Application().run(argc, argv); + return Application().run(argc, argv); } diff --git a/src/juci.h b/src/juci.h index 5a4a4bb0..0d267496 100644 --- a/src/juci.h +++ b/src/juci.h @@ -30,12 +30,16 @@ class Application : public Gtk::Application { public: - Application(); - int on_command_line(const Glib::RefPtr<Gio::ApplicationCommandLine> &cmd) override; - void on_activate() override; - void on_startup() override; + Application(); + + int on_command_line(const Glib::RefPtr<Gio::ApplicationCommandLine> &cmd) override; + + void on_activate() override; + + void on_startup() override; + private: - std::vector<boost::filesystem::path> directories; - std::vector<std::pair<boost::filesystem::path, size_t>> files; - std::vector<std::string> errors; + std::vector<boost::filesystem::path> directories; + std::vector<std::pair<boost::filesystem::path, size_t>> files; + std::vector<std::string> errors; }; diff --git a/src/menu.cc b/src/menu.cc index a79d156c..3a0e16dd 100644 --- a/src/menu.cc +++ b/src/menu.cc @@ -3,7 +3,7 @@ #include <string> #include <iostream> -const Glib::ustring menu_xml= R"RAW(<interface> +const Glib::ustring menu_xml = R"RAW(<interface> <menu id='right-click-line-menu'> <section> <item> @@ -478,39 +478,39 @@ const Glib::ustring menu_xml= R"RAW(<interface> )RAW"; void Menu::add_action(const std::string &name, std::function<void()> action) { - auto g_application=g_application_get_default(); - auto gio_application=Glib::wrap(g_application, true); - auto application=Glib::RefPtr<Gtk::Application>::cast_static(gio_application); + auto g_application = g_application_get_default(); + auto gio_application = Glib::wrap(g_application, true); + auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application); - actions[name]=application->add_action(name, action); + actions[name] = application->add_action(name, action); } void Menu::set_keys() { - auto g_application=g_application_get_default(); - auto gio_application=Glib::wrap(g_application, true); - auto application=Glib::RefPtr<Gtk::Application>::cast_static(gio_application); + auto g_application = g_application_get_default(); + auto gio_application = Glib::wrap(g_application, true); + auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application); - for(auto &key: Config::get().menu.keys) { - if(key.second.size()>0 && actions.find(key.first)!=actions.end()) - application->set_accel_for_action("app."+key.first, key.second); - } + for (auto &key: Config::get().menu.keys) { + if (key.second.size() > 0 && actions.find(key.first) != actions.end()) + application->set_accel_for_action("app." + key.first, key.second); + } } void Menu::build() { - try { - builder = Gtk::Builder::create_from_string(menu_xml); - auto object = builder->get_object("juci-menu"); - juci_menu = Glib::RefPtr<Gio::Menu>::cast_dynamic(object); - object = builder->get_object("window-menu"); - window_menu = Glib::RefPtr<Gio::Menu>::cast_dynamic(object); - object = builder->get_object("right-click-line-menu"); - auto ptr = Glib::RefPtr<Gio::Menu>::cast_dynamic(object); - right_click_line_menu = std::make_unique<Gtk::Menu>(ptr); - object = builder->get_object("right-click-selected-menu"); - ptr = Glib::RefPtr<Gio::Menu>::cast_dynamic(object); - right_click_selected_menu = std::make_unique<Gtk::Menu>(ptr); - } - catch (const Glib::Error &ex) { - std::cerr << "building menu failed: " << ex.what(); - } + try { + builder = Gtk::Builder::create_from_string(menu_xml); + auto object = builder->get_object("juci-menu"); + juci_menu = Glib::RefPtr<Gio::Menu>::cast_dynamic(object); + object = builder->get_object("window-menu"); + window_menu = Glib::RefPtr<Gio::Menu>::cast_dynamic(object); + object = builder->get_object("right-click-line-menu"); + auto ptr = Glib::RefPtr<Gio::Menu>::cast_dynamic(object); + right_click_line_menu = std::make_unique<Gtk::Menu>(ptr); + object = builder->get_object("right-click-selected-menu"); + ptr = Glib::RefPtr<Gio::Menu>::cast_dynamic(object); + right_click_selected_menu = std::make_unique<Gtk::Menu>(ptr); + } + catch (const Glib::Error &ex) { + std::cerr << "building menu failed: " << ex.what(); + } } diff --git a/src/menu.h b/src/menu.h index 9d56d6ff..9951b373 100644 --- a/src/menu.h +++ b/src/menu.h @@ -1,28 +1,32 @@ #pragma once + #include <string> #include <unordered_map> #include <functional> #include <gtkmm.h> class Menu { - Menu() {} + Menu() {} + public: - static Menu &get() { - static Menu singleton; - return singleton; - } - - void add_action(const std::string &name, std::function<void()> action); - std::unordered_map<std::string, Glib::RefPtr<Gio::SimpleAction> > actions; - void set_keys(); - - void build(); - - Glib::RefPtr<Gio::Menu> juci_menu; - Glib::RefPtr<Gio::Menu> window_menu; - std::unique_ptr<Gtk::Menu> right_click_line_menu; - std::unique_ptr<Gtk::Menu> right_click_selected_menu; - std::function<void()> toggle_menu_items = []{}; + static Menu &get() { + static Menu singleton; + return singleton; + } + + void add_action(const std::string &name, std::function<void()> action); + + std::unordered_map<std::string, Glib::RefPtr<Gio::SimpleAction> > actions; + + void set_keys(); + + void build(); + + Glib::RefPtr<Gio::Menu> juci_menu; + Glib::RefPtr<Gio::Menu> window_menu; + std::unique_ptr<Gtk::Menu> right_click_line_menu; + std::unique_ptr<Gtk::Menu> right_click_selected_menu; + std::function<void()> toggle_menu_items = [] {}; private: - Glib::RefPtr<Gtk::Builder> builder; + Glib::RefPtr<Gtk::Builder> builder; }; diff --git a/src/meson.cc b/src/meson.cc deleted file mode 100644 index 0960c925..00000000 --- a/src/meson.cc +++ /dev/null @@ -1,117 +0,0 @@ -#include "meson.h" -#include "filesystem.h" -#include "compile_commands.h" -#include <regex> -#include "terminal.h" -#include "dialogs.h" -#include "config.h" - -Meson::Meson(const boost::filesystem::path &path) { - const auto find_project=[](const boost::filesystem::path &file_path) { - for(auto &line: filesystem::read_lines(file_path)) { - const static std::regex project_regex("^ *project *\\(.*\\r?$", std::regex::icase); - std::smatch sm; - if(std::regex_match(line, sm, project_regex)) - return true; - } - return false; - }; - - auto search_path=boost::filesystem::is_directory(path)?path:path.parent_path(); - while(true) { - auto search_file=search_path/"meson.build"; - if(boost::filesystem::exists(search_file)) { - if(find_project(search_file)) { - project_path=search_path; - break; - } - } - if(search_path==search_path.root_directory()) - break; - search_path=search_path.parent_path(); - } -} - -bool Meson::update_default_build(const boost::filesystem::path &default_build_path, bool force) { - if(project_path.empty() || !boost::filesystem::exists(project_path/"meson.build") || default_build_path.empty()) - return false; - - if(!boost::filesystem::exists(default_build_path)) { - boost::system::error_code ec; - boost::filesystem::create_directories(default_build_path, ec); - if(ec) { - Terminal::get().print("Error: could not create "+default_build_path.string()+": "+ec.message()+"\n", true); - return false; - } - } - - auto compile_commands_path=default_build_path/"compile_commands.json"; - bool compile_commands_exists=boost::filesystem::exists(compile_commands_path); - if(!force && compile_commands_exists) - return true; - - Dialog::Message message("Creating/updating default build"); - auto exit_status=Terminal::get().process(Config::get().project.meson.command+' '+(compile_commands_exists?"--internal regenerate ":"")+ - filesystem::escape_argument(project_path.string()), default_build_path); - message.hide(); - if(exit_status==EXIT_SUCCESS) - return true; - return false; -} - -bool Meson::update_debug_build(const boost::filesystem::path &debug_build_path, bool force) { - if(project_path.empty() || !boost::filesystem::exists(project_path/"meson.build") || debug_build_path.empty()) - return false; - - if(!boost::filesystem::exists(debug_build_path)) { - boost::system::error_code ec; - boost::filesystem::create_directories(debug_build_path, ec); - if(ec) { - Terminal::get().print("Error: could not create "+debug_build_path.string()+": "+ec.message()+"\n", true); - return false; - } - } - - bool compile_commands_exists=boost::filesystem::exists(debug_build_path/"compile_commands.json"); - if(!force && compile_commands_exists) - return true; - - Dialog::Message message("Creating/updating debug build"); - auto exit_status=Terminal::get().process(Config::get().project.meson.command+' '+(compile_commands_exists?"--internal regenerate ":"")+ - "--buildtype debug "+filesystem::escape_argument(project_path.string()), debug_build_path); - message.hide(); - if(exit_status==EXIT_SUCCESS) - return true; - return false; -} - -boost::filesystem::path Meson::get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) { - CompileCommands compile_commands(build_path); - - size_t best_match_size=-1; - boost::filesystem::path best_match_executable; - for(auto &command: compile_commands.commands) { - auto command_file=filesystem::get_normal_path(command.file); - auto values=command.parameter_values("-o"); - if(!values.empty()) { - size_t pos; - if((pos=values[0].find("@"))!=std::string::npos) { - if(pos+1<values[0].size() && values[0].compare(pos+1, 3, "exe")==0) { - auto executable=build_path/values[0].substr(0, pos); - if(command_file==file_path) - return executable; - auto command_file_directory=command_file.parent_path(); - if(filesystem::file_in_path(file_path, command_file_directory)) { - auto size=static_cast<size_t>(std::distance(command_file_directory.begin(), command_file_directory.end())); - if(best_match_size==static_cast<size_t>(-1) || best_match_size<size) { - best_match_size=size; - best_match_executable=executable; - } - } - } - } - } - } - - return best_match_executable; -} diff --git a/src/meson.h b/src/meson.h deleted file mode 100644 index 264afb18..00000000 --- a/src/meson.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once -#include <boost/filesystem.hpp> -#include <vector> - -class Meson { -public: - Meson(const boost::filesystem::path &path); - - boost::filesystem::path project_path; - - bool update_default_build(const boost::filesystem::path &default_build_path, bool force=false); - bool update_debug_build(const boost::filesystem::path &debug_build_path, bool force=false); - - boost::filesystem::path get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path); -}; diff --git a/src/notebook.cc b/src/notebook.cc index b2327b11..a177968a 100644 --- a/src/notebook.cc +++ b/src/notebook.cc @@ -11,664 +11,673 @@ #include "gtksourceview-3.0/gtksourceview/gtksourcemap.h" Notebook::TabLabel::TabLabel(std::function<void()> on_close) { - set_can_focus(false); - - auto button=Gtk::manage(new Gtk::Button()); - auto hbox=Gtk::manage(new Gtk::Box()); - - hbox->set_can_focus(false); - 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); - - hbox->pack_start(label, Gtk::PACK_SHRINK); - hbox->pack_end(*button, Gtk::PACK_SHRINK); - add(*hbox); - - button->signal_clicked().connect(on_close); - signal_button_press_event().connect([on_close](GdkEventButton *event) { - if(event->button==GDK_BUTTON_MIDDLE) { - on_close(); - return true; - } - return false; - }); - - show_all(); -} + set_can_focus(false); -Notebook::Notebook() : Gtk::Paned(), notebooks(2) { - for(auto ¬ebook: notebooks) { - notebook.get_style_context()->add_class("juci_notebook"); - notebook.set_scrollable(); - notebook.set_group_name("source_notebooks"); - notebook.signal_switch_page().connect([this](Gtk::Widget *widget, guint) { - auto hbox=dynamic_cast<Gtk::Box*>(widget); - for(size_t c=0;c<hboxes.size();++c) { - if(hboxes[c].get()==hbox) { - focus_view(source_views[c]); - set_current_view(source_views[c]); - break; - } - } - last_index=-1; - }); - notebook.signal_page_added().connect([this](Gtk::Widget* widget, guint) { - auto hbox=dynamic_cast<Gtk::Box*>(widget); - for(size_t c=0;c<hboxes.size();++c) { - if(hboxes[c].get()==hbox) { - focus_view(source_views[c]); - set_current_view(source_views[c]); - break; + auto button = Gtk::manage(new Gtk::Button()); + auto hbox = Gtk::manage(new Gtk::Box()); + + hbox->set_can_focus(false); + 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); + + hbox->pack_start(label, Gtk::PACK_SHRINK); + hbox->pack_end(*button, Gtk::PACK_SHRINK); + add(*hbox); + + button->signal_clicked().connect(on_close); + signal_button_press_event().connect([on_close](GdkEventButton *event) { + if (event->button == GDK_BUTTON_MIDDLE) { + on_close(); + return true; } - } + return false; }); - } - pack1(notebooks[0], true, true); + + show_all(); +} + +Notebook::Notebook() : Gtk::Paned(), notebooks(2) { + for (auto ¬ebook: notebooks) { + notebook.get_style_context()->add_class("juci_notebook"); + notebook.set_scrollable(); + notebook.set_group_name("source_notebooks"); + notebook.signal_switch_page().connect([this](Gtk::Widget *widget, guint) { + auto hbox = dynamic_cast<Gtk::Box *>(widget); + for (size_t c = 0; c < hboxes.size(); ++c) { + if (hboxes[c].get() == hbox) { + focus_view(source_views[c]); + set_current_view(source_views[c]); + break; + } + } + last_index = -1; + }); + notebook.signal_page_added().connect([this](Gtk::Widget *widget, guint) { + auto hbox = dynamic_cast<Gtk::Box *>(widget); + for (size_t c = 0; c < hboxes.size(); ++c) { + if (hboxes[c].get() == hbox) { + focus_view(source_views[c]); + set_current_view(source_views[c]); + break; + } + } + }); + } + pack1(notebooks[0], true, true); } size_t Notebook::size() { - return source_views.size(); + return source_views.size(); } -Source::View* Notebook::get_view(size_t index) { - if(index>=size()) - return nullptr; - return source_views[index]; +Source::View *Notebook::get_view(size_t index) { + if (index >= size()) + return nullptr; + return source_views[index]; } -Source::View* Notebook::get_current_view() { - if(intermediate_view) { - for(auto view: source_views) { - if(view==intermediate_view) - return view; +Source::View *Notebook::get_current_view() { + if (intermediate_view) { + for (auto view: source_views) { + if (view == intermediate_view) + return view; + } + } + for (auto view: source_views) { + if (view == current_view) + return view; } - } - for(auto view: source_views) { - if(view==current_view) - return view; - } - //In case there exist a tab that has not yet received focus again in a different notebook - for(int notebook_index=0;notebook_index<2;++notebook_index) { - auto page=notebooks[notebook_index].get_current_page(); - if(page>=0) - return get_view(notebook_index, page); - } - return nullptr; + //In case there exist a tab that has not yet received focus again in a different notebook + for (int notebook_index = 0; notebook_index < 2; ++notebook_index) { + auto page = notebooks[notebook_index].get_current_page(); + if (page >= 0) + return get_view(notebook_index, page); + } + return nullptr; } -std::vector<Source::View*> &Notebook::get_views() { - return source_views; +std::vector<Source::View *> &Notebook::get_views() { + return source_views; } void Notebook::open(const boost::filesystem::path &file_path_, size_t notebook_index) { - auto file_path=filesystem::get_normal_path(file_path_); - - if(notebook_index==1 && !split) - toggle_split(); - - // Use canonical path to follow symbolic links - boost::system::error_code ec; - auto canonical_file_path=boost::filesystem::canonical(file_path, ec); - if(ec) - canonical_file_path=file_path; - for(size_t c=0;c<size();c++) { - if(canonical_file_path==source_views[c]->canonical_file_path) { - auto notebook_page=get_notebook_page(c); - notebooks[notebook_page.first].set_current_page(notebook_page.second); - focus_view(source_views[c]); - return; - } - } - - if(boost::filesystem::exists(file_path)) { - std::ifstream can_read(file_path.string()); - if(!can_read) { - Terminal::get().print("Error: could not open "+file_path.string()+"\n", true); - return; - } - can_read.close(); - } - - auto last_view=get_current_view(); - - auto language=Source::guess_language(file_path); - - std::string language_protocol_language_id; - if(language) { - language_protocol_language_id=language->get_id(); - if(language_protocol_language_id=="js") { - if(file_path.extension()==".ts") - language_protocol_language_id="typescript"; - else - language_protocol_language_id="javascript"; - } - } - - if(language && (language->get_id()=="chdr" || language->get_id()=="cpphdr" || language->get_id()=="c" || language->get_id()=="cpp" || language->get_id()=="objc")) - source_views.emplace_back(new Source::ClangView(file_path, language)); - else if(language && !language_protocol_language_id.empty() && !filesystem::find_executable(language_protocol_language_id+"-language-server").empty()) - source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id)); - else - source_views.emplace_back(new Source::GenericView(file_path, language)); - - auto source_view=source_views.back(); - - source_view->scroll_to_cursor_delayed=[this](Source::BaseView* view, bool center, bool show_tooltips) { - while(Gtk::Main::events_pending()) - Gtk::Main::iteration(false); - if(get_current_view()==view) { - if(center) - view->scroll_to(view->get_buffer()->get_insert(), 0.0, 1.0, 0.5); - else - view->scroll_to(view->get_buffer()->get_insert()); - if(!show_tooltips) - view->hide_tooltips(); + auto file_path = filesystem::get_normal_path(file_path_); + + if (notebook_index == 1 && !split) + toggle_split(); + + // Use canonical path to follow symbolic links + boost::system::error_code ec; + auto canonical_file_path = boost::filesystem::canonical(file_path, ec); + if (ec) + canonical_file_path = file_path; + for (size_t c = 0; c < size(); c++) { + if (canonical_file_path == source_views[c]->canonical_file_path) { + auto notebook_page = get_notebook_page(c); + notebooks[notebook_page.first].set_current_page(notebook_page.second); + focus_view(source_views[c]); + return; + } } - }; - source_view->update_status_location=[this](Source::BaseView* view) { - if(get_current_view()==view) { - auto iter=view->get_buffer()->get_insert()->get_iter(); - status_location.set_text(" "+std::to_string(iter.get_line()+1)+":"+std::to_string(iter.get_line_offset()+1)); + + if (boost::filesystem::exists(file_path)) { + std::ifstream can_read(file_path.string()); + if (!can_read) { + Terminal::get().print("Error: could not open " + file_path.string() + "\n", true); + return; + } + can_read.close(); } - }; - source_view->update_status_file_path=[this](Source::BaseView* view) { - if(get_current_view()==view) - status_file_path.set_text(' '+filesystem::get_short_path(view->file_path).string()); - }; - source_view->update_status_branch=[this](Source::BaseView* view) { - if(get_current_view()==view) { - if(!view->status_branch.empty()) - status_branch.set_text(" ("+view->status_branch+")"); - else - status_branch.set_text(""); + + auto last_view = get_current_view(); + + auto language = Source::guess_language(file_path); + + std::string language_protocol_language_id; + if (language) { + language_protocol_language_id = language->get_id(); + if (language_protocol_language_id == "js") { + if (file_path.extension() == ".ts") + language_protocol_language_id = "typescript"; + else + language_protocol_language_id = "javascript"; + } } - }; - source_view->update_status_diagnostics=[this](Source::BaseView* view) { - if(get_current_view()==view) { - std::string diagnostic_info; - - auto num_warnings=std::get<0>(view->status_diagnostics); - auto num_errors=std::get<1>(view->status_diagnostics); - auto num_fix_its=std::get<2>(view->status_diagnostics); - if(num_warnings>0 || num_errors>0 || num_fix_its>0) { - auto normal_color=get_style_context()->get_color(Gtk::StateFlags::STATE_FLAG_NORMAL); - Gdk::RGBA yellow; - yellow.set_rgba(1.0, 1.0, 0.2); - double factor=0.5; - yellow.set_red(normal_color.get_red()+factor*(yellow.get_red()-normal_color.get_red())); - yellow.set_green(normal_color.get_green()+factor*(yellow.get_green()-normal_color.get_green())); - yellow.set_blue(normal_color.get_blue()+factor*(yellow.get_blue()-normal_color.get_blue())); - Gdk::RGBA red; - red.set_rgba(1.0, 0.0, 0.0); - factor=0.5; - red.set_red(normal_color.get_red()+factor*(red.get_red()-normal_color.get_red())); - red.set_green(normal_color.get_green()+factor*(red.get_green()-normal_color.get_green())); - red.set_blue(normal_color.get_blue()+factor*(red.get_blue()-normal_color.get_blue())); - Gdk::RGBA green; - green.set_rgba(0.0, 1.0, 0.0); - factor=0.4; - green.set_red(normal_color.get_red()+factor*(green.get_red()-normal_color.get_red())); - green.set_green(normal_color.get_green()+factor*(green.get_green()-normal_color.get_green())); - green.set_blue(normal_color.get_blue()+factor*(green.get_blue()-normal_color.get_blue())); - - std::stringstream yellow_ss, red_ss, green_ss; - yellow_ss << std::hex << std::setfill('0') << std::setw(2) << (int)(yellow.get_red_u()>>8) << std::setw(2) << (int)(yellow.get_green_u()>>8) << std::setw(2) << (int)(yellow.get_blue_u()>>8); - red_ss << std::hex << std::setfill('0') << std::setw(2) << (int)(red.get_red_u()>>8) << std::setw(2) << (int)(red.get_green_u()>>8) << std::setw(2) << (int)(red.get_blue_u()>>8); - green_ss << std::hex << std::setfill('0') << std::setw(2) << (int)(green.get_red_u()>>8) << std::setw(2) << (int)(green.get_green_u()>>8) << std::setw(2) << (int)(green.get_blue_u()>>8); - if(num_warnings>0) { - diagnostic_info+="<span color='#"+yellow_ss.str()+"'>"; - diagnostic_info+=std::to_string(num_warnings)+" warning"; - if(num_warnings>1) - diagnostic_info+='s'; - diagnostic_info+="</span>"; + + if (language && (language->get_id() == "chdr" || language->get_id() == "cpphdr" || language->get_id() == "c" || + language->get_id() == "cpp" || language->get_id() == "objc")) + source_views.emplace_back(new Source::ClangView(file_path, language)); + else if (language && !language_protocol_language_id.empty() && + !filesystem::find_executable(language_protocol_language_id + "-language-server").empty()) + source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id)); + else + source_views.emplace_back(new Source::GenericView(file_path, language)); + + auto source_view = source_views.back(); + + source_view->scroll_to_cursor_delayed = [this](Source::BaseView *view, bool center, bool show_tooltips) { + while (Gtk::Main::events_pending()) + Gtk::Main::iteration(false); + if (get_current_view() == view) { + if (center) + view->scroll_to(view->get_buffer()->get_insert(), 0.0, 1.0, 0.5); + else + view->scroll_to(view->get_buffer()->get_insert()); + if (!show_tooltips) + view->hide_tooltips(); } - if(num_errors>0) { - if(num_warnings>0) - diagnostic_info+=", "; - diagnostic_info+="<span color='#"+red_ss.str()+"'>"; - diagnostic_info+=std::to_string(num_errors)+" error"; - if(num_errors>1) - diagnostic_info+='s'; - diagnostic_info+="</span>"; + }; + source_view->update_status_location = [this](Source::BaseView *view) { + if (get_current_view() == view) { + auto iter = view->get_buffer()->get_insert()->get_iter(); + status_location.set_text( + " " + std::to_string(iter.get_line() + 1) + ":" + std::to_string(iter.get_line_offset() + 1)); } - if(num_fix_its>0) { - if(num_warnings>0 || num_errors>0) - diagnostic_info+=", "; - diagnostic_info+="<span color='#"+green_ss.str()+"'>"; - diagnostic_info+=std::to_string(num_fix_its)+" fix it"; - if(num_fix_its>1) - diagnostic_info+='s'; - diagnostic_info+="</span>"; + }; + source_view->update_status_file_path = [this](Source::BaseView *view) { + if (get_current_view() == view) + status_file_path.set_text(' ' + filesystem::get_short_path(view->file_path).string()); + }; + source_view->update_status_branch = [this](Source::BaseView *view) { + if (get_current_view() == view) { + if (!view->status_branch.empty()) + status_branch.set_text(" (" + view->status_branch + ")"); + else + status_branch.set_text(""); } - } - status_diagnostics.set_markup(diagnostic_info); - } - }; - source_view->update_status_state=[this](Source::BaseView* view) { - if(get_current_view()==view) - status_state.set_text(view->status_state+" "); - }; - - scrolled_windows.emplace_back(new Gtk::ScrolledWindow()); - hboxes.emplace_back(new Gtk::Box()); - scrolled_windows.back()->add(*source_view); - hboxes.back()->pack_start(*scrolled_windows.back()); - - source_maps.emplace_back(Glib::wrap(gtk_source_map_new())); - gtk_source_map_set_view(GTK_SOURCE_MAP(source_maps.back()->gobj()), source_view->gobj()); - - configure(source_views.size()-1); - - //Set up tab label - tab_labels.emplace_back(new TabLabel([this, source_view]() { - auto index=get_index(source_view); - if(index!=static_cast<size_t>(-1)) - close(index); - })); - source_view->update_tab_label=[this](Source::BaseView *view) { - std::string title=view->file_path.filename().string(); - if(view->get_buffer()->get_modified()) - title+='*'; - else - title+=' '; - for(size_t c=0;c<size();++c) { - if(source_views[c]==view) { - auto &tab_label=tab_labels.at(c); - tab_label->label.set_text(title); - tab_label->set_tooltip_text(filesystem::get_short_path(view->file_path).string()); - return; - } - } - }; - source_view->update_tab_label(source_view); - - //Add star on tab label when the page is not saved: - source_view->get_buffer()->signal_modified_changed().connect([source_view]() { - if(source_view->update_tab_label) - source_view->update_tab_label(source_view); - }); - - //Cursor history - auto update_cursor_locations=[this, source_view](const Gtk::TextBuffer::iterator &iter) { - bool mark_moved=false; - if(current_cursor_location!=static_cast<size_t>(-1)) { - auto &cursor_location=cursor_locations.at(current_cursor_location); - if(cursor_location.view==source_view && abs(cursor_location.mark->get_iter().get_line()-iter.get_line())<=2) { - source_view->get_buffer()->move_mark(cursor_location.mark, iter); - mark_moved=true; - } - } - if(!mark_moved) { - if(current_cursor_location!=static_cast<size_t>(-1)) { - for(auto it=cursor_locations.begin()+current_cursor_location+1;it!=cursor_locations.end();) { - it->view->get_buffer()->delete_mark(it->mark); - it=cursor_locations.erase(it); + }; + source_view->update_status_diagnostics = [this](Source::BaseView *view) { + if (get_current_view() == view) { + std::string diagnostic_info; + + auto num_warnings = std::get<0>(view->status_diagnostics); + auto num_errors = std::get<1>(view->status_diagnostics); + auto num_fix_its = std::get<2>(view->status_diagnostics); + if (num_warnings > 0 || num_errors > 0 || num_fix_its > 0) { + auto normal_color = get_style_context()->get_color(Gtk::StateFlags::STATE_FLAG_NORMAL); + Gdk::RGBA yellow; + yellow.set_rgba(1.0, 1.0, 0.2); + double factor = 0.5; + yellow.set_red(normal_color.get_red() + factor * (yellow.get_red() - normal_color.get_red())); + yellow.set_green(normal_color.get_green() + factor * (yellow.get_green() - normal_color.get_green())); + yellow.set_blue(normal_color.get_blue() + factor * (yellow.get_blue() - normal_color.get_blue())); + Gdk::RGBA red; + red.set_rgba(1.0, 0.0, 0.0); + factor = 0.5; + red.set_red(normal_color.get_red() + factor * (red.get_red() - normal_color.get_red())); + red.set_green(normal_color.get_green() + factor * (red.get_green() - normal_color.get_green())); + red.set_blue(normal_color.get_blue() + factor * (red.get_blue() - normal_color.get_blue())); + Gdk::RGBA green; + green.set_rgba(0.0, 1.0, 0.0); + factor = 0.4; + green.set_red(normal_color.get_red() + factor * (green.get_red() - normal_color.get_red())); + green.set_green(normal_color.get_green() + factor * (green.get_green() - normal_color.get_green())); + green.set_blue(normal_color.get_blue() + factor * (green.get_blue() - normal_color.get_blue())); + + std::stringstream yellow_ss, red_ss, green_ss; + yellow_ss << std::hex << std::setfill('0') << std::setw(2) << (int) (yellow.get_red_u() >> 8) + << std::setw(2) << (int) (yellow.get_green_u() >> 8) << std::setw(2) + << (int) (yellow.get_blue_u() >> 8); + red_ss << std::hex << std::setfill('0') << std::setw(2) << (int) (red.get_red_u() >> 8) << std::setw(2) + << (int) (red.get_green_u() >> 8) << std::setw(2) << (int) (red.get_blue_u() >> 8); + green_ss << std::hex << std::setfill('0') << std::setw(2) << (int) (green.get_red_u() >> 8) + << std::setw(2) << (int) (green.get_green_u() >> 8) << std::setw(2) + << (int) (green.get_blue_u() >> 8); + if (num_warnings > 0) { + diagnostic_info += "<span color='#" + yellow_ss.str() + "'>"; + diagnostic_info += std::to_string(num_warnings) + " warning"; + if (num_warnings > 1) + diagnostic_info += 's'; + diagnostic_info += "</span>"; + } + if (num_errors > 0) { + if (num_warnings > 0) + diagnostic_info += ", "; + diagnostic_info += "<span color='#" + red_ss.str() + "'>"; + diagnostic_info += std::to_string(num_errors) + " error"; + if (num_errors > 1) + diagnostic_info += 's'; + diagnostic_info += "</span>"; + } + if (num_fix_its > 0) { + if (num_warnings > 0 || num_errors > 0) + diagnostic_info += ", "; + diagnostic_info += "<span color='#" + green_ss.str() + "'>"; + diagnostic_info += std::to_string(num_fix_its) + " fix it"; + if (num_fix_its > 1) + diagnostic_info += 's'; + diagnostic_info += "</span>"; + } + } + status_diagnostics.set_markup(diagnostic_info); } - } - cursor_locations.emplace_back(source_view, source_view->get_buffer()->create_mark(iter)); - current_cursor_location=cursor_locations.size()-1; - } - - // Combine adjacent cursor histories that are similar - if(!cursor_locations.empty()) { - size_t cursor_locations_index=1; - auto last_it=cursor_locations.begin(); - for(auto it=cursor_locations.begin()+1;it!=cursor_locations.end();) { - if(last_it->view==it->view && abs(last_it->mark->get_iter().get_line()-it->mark->get_iter().get_line())<=2) { - last_it->view->get_buffer()->delete_mark(last_it->mark); - last_it->mark=it->mark; - it=cursor_locations.erase(it); - if(current_cursor_location!=static_cast<size_t>(-1) && current_cursor_location>cursor_locations_index) - --current_cursor_location; + }; + source_view->update_status_state = [this](Source::BaseView *view) { + if (get_current_view() == view) + status_state.set_text(view->status_state + " "); + }; + + scrolled_windows.emplace_back(new Gtk::ScrolledWindow()); + hboxes.emplace_back(new Gtk::Box()); + scrolled_windows.back()->add(*source_view); + hboxes.back()->pack_start(*scrolled_windows.back()); + + source_maps.emplace_back(Glib::wrap(gtk_source_map_new())); + gtk_source_map_set_view(GTK_SOURCE_MAP(source_maps.back()->gobj()), source_view->gobj()); + + configure(source_views.size() - 1); + + //Set up tab label + tab_labels.emplace_back(new TabLabel([this, source_view]() { + auto index = get_index(source_view); + if (index != static_cast<size_t>(-1)) + close(index); + })); + source_view->update_tab_label = [this](Source::BaseView *view) { + std::string title = view->file_path.filename().string(); + if (view->get_buffer()->get_modified()) + title += '*'; + else + title += ' '; + for (size_t c = 0; c < size(); ++c) { + if (source_views[c] == view) { + auto &tab_label = tab_labels.at(c); + tab_label->label.set_text(title); + tab_label->set_tooltip_text(filesystem::get_short_path(view->file_path).string()); + return; + } + } + }; + source_view->update_tab_label(source_view); + + //Add star on tab label when the page is not saved: + source_view->get_buffer()->signal_modified_changed().connect([source_view]() { + if (source_view->update_tab_label) + source_view->update_tab_label(source_view); + }); + + //Cursor history + auto update_cursor_locations = [this, source_view](const Gtk::TextBuffer::iterator &iter) { + bool mark_moved = false; + if (current_cursor_location != static_cast<size_t>(-1)) { + auto &cursor_location = cursor_locations.at(current_cursor_location); + if (cursor_location.view == source_view && + abs(cursor_location.mark->get_iter().get_line() - iter.get_line()) <= 2) { + source_view->get_buffer()->move_mark(cursor_location.mark, iter); + mark_moved = true; + } + } + if (!mark_moved) { + if (current_cursor_location != static_cast<size_t>(-1)) { + for (auto it = cursor_locations.begin() + current_cursor_location + 1; it != cursor_locations.end();) { + it->view->get_buffer()->delete_mark(it->mark); + it = cursor_locations.erase(it); + } + } + cursor_locations.emplace_back(source_view, source_view->get_buffer()->create_mark(iter)); + current_cursor_location = cursor_locations.size() - 1; + } + + // Combine adjacent cursor histories that are similar + if (!cursor_locations.empty()) { + size_t cursor_locations_index = 1; + auto last_it = cursor_locations.begin(); + for (auto it = cursor_locations.begin() + 1; it != cursor_locations.end();) { + if (last_it->view == it->view && + abs(last_it->mark->get_iter().get_line() - it->mark->get_iter().get_line()) <= 2) { + last_it->view->get_buffer()->delete_mark(last_it->mark); + last_it->mark = it->mark; + it = cursor_locations.erase(it); + if (current_cursor_location != static_cast<size_t>(-1) && + current_cursor_location > cursor_locations_index) + --current_cursor_location; + } else { + ++it; + ++last_it; + ++cursor_locations_index; + } + } + } + + // Remove start of cache if cache limit is exceeded + while (cursor_locations.size() > 10) { + cursor_locations.begin()->view->get_buffer()->delete_mark(cursor_locations.begin()->mark); + cursor_locations.erase(cursor_locations.begin()); + if (current_cursor_location != static_cast<size_t>(-1)) + --current_cursor_location; + } + + if (current_cursor_location >= cursor_locations.size()) + current_cursor_location = cursor_locations.size() - 1; + }; + source_view->get_buffer()->signal_mark_set().connect( + [this, update_cursor_locations](const Gtk::TextBuffer::iterator &iter, + const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { + if (mark->get_name() == "insert") { + if (disable_next_update_cursor_locations) { + disable_next_update_cursor_locations = false; + return; + } + update_cursor_locations(iter); + } + }); + source_view->get_buffer()->signal_changed().connect([source_view, update_cursor_locations] { + update_cursor_locations(source_view->get_buffer()->get_insert()->get_iter()); + }); + +#ifdef JUCI_ENABLE_DEBUG + if(dynamic_cast<Source::ClangView*>(source_view) || (source_view->language && source_view->language->get_id()=="rust")) { + source_view->toggle_breakpoint=[source_view](int line_nr) { + if(source_view->get_source_buffer()->get_source_marks_at_line(line_nr, "debug_breakpoint").size()>0) { + auto start_iter=source_view->get_buffer()->get_iter_at_line(line_nr); + auto end_iter=source_view->get_iter_at_line_end(line_nr); + source_view->get_source_buffer()->remove_source_marks(start_iter, end_iter, "debug_breakpoint"); + source_view->get_source_buffer()->remove_source_marks(start_iter, end_iter, "debug_breakpoint_and_stop"); + if(Project::current && Project::debugging) + Project::current->debug_remove_breakpoint(source_view->file_path, line_nr+1, source_view->get_buffer()->get_line_count()+1); } else { - ++it; - ++last_it; - ++cursor_locations_index; + auto iter=source_view->get_buffer()->get_iter_at_line(line_nr); + source_view->get_source_buffer()->create_source_mark("debug_breakpoint", iter); + if(source_view->get_source_buffer()->get_source_marks_at_line(line_nr, "debug_stop").size()>0) + source_view->get_source_buffer()->create_source_mark("debug_breakpoint_and_stop", iter); + if(Project::current && Project::debugging) + Project::current->debug_add_breakpoint(source_view->file_path, line_nr+1); } - } + }; } - - // Remove start of cache if cache limit is exceeded - while(cursor_locations.size()>10) { - cursor_locations.begin()->view->get_buffer()->delete_mark(cursor_locations.begin()->mark); - cursor_locations.erase(cursor_locations.begin()); - if(current_cursor_location!=static_cast<size_t>(-1)) - --current_cursor_location; +#endif + + source_view->signal_focus_in_event().connect([this, source_view](GdkEventFocus *) { + set_current_view(source_view); + return false; + }); + + if (notebook_index == static_cast<size_t>(-1)) { + if (!split) + notebook_index = 0; + else if (notebooks[0].get_n_pages() == 0) + notebook_index = 0; + else if (notebooks[1].get_n_pages() == 0) + notebook_index = 1; + else if (last_view) + notebook_index = get_notebook_page(get_index(last_view)).first; } - - if(current_cursor_location>=cursor_locations.size()) - current_cursor_location=cursor_locations.size()-1; - }; - source_view->get_buffer()->signal_mark_set().connect([this, update_cursor_locations](const Gtk::TextBuffer::iterator &iter, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { - if(mark->get_name()=="insert") { - if(disable_next_update_cursor_locations) { - disable_next_update_cursor_locations=false; - return; - } - update_cursor_locations(iter); + auto ¬ebook = notebooks[notebook_index]; + + notebook.append_page(*hboxes.back(), *tab_labels.back()); + + notebook.set_tab_reorderable(*hboxes.back(), true); + notebook.set_tab_detachable(*hboxes.back(), true); + show_all_children(); + + notebook.set_current_page(notebook.get_n_pages() - 1); + last_index = -1; + if (last_view) { + auto index = get_index(last_view); + auto notebook_page = get_notebook_page(index); + if (notebook_page.first == notebook_index) + last_index = index; } - }); - source_view->get_buffer()->signal_changed().connect([source_view, update_cursor_locations] { - update_cursor_locations(source_view->get_buffer()->get_insert()->get_iter()); - }); - -#ifdef JUCI_ENABLE_DEBUG - if(dynamic_cast<Source::ClangView*>(source_view) || (source_view->language && source_view->language->get_id()=="rust")) { - source_view->toggle_breakpoint=[source_view](int line_nr) { - if(source_view->get_source_buffer()->get_source_marks_at_line(line_nr, "debug_breakpoint").size()>0) { - auto start_iter=source_view->get_buffer()->get_iter_at_line(line_nr); - auto end_iter=source_view->get_iter_at_line_end(line_nr); - source_view->get_source_buffer()->remove_source_marks(start_iter, end_iter, "debug_breakpoint"); - source_view->get_source_buffer()->remove_source_marks(start_iter, end_iter, "debug_breakpoint_and_stop"); - if(Project::current && Project::debugging) - Project::current->debug_remove_breakpoint(source_view->file_path, line_nr+1, source_view->get_buffer()->get_line_count()+1); - } - else { - auto iter=source_view->get_buffer()->get_iter_at_line(line_nr); - source_view->get_source_buffer()->create_source_mark("debug_breakpoint", iter); - if(source_view->get_source_buffer()->get_source_marks_at_line(line_nr, "debug_stop").size()>0) - source_view->get_source_buffer()->create_source_mark("debug_breakpoint_and_stop", iter); - if(Project::current && Project::debugging) - Project::current->debug_add_breakpoint(source_view->file_path, line_nr+1); - } - }; - } -#endif - - source_view->signal_focus_in_event().connect([this, source_view](GdkEventFocus *) { - set_current_view(source_view); - return false; - }); - - if(notebook_index==static_cast<size_t>(-1)) { - if(!split) - notebook_index=0; - else if(notebooks[0].get_n_pages()==0) - notebook_index=0; - else if(notebooks[1].get_n_pages()==0) - notebook_index=1; - else if(last_view) - notebook_index=get_notebook_page(get_index(last_view)).first; - } - auto ¬ebook=notebooks[notebook_index]; - - notebook.append_page(*hboxes.back(), *tab_labels.back()); - - notebook.set_tab_reorderable(*hboxes.back(), true); - notebook.set_tab_detachable(*hboxes.back(), true); - show_all_children(); - - notebook.set_current_page(notebook.get_n_pages()-1); - last_index=-1; - if(last_view) { - auto index=get_index(last_view); - auto notebook_page=get_notebook_page(index); - if(notebook_page.first==notebook_index) - last_index=index; - } - - set_focus_child(*source_views.back()); - focus_view(source_view); + + set_focus_child(*source_views.back()); + focus_view(source_view); } void Notebook::configure(size_t index) { - auto source_font_description=Pango::FontDescription(Config::get().source.font); - auto source_map_font_desc=Pango::FontDescription(static_cast<std::string>(source_font_description.get_family())+" "+Config::get().source.map_font_size); - source_maps.at(index)->override_font(source_map_font_desc); - if(Config::get().source.show_map) { - if(hboxes.at(index)->get_children().size()==1) - hboxes.at(index)->pack_end(*source_maps.at(index), Gtk::PACK_SHRINK); - } - else if(hboxes.at(index)->get_children().size()==2) - hboxes.at(index)->remove(*source_maps.at(index)); + auto source_font_description = Pango::FontDescription(Config::get().source.font); + auto source_map_font_desc = Pango::FontDescription( + static_cast<std::string>(source_font_description.get_family()) + " " + Config::get().source.map_font_size); + source_maps.at(index)->override_font(source_map_font_desc); + if (Config::get().source.show_map) { + if (hboxes.at(index)->get_children().size() == 1) + hboxes.at(index)->pack_end(*source_maps.at(index), Gtk::PACK_SHRINK); + } else if (hboxes.at(index)->get_children().size() == 2) + hboxes.at(index)->remove(*source_maps.at(index)); } bool Notebook::save(size_t index) { - if(!source_views[index]->save()) - return false; - Project::on_save(index); - return true; + if (!source_views[index]->save()) + return false; + Project::on_save(index); + return true; } bool Notebook::save_current() { - if(auto view=get_current_view()) - return save(get_index(view)); - return false; + if (auto view = get_current_view()) + return save(get_index(view)); + return false; } bool Notebook::close(size_t index) { - if(auto view=get_view(index)) { - if(view->get_buffer()->get_modified()){ - if(!save_modified_dialog(index)) - return false; - } - if(view==get_current_view()) { - bool focused=false; - if(last_index!=static_cast<size_t>(-1)) { - auto notebook_page=get_notebook_page(last_index); - if(notebook_page.first==get_notebook_page(get_index(view)).first) { - focus_view(source_views[last_index]); - notebooks[notebook_page.first].set_current_page(notebook_page.second); - last_index=-1; - focused=true; - } - } - if(!focused) { - auto notebook_page=get_notebook_page(get_index(view)); - if(notebook_page.second>0) - focus_view(get_view(notebook_page.first, notebook_page.second-1)); - else { - size_t notebook_index=notebook_page.first==0?1:0; - if(notebooks[notebook_index].get_n_pages()>0) - focus_view(get_view(notebook_index, notebooks[notebook_index].get_current_page())); - else - set_current_view(nullptr); + if (auto view = get_view(index)) { + if (view->get_buffer()->get_modified()) { + if (!save_modified_dialog(index)) + return false; } - } + if (view == get_current_view()) { + bool focused = false; + if (last_index != static_cast<size_t>(-1)) { + auto notebook_page = get_notebook_page(last_index); + if (notebook_page.first == get_notebook_page(get_index(view)).first) { + focus_view(source_views[last_index]); + notebooks[notebook_page.first].set_current_page(notebook_page.second); + last_index = -1; + focused = true; + } + } + if (!focused) { + auto notebook_page = get_notebook_page(get_index(view)); + if (notebook_page.second > 0) + focus_view(get_view(notebook_page.first, notebook_page.second - 1)); + else { + size_t notebook_index = notebook_page.first == 0 ? 1 : 0; + if (notebooks[notebook_index].get_n_pages() > 0) + focus_view(get_view(notebook_index, notebooks[notebook_index].get_current_page())); + else + set_current_view(nullptr); + } + } + } else if (index == last_index) + last_index = -1; + else if (index < last_index && last_index != static_cast<size_t>(-1)) + last_index--; + + auto notebook_page = get_notebook_page(index); + notebooks[notebook_page.first].remove_page(notebook_page.second); + source_maps.erase(source_maps.begin() + index); + + if (on_close_page) + on_close_page(view); + + delete_cursor_locations(view); + + SelectionDialog::get() = nullptr; + CompletionDialog::get() = nullptr; + + if (auto clang_view = dynamic_cast<Source::ClangView *>(view)) + clang_view->async_delete(); + else + delete view; + source_views.erase(source_views.begin() + index); + scrolled_windows.erase(scrolled_windows.begin() + index); + hboxes.erase(hboxes.begin() + index); + tab_labels.erase(tab_labels.begin() + index); } - else if(index==last_index) - last_index=-1; - else if(index<last_index && last_index!=static_cast<size_t>(-1)) - last_index--; - - auto notebook_page=get_notebook_page(index); - notebooks[notebook_page.first].remove_page(notebook_page.second); - source_maps.erase(source_maps.begin()+index); - - if(on_close_page) - on_close_page(view); - - delete_cursor_locations(view); - - SelectionDialog::get()=nullptr; - CompletionDialog::get()=nullptr; - - if(auto clang_view=dynamic_cast<Source::ClangView*>(view)) - clang_view->async_delete(); - else - delete view; - source_views.erase(source_views.begin()+index); - scrolled_windows.erase(scrolled_windows.begin()+index); - hboxes.erase(hboxes.begin()+index); - tab_labels.erase(tab_labels.begin()+index); - } - return true; + return true; } void Notebook::delete_cursor_locations(Source::View *view) { - size_t cursor_locations_index=0; - for(auto it=cursor_locations.begin();it!=cursor_locations.end();) { - if(it->view==view) { - view->get_buffer()->delete_mark(it->mark); - it=cursor_locations.erase(it); - if(current_cursor_location!=static_cast<size_t>(-1) && current_cursor_location>cursor_locations_index) - --current_cursor_location; - } - else { - ++it; - ++cursor_locations_index; + size_t cursor_locations_index = 0; + for (auto it = cursor_locations.begin(); it != cursor_locations.end();) { + if (it->view == view) { + view->get_buffer()->delete_mark(it->mark); + it = cursor_locations.erase(it); + if (current_cursor_location != static_cast<size_t>(-1) && current_cursor_location > cursor_locations_index) + --current_cursor_location; + } else { + ++it; + ++cursor_locations_index; + } } - } - if(current_cursor_location>=cursor_locations.size()) - current_cursor_location=cursor_locations.size()-1; + if (current_cursor_location >= cursor_locations.size()) + current_cursor_location = cursor_locations.size() - 1; } bool Notebook::close_current() { - return close(get_index(get_current_view())); + return close(get_index(get_current_view())); } void Notebook::next() { - if(auto view=get_current_view()) { - auto notebook_page=get_notebook_page(get_index(view)); - int page=notebook_page.second+1; - if(page>=notebooks[notebook_page.first].get_n_pages()) - notebooks[notebook_page.first].set_current_page(0); - else - notebooks[notebook_page.first].set_current_page(page); - } + if (auto view = get_current_view()) { + auto notebook_page = get_notebook_page(get_index(view)); + int page = notebook_page.second + 1; + if (page >= notebooks[notebook_page.first].get_n_pages()) + notebooks[notebook_page.first].set_current_page(0); + else + notebooks[notebook_page.first].set_current_page(page); + } } void Notebook::previous() { - if(auto view=get_current_view()) { - auto notebook_page=get_notebook_page(get_index(view)); - int page=notebook_page.second-1; - if(page<0) - notebooks[notebook_page.first].set_current_page(notebooks[notebook_page.first].get_n_pages()-1); - else - notebooks[notebook_page.first].set_current_page(page); - } + if (auto view = get_current_view()) { + auto notebook_page = get_notebook_page(get_index(view)); + int page = notebook_page.second - 1; + if (page < 0) + notebooks[notebook_page.first].set_current_page(notebooks[notebook_page.first].get_n_pages() - 1); + else + notebooks[notebook_page.first].set_current_page(page); + } } void Notebook::toggle_split() { - if(!split) { - pack2(notebooks[1], true, true); - set_position(get_width()/2); - show_all(); - //Make sure the position is correct - //TODO: report bug to gtk if it is not fixed in gtk3.22 - Glib::signal_timeout().connect([this] { - set_position(get_width()/2); - return false; - }, 200); - } - else { - for(size_t c=size()-1;c!=static_cast<size_t>(-1);--c) { - auto notebook_index=get_notebook_page(c).first; - if(notebook_index==1 && !close(c)) - return; + if (!split) { + pack2(notebooks[1], true, true); + set_position(get_width() / 2); + show_all(); + //Make sure the position is correct + //TODO: report bug to gtk if it is not fixed in gtk3.22 + Glib::signal_timeout().connect([this] { + set_position(get_width() / 2); + return false; + }, 200); + } else { + for (size_t c = size() - 1; c != static_cast<size_t>(-1); --c) { + auto notebook_index = get_notebook_page(c).first; + if (notebook_index == 1 && !close(c)) + return; + } + remove(notebooks[1]); } - remove(notebooks[1]); - } - split=!split; + split = !split; } + void Notebook::toggle_tabs() { - //Show / Hide tabs for each notebook. - for(auto ¬ebook : Notebook::notebooks) - notebook.set_show_tabs(!notebook.get_show_tabs()); + //Show / Hide tabs for each notebook. + for (auto ¬ebook : Notebook::notebooks) + notebook.set_show_tabs(!notebook.get_show_tabs()); } boost::filesystem::path Notebook::get_current_folder() { - if(!Directories::get().path.empty()) - return Directories::get().path; - else if(auto view=get_current_view()) - return view->file_path.parent_path(); - else - return boost::filesystem::path(); + if (!Directories::get().path.empty()) + return Directories::get().path; + else if (auto view = get_current_view()) + return view->file_path.parent_path(); + else + return boost::filesystem::path(); } std::vector<std::pair<size_t, Source::View *>> Notebook::get_notebook_views() { - std::vector<std::pair<size_t, Source::View *>> notebook_views; - for(size_t notebook_index=0;notebook_index<notebooks.size();++notebook_index) { - for(int page=0;page<notebooks[notebook_index].get_n_pages();++page) { - if(auto view=get_view(notebook_index, page)) - notebook_views.emplace_back(notebook_index, view); + std::vector<std::pair<size_t, Source::View *>> notebook_views; + for (size_t notebook_index = 0; notebook_index < notebooks.size(); ++notebook_index) { + for (int page = 0; page < notebooks[notebook_index].get_n_pages(); ++page) { + if (auto view = get_view(notebook_index, page)) + notebook_views.emplace_back(notebook_index, view); + } } - } - return notebook_views; + return notebook_views; } void Notebook::update_status(Source::BaseView *view) { - if(view->update_status_location) - view->update_status_location(view); - if(view->update_status_file_path) - view->update_status_file_path(view); - if(view->update_status_branch) - view->update_status_branch(view); - if(view->update_status_diagnostics) - view->update_status_diagnostics(view); - if(view->update_status_state) - view->update_status_state(view); + if (view->update_status_location) + view->update_status_location(view); + if (view->update_status_file_path) + view->update_status_file_path(view); + if (view->update_status_branch) + view->update_status_branch(view); + if (view->update_status_diagnostics) + view->update_status_diagnostics(view); + if (view->update_status_state) + view->update_status_state(view); } void Notebook::clear_status() { - status_location.set_text(""); - status_file_path.set_text(""); - status_branch.set_text(""); - status_diagnostics.set_text(""); - status_state.set_text(""); + status_location.set_text(""); + status_file_path.set_text(""); + status_branch.set_text(""); + status_diagnostics.set_text(""); + status_state.set_text(""); } size_t Notebook::get_index(Source::View *view) { - for(size_t c=0;c<size();++c) { - if(source_views[c]==view) - return c; - } - return -1; + for (size_t c = 0; c < size(); ++c) { + if (source_views[c] == view) + return c; + } + return -1; } Source::View *Notebook::get_view(size_t notebook_index, int page) { - if(notebook_index==static_cast<size_t>(-1) || notebook_index>=notebooks.size() || - page<0 || page>=notebooks[notebook_index].get_n_pages()) - return nullptr; - auto hbox=dynamic_cast<Gtk::Box*>(notebooks[notebook_index].get_nth_page(page)); - auto scrolled_window=dynamic_cast<Gtk::ScrolledWindow*>(hbox->get_children()[0]); - return dynamic_cast<Source::View*>(scrolled_window->get_children()[0]); + if (notebook_index == static_cast<size_t>(-1) || notebook_index >= notebooks.size() || + page < 0 || page >= notebooks[notebook_index].get_n_pages()) + return nullptr; + auto hbox = dynamic_cast<Gtk::Box *>(notebooks[notebook_index].get_nth_page(page)); + auto scrolled_window = dynamic_cast<Gtk::ScrolledWindow *>(hbox->get_children()[0]); + return dynamic_cast<Source::View *>(scrolled_window->get_children()[0]); } void Notebook::focus_view(Source::View *view) { - intermediate_view=view; - view->grab_focus(); + intermediate_view = view; + view->grab_focus(); } std::pair<size_t, int> Notebook::get_notebook_page(size_t index) { - if(index>=hboxes.size()) + if (index >= hboxes.size()) + return {-1, -1}; + for (size_t c = 0; c < notebooks.size(); ++c) { + auto page_num = notebooks[c].page_num(*hboxes[index]); + if (page_num >= 0) + return {c, page_num}; + } return {-1, -1}; - for(size_t c=0;c<notebooks.size();++c) { - auto page_num=notebooks[c].page_num(*hboxes[index]); - if(page_num>=0) - return {c, page_num}; - } - return {-1, -1}; } void Notebook::set_current_view(Source::View *view) { - intermediate_view=nullptr; - if(current_view!=view) { - if(auto view=get_current_view()) { - view->hide_tooltips(); - view->hide_dialogs(); + intermediate_view = nullptr; + if (current_view != view) { + if (auto view = get_current_view()) { + view->hide_tooltips(); + view->hide_dialogs(); + } + current_view = view; + if (view && on_change_page) + on_change_page(view); } - current_view=view; - if(view && on_change_page) - on_change_page(view); - } } bool Notebook::save_modified_dialog(size_t index) { - Gtk::MessageDialog dialog(*static_cast<Gtk::Window*>(get_toplevel()), "Save file!", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO); - dialog.set_default_response(Gtk::RESPONSE_YES); - dialog.set_secondary_text("Do you want to save: " + get_view(index)->file_path.string()+" ?"); - int result = dialog.run(); - if(result==Gtk::RESPONSE_YES) { - return save(index); - } - else if(result==Gtk::RESPONSE_NO) { - return true; - } - else { - return false; - } + Gtk::MessageDialog dialog(*static_cast<Gtk::Window *>(get_toplevel()), "Save file!", false, Gtk::MESSAGE_QUESTION, + Gtk::BUTTONS_YES_NO); + dialog.set_default_response(Gtk::RESPONSE_YES); + dialog.set_secondary_text("Do you want to save: " + get_view(index)->file_path.string() + " ?"); + int result = dialog.run(); + if (result == Gtk::RESPONSE_YES) { + return save(index); + } else if (result == Gtk::RESPONSE_NO) { + return true; + } else { + return false; + } } diff --git a/src/notebook.h b/src/notebook.h index 1a601f1c..9bf1cdc4 100644 --- a/src/notebook.h +++ b/src/notebook.h @@ -1,4 +1,5 @@ #pragma once + #include <iostream> #include "gtkmm.h" #include "source.h" @@ -7,82 +8,106 @@ #include <sigc++/sigc++.h> class Notebook : public Gtk::Paned { - class TabLabel : public Gtk::EventBox { - public: - TabLabel(std::function<void()> on_close); - Gtk::Label label; - }; - - class CursorLocation { - public: - CursorLocation(Source::View *view, Glib::RefPtr<Gtk::TextBuffer::Mark> mark) : view(view), mark(mark) {} - Source::View *view; - Glib::RefPtr<Gtk::TextBuffer::Mark> mark; - }; - + class TabLabel : public Gtk::EventBox { + public: + TabLabel(std::function<void()> on_close); + + Gtk::Label label; + }; + + class CursorLocation { + public: + CursorLocation(Source::View *view, Glib::RefPtr<Gtk::TextBuffer::Mark> mark) : view(view), mark(mark) {} + + Source::View *view; + Glib::RefPtr<Gtk::TextBuffer::Mark> mark; + }; + private: - Notebook(); + Notebook(); + public: - static Notebook &get() { - static Notebook singleton; - return singleton; - } - - size_t size(); - Source::View* get_view(size_t index); - Source::View* get_current_view(); - std::vector<Source::View*> &get_views(); - - void open(const boost::filesystem::path &file_path, size_t notebook_index=-1); - void configure(size_t index); - bool save(size_t index); - bool save_current(); - bool close(size_t index); - bool close_current(); - void next(); - void previous(); - void toggle_split(); - /// Hide/Show tabs. - void toggle_tabs(); - boost::filesystem::path get_current_folder(); - std::vector<std::pair<size_t, Source::View*>> get_notebook_views(); - - Gtk::Label status_location; - Gtk::Label status_file_path; - Gtk::Label status_branch; - Gtk::Label status_diagnostics; - Gtk::Label status_state; - void update_status(Source::BaseView *view); - void clear_status(); - - std::function<void(Source::View*)> on_change_page; - std::function<void(Source::View*)> on_close_page; - - /// Cursor history - std::vector<CursorLocation> cursor_locations; - size_t current_cursor_location=-1; - bool disable_next_update_cursor_locations=false; - void delete_cursor_locations(Source::View *view); - + static Notebook &get() { + static Notebook singleton; + return singleton; + } + + size_t size(); + + Source::View *get_view(size_t index); + + Source::View *get_current_view(); + + std::vector<Source::View *> &get_views(); + + void open(const boost::filesystem::path &file_path, size_t notebook_index = -1); + + void configure(size_t index); + + bool save(size_t index); + + bool save_current(); + + bool close(size_t index); + + bool close_current(); + + void next(); + + void previous(); + + void toggle_split(); + + /// Hide/Show tabs. + void toggle_tabs(); + + boost::filesystem::path get_current_folder(); + + std::vector<std::pair<size_t, Source::View *>> get_notebook_views(); + + Gtk::Label status_location; + Gtk::Label status_file_path; + Gtk::Label status_branch; + Gtk::Label status_diagnostics; + Gtk::Label status_state; + + void update_status(Source::BaseView *view); + + void clear_status(); + + std::function<void(Source::View *)> on_change_page; + std::function<void(Source::View *)> on_close_page; + + /// Cursor history + std::vector<CursorLocation> cursor_locations; + size_t current_cursor_location = -1; + bool disable_next_update_cursor_locations = false; + + void delete_cursor_locations(Source::View *view); + private: - size_t get_index(Source::View *view); - Source::View *get_view(size_t notebook_index, int page); - void focus_view(Source::View *view); - std::pair<size_t, int> get_notebook_page(size_t index); - - std::vector<Gtk::Notebook> notebooks; - std::vector<Source::View*> source_views; //Is NOT freed in destructor, this is intended for quick program exit. - std::vector<std::unique_ptr<Gtk::Widget> > source_maps; - std::vector<std::unique_ptr<Gtk::ScrolledWindow> > scrolled_windows; - std::vector<std::unique_ptr<Gtk::Box> > hboxes; - std::vector<std::unique_ptr<TabLabel> > tab_labels; - - bool split=false; - size_t last_index=-1; - - void set_current_view(Source::View *view); - Source::View* current_view=nullptr; - Source::View* intermediate_view=nullptr; - - bool save_modified_dialog(size_t index); + size_t get_index(Source::View *view); + + Source::View *get_view(size_t notebook_index, int page); + + void focus_view(Source::View *view); + + std::pair<size_t, int> get_notebook_page(size_t index); + + std::vector<Gtk::Notebook> notebooks; + std::vector<Source::View *> source_views; //Is NOT freed in destructor, this is intended for quick program exit. + std::vector<std::unique_ptr<Gtk::Widget> > source_maps; + std::vector<std::unique_ptr<Gtk::ScrolledWindow> > scrolled_windows; + std::vector<std::unique_ptr<Gtk::Box> > hboxes; + std::vector<std::unique_ptr<TabLabel> > tab_labels; + + bool split = false; + size_t last_index = -1; + + void set_current_view(Source::View *view); + + Source::View *current_view = nullptr; + Source::View *intermediate_view = nullptr; + + bool save_modified_dialog(size_t index); }; diff --git a/src/project.cc b/src/project.cc index 508ffcdb..61976041 100644 --- a/src/project.cc +++ b/src/project.cc @@ -7,9 +7,11 @@ #include "menu.h" #include "notebook.h" #include "selection_dialog.h" + #ifdef JUCI_ENABLE_DEBUG #include "debug_lldb.h" #endif + #include "info.h" #include "source_clang.h" #include "source_language_protocol.h" @@ -28,229 +30,231 @@ std::shared_ptr<Project::Base> Project::current; std::unique_ptr<Project::DebugOptions> Project::Base::debug_options; Gtk::Label &Project::debug_status_label() { - static Gtk::Label label; - return label; + static Gtk::Label label; + return label; } void Project::save_files(const boost::filesystem::path &path) { - for(size_t c=0;c<Notebook::get().size();c++) { - auto view=Notebook::get().get_view(c); - if(view->get_buffer()->get_modified()) { - if(filesystem::file_in_path(view->file_path, path)) - Notebook::get().save(c); + for (size_t c = 0; c < Notebook::get().size(); c++) { + auto view = Notebook::get().get_view(c); + if (view->get_buffer()->get_modified()) { + if (filesystem::file_in_path(view->file_path, path)) + Notebook::get().save(c); + } } - } } void Project::on_save(size_t index) { - auto view=Notebook::get().get_view(index); - if(!view) - return; - boost::filesystem::path build_path; - if(view->language && view->language->get_id()=="cmake") { - if(view->file_path.filename()=="CMakeLists.txt") - build_path=view->file_path; - else - build_path=filesystem::find_file_in_path_parents("CMakeLists.txt", view->file_path.parent_path()); - } - else if(view->language && view->language->get_id()=="meson") { - if(view->file_path.filename()=="meson.build") - build_path=view->file_path; - else - build_path=filesystem::find_file_in_path_parents("meson.build", view->file_path.parent_path()); - } - - if(!build_path.empty()) { - auto build=Build::create(build_path); - if(dynamic_cast<CMakeBuild*>(build.get()) || dynamic_cast<MesonBuild*>(build.get())) { - build->update_default(true); - Usages::Clang::erase_all_caches_for_project(build->project_path, build->get_default_path()); - boost::system::error_code ec; - if(boost::filesystem::exists(build->get_debug_path()), ec) - build->update_debug(true); - - for(size_t c=0;c<Notebook::get().size();c++) { - auto source_view=Notebook::get().get_view(c); - if(auto source_clang_view=dynamic_cast<Source::ClangView*>(source_view)) { - if(filesystem::file_in_path(source_clang_view->file_path, build->project_path)) - source_clang_view->full_reparse_needed=true; + auto view = Notebook::get().get_view(index); + if (!view) + return; + boost::filesystem::path build_path; + if (view->language && view->language->get_id() == "cmake") { + if (view->file_path.filename() == "CMakeLists.txt") + build_path = view->file_path; + else + build_path = filesystem::find_file_in_path_parents("CMakeLists.txt", view->file_path.parent_path()); + } else if (view->language && view->language->get_id() == "meson") { + if (view->file_path.filename() == "meson.build") + build_path = view->file_path; + else + build_path = filesystem::find_file_in_path_parents("meson.build", view->file_path.parent_path()); + } + + if (!build_path.empty()) { + auto build = Build::create(build_path); + if (dynamic_cast<CMakeBuild *>(build.get()) || dynamic_cast<MesonBuild *>(build.get())) { + build->update_default(true); + Usages::Clang::erase_all_caches_for_project(build->project_path, build->get_default_path()); + boost::system::error_code ec; + if (boost::filesystem::exists(build->get_debug_path()), ec) + build->update_debug(true); + + for (size_t c = 0; c < Notebook::get().size(); c++) { + auto source_view = Notebook::get().get_view(c); + if (auto source_clang_view = dynamic_cast<Source::ClangView *>(source_view)) { + if (filesystem::file_in_path(source_clang_view->file_path, build->project_path)) + source_clang_view->full_reparse_needed = true; + } + } } - } } - } } void Project::debug_update_status(const std::string &new_debug_status) { - debug_status=new_debug_status; - if(debug_status.empty()) - debug_status_label().set_text(""); - else - debug_status_label().set_text(debug_status); - debug_activate_menu_items(); + debug_status = new_debug_status; + if (debug_status.empty()) + debug_status_label().set_text(""); + else + debug_status_label().set_text(debug_status); + debug_activate_menu_items(); } void Project::debug_activate_menu_items() { - auto &menu=Menu::get(); - auto view=Notebook::get().get_current_view(); - 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_toggle_breakpoint"]->set_enabled(view && view->toggle_breakpoint); - menu.actions["debug_goto_stop"]->set_enabled(!debug_status.empty()); + auto &menu = Menu::get(); + auto view = Notebook::get().get_current_view(); + 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_toggle_breakpoint"]->set_enabled(view && view->toggle_breakpoint); + menu.actions["debug_goto_stop"]->set_enabled(!debug_status.empty()); } void Project::debug_update_stop() { - if(!debug_last_stop_file_path.empty()) { - for(size_t 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"); - view->get_source_buffer()->remove_source_marks(view->get_buffer()->begin(), view->get_buffer()->end(), "debug_breakpoint_and_stop"); - break; - } + if (!debug_last_stop_file_path.empty()) { + for (size_t 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"); + view->get_source_buffer()->remove_source_marks(view->get_buffer()->begin(), view->get_buffer()->end(), + "debug_breakpoint_and_stop"); + break; + } + } } - } - //Add debug stop source mark - debug_last_stop_file_path.clear(); - for(size_t 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<view->get_buffer()->get_line_count()) { - auto iter=view->get_buffer()->get_iter_at_line(debug_stop.second.first); - view->get_source_buffer()->create_source_mark("debug_stop", iter); - if(view->get_source_buffer()->get_source_marks_at_iter(iter, "debug_breakpoint").size()>0) - view->get_source_buffer()->create_source_mark("debug_breakpoint_and_stop", iter); - debug_last_stop_file_path=debug_stop.first; - } - break; + //Add debug stop source mark + debug_last_stop_file_path.clear(); + for (size_t 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 < view->get_buffer()->get_line_count()) { + auto iter = view->get_buffer()->get_iter_at_line(debug_stop.second.first); + view->get_source_buffer()->create_source_mark("debug_stop", iter); + if (view->get_source_buffer()->get_source_marks_at_iter(iter, "debug_breakpoint").size() > 0) + view->get_source_buffer()->create_source_mark("debug_breakpoint_and_stop", iter); + debug_last_stop_file_path = debug_stop.first; + } + break; + } } - } } std::shared_ptr<Project::Base> Project::create() { - std::unique_ptr<Project::Build> build; - - if(auto view=Notebook::get().get_current_view()) { - build=Build::create(view->file_path); - if(view->language) { - auto language_id=view->language->get_id(); - if(language_id=="markdown") - return std::shared_ptr<Project::Base>(new Project::Markdown(std::move(build))); - if(language_id=="python") - return std::shared_ptr<Project::Base>(new Project::Python(std::move(build))); - if(language_id=="js") + std::unique_ptr<Project::Build> build; + + if (auto view = Notebook::get().get_current_view()) { + build = Build::create(view->file_path); + if (view->language) { + auto language_id = view->language->get_id(); + if (language_id == "markdown") + return std::shared_ptr<Project::Base>(new Project::Markdown(std::move(build))); + if (language_id == "python") + return std::shared_ptr<Project::Base>(new Project::Python(std::move(build))); + if (language_id == "js") + return std::shared_ptr<Project::Base>(new Project::JavaScript(std::move(build))); + if (language_id == "html") + return std::shared_ptr<Project::Base>(new Project::HTML(std::move(build))); + } + } else + build = Build::create(Directories::get().path); + + if (dynamic_cast<CMakeBuild *>(build.get()) || dynamic_cast<MesonBuild *>(build.get())) + return std::shared_ptr<Project::Base>(new Project::Clang(std::move(build))); + else if (dynamic_cast<CargoBuild *>(build.get())) + return std::shared_ptr<Project::Base>(new Project::Rust(std::move(build))); + else if (dynamic_cast<NpmBuild *>(build.get())) return std::shared_ptr<Project::Base>(new Project::JavaScript(std::move(build))); - if(language_id=="html") - return std::shared_ptr<Project::Base>(new Project::HTML(std::move(build))); - } - } - else - build=Build::create(Directories::get().path); - - if(dynamic_cast<CMakeBuild*>(build.get()) || dynamic_cast<MesonBuild*>(build.get())) - return std::shared_ptr<Project::Base>(new Project::Clang(std::move(build))); - else if(dynamic_cast<CargoBuild*>(build.get())) - return std::shared_ptr<Project::Base>(new Project::Rust(std::move(build))); - else if(dynamic_cast<NpmBuild*>(build.get())) - return std::shared_ptr<Project::Base>(new Project::JavaScript(std::move(build))); - else - return std::shared_ptr<Project::Base>(new Project::Base(std::move(build))); + else + return std::shared_ptr<Project::Base>(new Project::Base(std::move(build))); } std::pair<std::string, std::string> Project::Base::get_run_arguments() { - Info::get().print("Could not find a supported project"); - return {"", ""}; + Info::get().print("Could not find a supported project"); + return {"", ""}; } void Project::Base::compile() { - Info::get().print("Could not find a supported project"); + Info::get().print("Could not find a supported project"); } void Project::Base::compile_and_run() { - Info::get().print("Could not find a supported project"); + Info::get().print("Could not find a supported project"); } void Project::Base::recreate_build() { - Info::get().print("Could not find a supported project"); + Info::get().print("Could not find a supported project"); } void Project::Base::show_symbols() { - auto view=Notebook::get().get_current_view(); - - boost::filesystem::path search_path; - if(view) - search_path=view->file_path.parent_path(); - else if(!Directories::get().path.empty()) - search_path=Directories::get().path; - else { - boost::system::error_code ec; - search_path=boost::filesystem::current_path(ec); - if(ec) { - Terminal::get().print("Error: could not find current path\n", true); - return; + auto view = Notebook::get().get_current_view(); + + boost::filesystem::path search_path; + if (view) + search_path = view->file_path.parent_path(); + else if (!Directories::get().path.empty()) + search_path = Directories::get().path; + else { + boost::system::error_code ec; + search_path = boost::filesystem::current_path(ec); + if (ec) { + Terminal::get().print("Error: could not find current path\n", true); + return; + } } - } - auto pair=Ctags::get_result(search_path); - - auto path=std::move(pair.first); - auto stream=std::move(pair.second); - stream->seekg(0, std::ios::end); - if(stream->tellg()==0) { - Info::get().print("No symbols found in current project"); - return; - } - stream->seekg(0, std::ios::beg); - - if(view) { - auto dialog_iter=view->get_iter_for_dialog(); - SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); - } - else - SelectionDialog::create(true, true); - - std::vector<Source::Offset> rows; - - std::string line; - while(std::getline(*stream, line)) { - auto location=Ctags::get_location(line, true); - - std::string row=location.file_path.string()+":"+std::to_string(location.line+1)+": "+location.source; - rows.emplace_back(Source::Offset(location.line, location.index, location.file_path)); - SelectionDialog::get()->add_row(row); - } - - if(rows.size()==0) - return; - SelectionDialog::get()->on_select=[rows=std::move(rows), path=std::move(path)](unsigned int index, const std::string &text, bool hide_window) { - if(index>=rows.size()) - return; - auto offset=rows[index]; - auto full_path=path/offset.file_path; - if(!boost::filesystem::is_regular_file(full_path)) - return; - Notebook::get().open(full_path); - auto view=Notebook::get().get_current_view(); - view->place_cursor_at_line_index(offset.line, offset.index); - view->scroll_to_cursor_delayed(view, true, false); - view->hide_tooltips(); - }; - if(view) - view->hide_tooltips(); - SelectionDialog::get()->show(); + auto pair = Ctags::get_result(search_path); + + auto path = std::move(pair.first); + auto stream = std::move(pair.second); + stream->seekg(0, std::ios::end); + if (stream->tellg() == 0) { + Info::get().print("No symbols found in current project"); + return; + } + stream->seekg(0, std::ios::beg); + + if (view) { + auto dialog_iter = view->get_iter_for_dialog(); + SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); + } else + SelectionDialog::create(true, true); + + std::vector<Source::Offset> rows; + + std::string line; + while (std::getline(*stream, line)) { + auto location = Ctags::get_location(line, true); + + std::string row = + location.file_path.string() + ":" + std::to_string(location.line + 1) + ": " + location.source; + rows.emplace_back(Source::Offset(location.line, location.index, location.file_path)); + SelectionDialog::get()->add_row(row); + } + + if (rows.size() == 0) + return; + SelectionDialog::get()->on_select = [rows = std::move(rows), path = std::move(path)](unsigned int index, + const std::string &text, + bool hide_window) { + if (index >= rows.size()) + return; + auto offset = rows[index]; + auto full_path = path / offset.file_path; + if (!boost::filesystem::is_regular_file(full_path)) + return; + Notebook::get().open(full_path); + auto view = Notebook::get().get_current_view(); + view->place_cursor_at_line_index(offset.line, offset.index); + view->scroll_to_cursor_delayed(view, true, false); + view->hide_tooltips(); + }; + if (view) + view->hide_tooltips(); + SelectionDialog::get()->show(); } std::pair<std::string, std::string> Project::Base::debug_get_run_arguments() { - Info::get().print("Could not find a supported project"); - return {"", ""}; + Info::get().print("Could not find a supported project"); + return {"", ""}; } void Project::Base::debug_start() { - Info::get().print("Could not find a supported project"); + Info::get().print("Could not find a supported project"); } #ifdef JUCI_ENABLE_DEBUG @@ -667,351 +671,371 @@ void Project::LLDB::debug_cancel() { #endif void Project::LanguageProtocol::show_symbols() { - if(build->project_path.empty()) { - Info::get().print("Could not find project folder"); - return; - } - - auto language_id=get_language_id(); - auto executable_name=language_id+"-language-server"; - if(filesystem::find_executable(executable_name).empty()) { - Info::get().print("Executable "+executable_name+" not found"); - return; - } - - auto project_path=std::make_shared<boost::filesystem::path>(build->project_path); - - auto client=::LanguageProtocol::Client::get(*project_path, language_id); - auto capabilities=client->initialize(nullptr); - - if(!capabilities.workspace_symbol) { - Info::get().print("Language server does not support workspace/symbol"); - return; - } - - auto view=Notebook::get().get_current_view(); - if(view) { - auto dialog_iter=view->get_iter_for_dialog(); - SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); - } - else - SelectionDialog::create(true, true); - - SelectionDialog::get()->on_hide=[] { - SelectionDialog::get()->on_search_entry_changed=nullptr; // To delete client object - }; - - auto offsets=std::make_shared<std::vector<Source::Offset>>(); - SelectionDialog::get()->on_search_entry_changed=[client, project_path, offsets](const std::string &text) { - if(text.size()>1) - return; - else { - offsets->clear(); - SelectionDialog::get()->erase_rows(); - if(text.empty()) + if (build->project_path.empty()) { + Info::get().print("Could not find project folder"); return; } - std::vector<std::string> names; - std::promise<void> result_processed; - client->write_request(nullptr, "workspace/symbol", "\"query\":\""+text+"\"", [&result_processed, &names, offsets, project_path](const boost::property_tree::ptree &result, bool error) { - if(!error) { - for(auto it=result.begin();it!=result.end();++it) { - auto name=it->second.get<std::string>("name", ""); - if(!name.empty()) { - auto location_it=it->second.find("location"); - if(location_it!=it->second.not_found()) { - auto file=location_it->second.get<std::string>("uri", ""); - if(file.size()>7) { - file.erase(0, 7); - auto range_it=location_it->second.find("range"); - if(range_it!=location_it->second.not_found()) { - auto start_it=range_it->second.find("start"); - if(start_it!=range_it->second.not_found()) { - try { - offsets->emplace_back(Source::Offset(start_it->second.get<unsigned>("line"), start_it->second.get<unsigned>("character"), file)); - names.emplace_back(name); - } - catch(...) {} - } - } - } - } - } + + auto language_id = get_language_id(); + auto executable_name = language_id + "-language-server"; + if (filesystem::find_executable(executable_name).empty()) { + Info::get().print("Executable " + executable_name + " not found"); + return; + } + + auto project_path = std::make_shared<boost::filesystem::path>(build->project_path); + + auto client = ::LanguageProtocol::Client::get(*project_path, language_id); + auto capabilities = client->initialize(nullptr); + + if (!capabilities.workspace_symbol) { + Info::get().print("Language server does not support workspace/symbol"); + return; + } + + auto view = Notebook::get().get_current_view(); + if (view) { + auto dialog_iter = view->get_iter_for_dialog(); + SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); + } else + SelectionDialog::create(true, true); + + SelectionDialog::get()->on_hide = [] { + SelectionDialog::get()->on_search_entry_changed = nullptr; // To delete client object + }; + + auto offsets = std::make_shared<std::vector<Source::Offset>>(); + SelectionDialog::get()->on_search_entry_changed = [client, project_path, offsets](const std::string &text) { + if (text.size() > 1) + return; + else { + offsets->clear(); + SelectionDialog::get()->erase_rows(); + if (text.empty()) + return; } - } - result_processed.set_value(); - }); - result_processed.get_future().get(); - for(size_t c=0;c<offsets->size() && c<names.size();++c) - SelectionDialog::get()->add_row(filesystem::get_relative_path((*offsets)[c].file_path, *project_path).string()+':'+std::to_string((*offsets)[c].line+1)+':'+std::to_string((*offsets)[c].index+1)+": "+names[c]); - }; - - SelectionDialog::get()->on_select=[offsets](unsigned int index, const std::string &text, bool hide_window) { - auto &offset=(*offsets)[index]; - if(!boost::filesystem::is_regular_file(offset.file_path)) - return; - Notebook::get().open(offset.file_path); - auto view=Notebook::get().get_current_view(); - view->place_cursor_at_line_offset(offset.line, offset.index); - view->scroll_to_cursor_delayed(view, true, false); - view->hide_tooltips(); - }; - - if(view) - view->hide_tooltips(); - SelectionDialog::get()->show(); + std::vector<std::string> names; + std::promise<void> result_processed; + client->write_request(nullptr, "workspace/symbol", "\"query\":\"" + text + "\"", + [&result_processed, &names, offsets, project_path]( + const boost::property_tree::ptree &result, bool error) { + if (!error) { + for (auto it = result.begin(); it != result.end(); ++it) { + auto name = it->second.get<std::string>("name", ""); + if (!name.empty()) { + auto location_it = it->second.find("location"); + if (location_it != it->second.not_found()) { + auto file = location_it->second.get<std::string>("uri", ""); + if (file.size() > 7) { + file.erase(0, 7); + auto range_it = location_it->second.find("range"); + if (range_it != location_it->second.not_found()) { + auto start_it = range_it->second.find("start"); + if (start_it != range_it->second.not_found()) { + try { + offsets->emplace_back(Source::Offset( + start_it->second.get<unsigned>("line"), + start_it->second.get<unsigned>("character"), + file)); + names.emplace_back(name); + } + catch (...) {} + } + } + } + } + } + } + } + result_processed.set_value(); + }); + result_processed.get_future().get(); + for (size_t c = 0; c < offsets->size() && c < names.size(); ++c) + SelectionDialog::get()->add_row( + filesystem::get_relative_path((*offsets)[c].file_path, *project_path).string() + ':' + + std::to_string((*offsets)[c].line + 1) + ':' + std::to_string((*offsets)[c].index + 1) + ": " + + names[c]); + }; + + SelectionDialog::get()->on_select = [offsets](unsigned int index, const std::string &text, bool hide_window) { + auto &offset = (*offsets)[index]; + if (!boost::filesystem::is_regular_file(offset.file_path)) + return; + Notebook::get().open(offset.file_path); + auto view = Notebook::get().get_current_view(); + view->place_cursor_at_line_offset(offset.line, offset.index); + view->scroll_to_cursor_delayed(view, true, false); + view->hide_tooltips(); + }; + + if (view) + view->hide_tooltips(); + SelectionDialog::get()->show(); } std::pair<std::string, std::string> Project::Clang::get_run_arguments() { - auto build_path=build->get_default_path(); - if(build_path.empty()) - return {"", ""}; - - auto project_path=build->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 view=Notebook::get().get_current_view(); - auto executable=build->get_executable(view?view->file_path:Directories::get().path); - - if(!executable.empty()) - arguments=filesystem::escape_argument(filesystem::get_short_path(executable).string()); - else - arguments=filesystem::escape_argument(filesystem::get_short_path(build->get_default_path()).string()); - } - - return {project_path, arguments}; + auto build_path = build->get_default_path(); + if (build_path.empty()) + return {"", ""}; + + auto project_path = build->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 view = Notebook::get().get_current_view(); + auto executable = build->get_executable(view ? view->file_path : Directories::get().path); + + if (!executable.empty()) + arguments = filesystem::escape_argument(filesystem::get_short_path(executable).string()); + else + arguments = filesystem::escape_argument(filesystem::get_short_path(build->get_default_path()).string()); + } + + return {project_path, arguments}; } void Project::Clang::compile() { - auto default_build_path=build->get_default_path(); - if(default_build_path.empty() || !build->update_default()) - return; - - compiling=true; - - if(Config::get().project.clear_terminal_on_compile) - Terminal::get().clear(); - - Terminal::get().print("Compiling project "+filesystem::get_short_path(build->project_path).string()+"\n"); - Terminal::get().async_process(build->get_compile_command(), default_build_path, [](int exit_status) { - compiling=false; - }); + auto default_build_path = build->get_default_path(); + if (default_build_path.empty() || !build->update_default()) + return; + + compiling = true; + + if (Config::get().project.clear_terminal_on_compile) + Terminal::get().clear(); + + Terminal::get().print("Compiling project " + filesystem::get_short_path(build->project_path).string() + "\n"); + Terminal::get().async_process(build->get_compile_command(), default_build_path, [](int exit_status) { + compiling = false; + }); } void Project::Clang::compile_and_run() { - auto default_build_path=build->get_default_path(); - if(default_build_path.empty() || !build->update_default()) - return; - - auto project_path=build->project_path; - - 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()) { - auto view=Notebook::get().get_current_view(); - auto executable=build->get_executable(view?view->file_path:Directories::get().path); - if(executable.empty()) { - Terminal::get().print("Warning: could not find executable.\n"); - Terminal::get().print("Solution: either use Project Set Run Arguments, or open a source file within a directory where an executable is defined.\n", true); - return; - } - arguments=filesystem::escape_argument(filesystem::get_short_path(executable).string()); - } - - compiling=true; - - if(Config::get().project.clear_terminal_on_compile) - Terminal::get().clear(); - - Terminal::get().print("Compiling and running "+arguments+"\n"); - Terminal::get().async_process(build->get_compile_command(), default_build_path, [arguments, project_path](int exit_status){ - compiling=false; - if(exit_status==EXIT_SUCCESS) { - Terminal::get().async_process(arguments, project_path, [arguments](int exit_status){ - Terminal::get().async_print(arguments+" returned: "+std::to_string(exit_status)+'\n'); - }); + auto default_build_path = build->get_default_path(); + if (default_build_path.empty() || !build->update_default()) + return; + + auto project_path = build->project_path; + + 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()) { + auto view = Notebook::get().get_current_view(); + auto executable = build->get_executable(view ? view->file_path : Directories::get().path); + if (executable.empty()) { + Terminal::get().print("Warning: could not find executable.\n"); + Terminal::get().print( + "Solution: either use Project Set Run Arguments, or open a source file within a directory where an executable is defined.\n", + true); + return; + } + arguments = filesystem::escape_argument(filesystem::get_short_path(executable).string()); } - }); + + compiling = true; + + if (Config::get().project.clear_terminal_on_compile) + Terminal::get().clear(); + + Terminal::get().print("Compiling and running " + arguments + "\n"); + Terminal::get().async_process(build->get_compile_command(), default_build_path, + [arguments, project_path](int exit_status) { + compiling = false; + if (exit_status == EXIT_SUCCESS) { + Terminal::get().async_process(arguments, project_path, + [arguments](int exit_status) { + Terminal::get().async_print( + arguments + " returned: " + + std::to_string(exit_status) + '\n'); + }); + } + }); } void Project::Clang::recreate_build() { - if(build->project_path.empty()) - return; - auto default_build_path=build->get_default_path(); - if(default_build_path.empty()) - return; - - auto debug_build_path=build->get_debug_path(); - bool has_default_build=boost::filesystem::exists(default_build_path); - bool has_debug_build=!debug_build_path.empty() && boost::filesystem::exists(debug_build_path); - - if(has_default_build || has_debug_build) { - Gtk::MessageDialog dialog(*static_cast<Gtk::Window*>(Notebook::get().get_toplevel()), "Recreate Build", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO); - dialog.set_default_response(Gtk::RESPONSE_NO); - std::string message="Are you sure you want to recreate "; - if(has_default_build) - message+=default_build_path.string(); - if(has_debug_build) { - if(has_default_build) - message+=" and "; - message+=debug_build_path.string(); - } - dialog.set_secondary_text(message+"?"); - if(dialog.run()!=Gtk::RESPONSE_YES) - return; - Usages::Clang::erase_all_caches_for_project(build->project_path, default_build_path); - try { - if(has_default_build) - boost::filesystem::remove_all(default_build_path); - if(has_debug_build) - boost::filesystem::remove_all(debug_build_path); + if (build->project_path.empty()) + return; + auto default_build_path = build->get_default_path(); + if (default_build_path.empty()) + return; + + auto debug_build_path = build->get_debug_path(); + bool has_default_build = boost::filesystem::exists(default_build_path); + bool has_debug_build = !debug_build_path.empty() && boost::filesystem::exists(debug_build_path); + + if (has_default_build || has_debug_build) { + Gtk::MessageDialog dialog(*static_cast<Gtk::Window *>(Notebook::get().get_toplevel()), "Recreate Build", false, + Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO); + dialog.set_default_response(Gtk::RESPONSE_NO); + std::string message = "Are you sure you want to recreate "; + if (has_default_build) + message += default_build_path.string(); + if (has_debug_build) { + if (has_default_build) + message += " and "; + message += debug_build_path.string(); + } + dialog.set_secondary_text(message + "?"); + if (dialog.run() != Gtk::RESPONSE_YES) + return; + Usages::Clang::erase_all_caches_for_project(build->project_path, default_build_path); + try { + if (has_default_build) + boost::filesystem::remove_all(default_build_path); + if (has_debug_build) + boost::filesystem::remove_all(debug_build_path); + } + catch (const std::exception &e) { + Terminal::get().print(std::string("Error: could not remove build: ") + e.what() + "\n", true); + return; + } } - catch(const std::exception &e) { - Terminal::get().print(std::string("Error: could not remove build: ")+e.what()+"\n", true); - return; + + build->update_default(true); + if (has_debug_build) + build->update_debug(true); + + for (size_t c = 0; c < Notebook::get().size(); c++) { + auto source_view = Notebook::get().get_view(c); + if (auto source_clang_view = dynamic_cast<Source::ClangView *>(source_view)) { + if (filesystem::file_in_path(source_clang_view->file_path, build->project_path)) + source_clang_view->full_reparse_needed = true; + } } - } - - build->update_default(true); - if(has_debug_build) - build->update_debug(true); - - for(size_t c=0;c<Notebook::get().size();c++) { - auto source_view=Notebook::get().get_view(c); - if(auto source_clang_view=dynamic_cast<Source::ClangView*>(source_view)) { - if(filesystem::file_in_path(source_clang_view->file_path, build->project_path)) - source_clang_view->full_reparse_needed=true; + + if (auto view = Notebook::get().get_current_view()) { + if (view->full_reparse_needed) + view->full_reparse(); } - } - - if(auto view=Notebook::get().get_current_view()) { - if(view->full_reparse_needed) - view->full_reparse(); - } } Project::Markdown::~Markdown() { - if(!last_temp_path.empty()) { - boost::filesystem::remove(last_temp_path); - last_temp_path=boost::filesystem::path(); - } + 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, "command -v grip"); - if(exit_status==0) { - auto command="grip -b "+filesystem::escape_argument(filesystem::get_short_path(Notebook::get().get_current_view()->file_path).string()); - Terminal::get().print("Running: "+command+" in a quiet background process\n"); - Terminal::get().async_process(command, "", nullptr, true); - } - else - Terminal::get().print("Warning: install grip to preview Markdown files\n"); + 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, "command -v grip"); + if (exit_status == 0) { + auto command = "grip -b " + filesystem::escape_argument( + filesystem::get_short_path(Notebook::get().get_current_view()->file_path).string()); + Terminal::get().print("Running: " + command + " in a quiet background process\n"); + Terminal::get().async_process(command, "", nullptr, true); + } else + Terminal::get().print("Warning: install grip to preview Markdown files\n"); } void Project::Python::compile_and_run() { - auto command=Config::get().project.python_command+' '+filesystem::escape_argument(filesystem::get_short_path(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'); - }); + auto command = Config::get().project.python_command + ' ' + filesystem::escape_argument( + filesystem::get_short_path(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() { - std::string command; - boost::filesystem::path path; - if(!build->project_path.empty()) { - command="npm start"; - path=build->project_path; - } - else { - auto view=Notebook::get().get_current_view(); - if(!view) { - Info::get().print("No executable found"); - return; + std::string command; + boost::filesystem::path path; + if (!build->project_path.empty()) { + command = "npm start"; + path = build->project_path; + } else { + auto view = Notebook::get().get_current_view(); + if (!view) { + Info::get().print("No executable found"); + return; + } + command = "node --harmony " + filesystem::escape_argument(filesystem::get_short_path(view->file_path).string()); + path = view->file_path.parent_path(); } - command="node --harmony "+filesystem::escape_argument(filesystem::get_short_path(view->file_path).string()); - path=view->file_path.parent_path(); - } - Terminal::get().print("Running "+command+"\n"); - Terminal::get().async_process(command, path, [command](int exit_status) { - Terminal::get().async_print(command+" returned: "+std::to_string(exit_status)+'\n'); - }); + Terminal::get().print("Running " + command + "\n"); + Terminal::get().async_process(command, 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(); + auto uri = Notebook::get().get_current_view()->file_path.string(); #ifdef __APPLE__ - Terminal::get().process("open "+filesystem::escape_argument(uri)); + Terminal::get().process("open "+filesystem::escape_argument(uri)); #else #ifdef __linux - uri="file://"+uri; + uri="file://"+uri; #endif - GError* error=nullptr; + GError *error = nullptr; #if GTK_VERSION_GE(3, 22) - gtk_show_uri_on_window(nullptr, uri.c_str(), GDK_CURRENT_TIME, &error); + gtk_show_uri_on_window(nullptr, uri.c_str(), GDK_CURRENT_TIME, &error); #else - gtk_show_uri(nullptr, uri.c_str(), GDK_CURRENT_TIME, &error); + gtk_show_uri(nullptr, uri.c_str(), GDK_CURRENT_TIME, &error); #endif - g_clear_error(&error); + g_clear_error(&error); #endif } std::pair<std::string, std::string> Project::Rust::get_run_arguments() { - auto project_path=build->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()) - arguments=filesystem::get_short_path(build->get_executable(project_path)).string(); - - return {project_path, arguments}; + auto project_path = build->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()) + arguments = filesystem::get_short_path(build->get_executable(project_path)).string(); + + return {project_path, arguments}; } void Project::Rust::compile() { - compiling=true; - - if(Config::get().project.clear_terminal_on_compile) - Terminal::get().clear(); - - Terminal::get().print("Compiling project "+filesystem::get_short_path(build->project_path).string()+"\n"); - - auto command=build->get_compile_command(); - Terminal::get().async_process(command, build->project_path, [](int exit_status) { - compiling=false; - }); + compiling = true; + + if (Config::get().project.clear_terminal_on_compile) + Terminal::get().clear(); + + Terminal::get().print("Compiling project " + filesystem::get_short_path(build->project_path).string() + "\n"); + + auto command = build->get_compile_command(); + Terminal::get().async_process(command, build->project_path, [](int exit_status) { + compiling = false; + }); } void Project::Rust::compile_and_run() { - compiling=true; - - if(Config::get().project.clear_terminal_on_compile) - Terminal::get().clear(); - - auto arguments=get_run_arguments().second; - Terminal::get().print("Compiling and running "+arguments+"\n"); - - auto self=this->shared_from_this(); - Terminal::get().async_process(build->get_compile_command(), build->project_path, [self, arguments=std::move(arguments)](int exit_status) { - compiling=false; - if(exit_status==EXIT_SUCCESS) { - Terminal::get().async_process(arguments, self->build->project_path, [arguments](int exit_status) { - Terminal::get().async_print(arguments+" returned: "+std::to_string(exit_status)+'\n'); - }); - } - }); + compiling = true; + + if (Config::get().project.clear_terminal_on_compile) + Terminal::get().clear(); + + auto arguments = get_run_arguments().second; + Terminal::get().print("Compiling and running " + arguments + "\n"); + + auto self = this->shared_from_this(); + Terminal::get().async_process(build->get_compile_command(), build->project_path, + [self, arguments = std::move(arguments)](int exit_status) { + compiling = false; + if (exit_status == EXIT_SUCCESS) { + Terminal::get().async_process(arguments, self->build->project_path, + [arguments](int exit_status) { + Terminal::get().async_print( + arguments + " returned: " + + std::to_string(exit_status) + '\n'); + }); + } + }); } diff --git a/src/project.h b/src/project.h index d0db2c68..82c100ce 100644 --- a/src/project.h +++ b/src/project.h @@ -1,4 +1,5 @@ #pragma once + #include <gtkmm.h> #include <boost/filesystem.hpp> #include <atomic> @@ -9,161 +10,200 @@ #include "project_build.h" namespace Project { - class DebugRunArguments { - public: - std::string arguments; - bool remote_enabled; - std::string remote_host_port; - }; - - class DebugOptions : public Gtk::Popover { - public: - DebugOptions() : Gtk::Popover(), vbox(Gtk::Orientation::ORIENTATION_VERTICAL) { add(vbox); } - Gtk::Box vbox; - }; - - Gtk::Label &debug_status_label(); - void save_files(const boost::filesystem::path &path); - void on_save(size_t index); - - extern boost::filesystem::path debug_last_stop_file_path; - extern std::unordered_map<std::string, std::string> run_arguments; - extern std::unordered_map<std::string, DebugRunArguments> debug_run_arguments; - extern std::atomic<bool> compiling; - extern std::atomic<bool> debugging; - extern std::pair<boost::filesystem::path, std::pair<int, int> > debug_stop; - extern std::string debug_status; - void debug_update_status(const std::string &new_debug_status); - void debug_activate_menu_items(); - void debug_update_stop(); - - class Base : public std::enable_shared_from_this<Base> { - protected: - static std::unique_ptr<DebugOptions> debug_options; - public: - Base() {} - Base(std::unique_ptr<Build> &&build): build(std::move(build)) {} - virtual ~Base() {} - - std::unique_ptr<Build> build; - - Dispatcher dispatcher; - - virtual std::pair<std::string, std::string> get_run_arguments(); - virtual void compile(); - virtual void compile_and_run(); - virtual void recreate_build(); - - virtual void show_symbols(); - - virtual std::pair<std::string, std::string> debug_get_run_arguments(); - virtual Project::DebugOptions *debug_get_options() { return nullptr; } - 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_cancel() {} - }; - - class LLDB : public virtual Base { - public: - LLDB() {} - ~LLDB() { dispatcher.disconnect(); } - + class DebugRunArguments { + public: + std::string arguments; + bool remote_enabled; + std::string remote_host_port; + }; + + class DebugOptions : public Gtk::Popover { + public: + DebugOptions() : Gtk::Popover(), vbox(Gtk::Orientation::ORIENTATION_VERTICAL) { add(vbox); } + + Gtk::Box vbox; + }; + + Gtk::Label &debug_status_label(); + + void save_files(const boost::filesystem::path &path); + + void on_save(size_t index); + + extern boost::filesystem::path debug_last_stop_file_path; + extern std::unordered_map<std::string, std::string> run_arguments; + extern std::unordered_map<std::string, DebugRunArguments> debug_run_arguments; + extern std::atomic<bool> compiling; + extern std::atomic<bool> debugging; + extern std::pair<boost::filesystem::path, std::pair<int, int> > debug_stop; + extern std::string debug_status; + + void debug_update_status(const std::string &new_debug_status); + + void debug_activate_menu_items(); + + void debug_update_stop(); + + class Base : public std::enable_shared_from_this<Base> { + protected: + static std::unique_ptr<DebugOptions> debug_options; + public: + Base() {} + + Base(std::unique_ptr<Build> &&build) : build(std::move(build)) {} + + virtual ~Base() {} + + std::unique_ptr<Build> build; + + Dispatcher dispatcher; + + virtual std::pair<std::string, std::string> get_run_arguments(); + + virtual void compile(); + + virtual void compile_and_run(); + + virtual void recreate_build(); + + virtual void show_symbols(); + + virtual std::pair<std::string, std::string> debug_get_run_arguments(); + + virtual Project::DebugOptions *debug_get_options() { return nullptr; } + + 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_cancel() {} + }; + + class LLDB : public virtual Base { + public: + LLDB() {} + + ~LLDB() { dispatcher.disconnect(); } + #ifdef JUCI_ENABLE_DEBUG - std::pair<std::string, std::string> debug_get_run_arguments() override; - Project::DebugOptions *debug_get_options() 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_cancel() override; + std::pair<std::string, std::string> debug_get_run_arguments() override; + Project::DebugOptions *debug_get_options() 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_cancel() override; #endif - }; - - class LanguageProtocol : public virtual Base { - public: - LanguageProtocol() {} - virtual std::string get_language_id()=0; - void show_symbols() override; - }; - - class Clang : public LLDB { - public: - Clang(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} - - std::pair<std::string, std::string> get_run_arguments() override; - void compile() override; - void compile_and_run() override; - void recreate_build() override; - }; - - class Markdown : public Base { - public: - Markdown(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} - ~Markdown(); - - boost::filesystem::path last_temp_path; - void compile_and_run() override; - }; - - class Python : public LanguageProtocol { - public: - Python(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} - - void compile_and_run() override; - - std::string get_language_id() override { return "python"; } - }; - - class JavaScript : public LanguageProtocol { - public: - JavaScript(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} - - void compile_and_run() override; - - std::string get_language_id() override { return "javascript"; } - }; - - class HTML : public Base { - public: - HTML(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} - - void compile_and_run() override; - }; - - class Rust : public LLDB, public LanguageProtocol { - public: - Rust(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} - - std::pair<std::string, std::string> get_run_arguments() override; - void compile() override; - void compile_and_run() override; - - std::string get_language_id() override { return "rust"; } - }; - - std::shared_ptr<Base> create(); - extern std::shared_ptr<Base> current; + }; + + class LanguageProtocol : public virtual Base { + public: + LanguageProtocol() {} + + virtual std::string get_language_id()=0; + + void show_symbols() override; + }; + + class Clang : public LLDB { + public: + Clang(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} + + std::pair<std::string, std::string> get_run_arguments() override; + + void compile() override; + + void compile_and_run() override; + + void recreate_build() override; + }; + + class Markdown : public Base { + public: + Markdown(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} + + ~Markdown(); + + boost::filesystem::path last_temp_path; + + void compile_and_run() override; + }; + + class Python : public LanguageProtocol { + public: + Python(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} + + void compile_and_run() override; + + std::string get_language_id() override { return "python"; } + }; + + class JavaScript : public LanguageProtocol { + public: + JavaScript(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} + + void compile_and_run() override; + + std::string get_language_id() override { return "javascript"; } + }; + + class HTML : public Base { + public: + HTML(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} + + void compile_and_run() override; + }; + + class Rust : public LLDB, public LanguageProtocol { + public: + Rust(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} + + std::pair<std::string, std::string> get_run_arguments() override; + + void compile() override; + + void compile_and_run() override; + + std::string get_language_id() override { return "rust"; } + }; + + std::shared_ptr<Base> create(); + + extern std::shared_ptr<Base> current; }; diff --git a/src/project_build.cc b/src/project_build.cc index 5f313b3f..b756f592 100644 --- a/src/project_build.cc +++ b/src/project_build.cc @@ -3,136 +3,136 @@ #include "filesystem.h" std::unique_ptr<Project::Build> Project::Build::create(const boost::filesystem::path &path) { - auto search_path=boost::filesystem::is_directory(path)?path:path.parent_path(); - - while(true) { - if(boost::filesystem::exists(search_path/"CMakeLists.txt")) { - std::unique_ptr<Project::Build> build(new CMakeBuild(path)); - if(!build->project_path.empty()) - return build; - else - return std::make_unique<Project::Build>(); - } - - if(boost::filesystem::exists(search_path/"meson.build")) { - std::unique_ptr<Project::Build> build(new MesonBuild(path)); - if(!build->project_path.empty()) - return build; - } - - if(boost::filesystem::exists(search_path/"Cargo.toml")) { - std::unique_ptr<Project::Build> build(new CargoBuild()); - build->project_path=search_path; - return build; - } - - if(boost::filesystem::exists(search_path/"package.json")) { - std::unique_ptr<Project::Build> build(new NpmBuild()); - build->project_path=search_path; - return build; + auto search_path = boost::filesystem::is_directory(path) ? path : path.parent_path(); + + while (true) { + if (boost::filesystem::exists(search_path / "CMakeLists.txt")) { + std::unique_ptr<Project::Build> build(new CMakeBuild(path)); + if (!build->project_path.empty()) + return build; + else + return std::make_unique<Project::Build>(); + } + + if (boost::filesystem::exists(search_path / "meson.build")) { + std::unique_ptr<Project::Build> build(new MesonBuild(path)); + if (!build->project_path.empty()) + return build; + } + + if (boost::filesystem::exists(search_path / "Cargo.toml")) { + std::unique_ptr<Project::Build> build(new CargoBuild()); + build->project_path = search_path; + return build; + } + + if (boost::filesystem::exists(search_path / "package.json")) { + std::unique_ptr<Project::Build> build(new NpmBuild()); + build->project_path = search_path; + return build; + } + + if (search_path == search_path.root_directory()) + break; + search_path = search_path.parent_path(); } - - if(search_path==search_path.root_directory()) - break; - search_path=search_path.parent_path(); - } - - return std::make_unique<Project::Build>(); + + return std::make_unique<Project::Build>(); } boost::filesystem::path Project::Build::get_default_path() { - if(project_path.empty()) - return boost::filesystem::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; - auto default_build_path_string=default_build_path.string(); - auto path_filename_string=project_path.filename().string(); - while((pos=default_build_path_string.find(path_variable_project_directory_name, pos))!=std::string::npos) { - default_build_path_string.replace(pos, path_variable_project_directory_name.size(), path_filename_string); - pos+=path_filename_string.size(); - } - if(pos!=0) - default_build_path=default_build_path_string; - - if(default_build_path.is_relative()) - default_build_path=project_path/default_build_path; - - return filesystem::get_normal_path(default_build_path); + if (project_path.empty()) + return boost::filesystem::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; + auto default_build_path_string = default_build_path.string(); + auto path_filename_string = project_path.filename().string(); + while ((pos = default_build_path_string.find(path_variable_project_directory_name, pos)) != std::string::npos) { + default_build_path_string.replace(pos, path_variable_project_directory_name.size(), path_filename_string); + pos += path_filename_string.size(); + } + if (pos != 0) + default_build_path = default_build_path_string; + + if (default_build_path.is_relative()) + default_build_path = project_path / default_build_path; + + return filesystem::get_normal_path(default_build_path); } boost::filesystem::path Project::Build::get_debug_path() { - if(project_path.empty()) - return boost::filesystem::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; - auto debug_build_path_string=debug_build_path.string(); - auto path_filename_string=project_path.filename().string(); - while((pos=debug_build_path_string.find(path_variable_project_directory_name, pos))!=std::string::npos) { - debug_build_path_string.replace(pos, path_variable_project_directory_name.size(), path_filename_string); - pos+=path_filename_string.size(); - } - if(pos!=0) - debug_build_path=debug_build_path_string; - - const std::string path_variable_default_build_path="<default_build_path>"; - pos=0; - debug_build_path_string=debug_build_path.string(); - auto default_build_path=Config::get().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(); - } - if(pos!=0) - debug_build_path=debug_build_path_string; - - if(debug_build_path.is_relative()) - debug_build_path=project_path/debug_build_path; - - return filesystem::get_normal_path(debug_build_path); + if (project_path.empty()) + return boost::filesystem::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; + auto debug_build_path_string = debug_build_path.string(); + auto path_filename_string = project_path.filename().string(); + while ((pos = debug_build_path_string.find(path_variable_project_directory_name, pos)) != std::string::npos) { + debug_build_path_string.replace(pos, path_variable_project_directory_name.size(), path_filename_string); + pos += path_filename_string.size(); + } + if (pos != 0) + debug_build_path = debug_build_path_string; + + const std::string path_variable_default_build_path = "<default_build_path>"; + pos = 0; + debug_build_path_string = debug_build_path.string(); + auto default_build_path = Config::get().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(); + } + if (pos != 0) + debug_build_path = debug_build_path_string; + + if (debug_build_path.is_relative()) + debug_build_path = project_path / debug_build_path; + + return filesystem::get_normal_path(debug_build_path); } Project::CMakeBuild::CMakeBuild(const boost::filesystem::path &path) : Project::Build(), cmake(path) { - project_path=cmake.project_path; + project_path = cmake.project_path; } bool Project::CMakeBuild::update_default(bool force) { - return cmake.update_default_build(get_default_path(), force); + return cmake.update_default_build(get_default_path(), force); } bool Project::CMakeBuild::update_debug(bool force) { - return cmake.update_debug_build(get_debug_path(), force); + return cmake.update_debug_build(get_debug_path(), force); } std::string Project::CMakeBuild::get_compile_command() { - return Config::get().project.cmake.compile_command; + return Config::get().project.cmake.compile_command; } boost::filesystem::path Project::CMakeBuild::get_executable(const boost::filesystem::path &path) { - return cmake.get_executable(get_default_path(), path).string(); + return cmake.get_executable(get_default_path(), path).string(); } Project::MesonBuild::MesonBuild(const boost::filesystem::path &path) : Project::Build(), meson(path) { - project_path=meson.project_path; + project_path = meson.project_path; } bool Project::MesonBuild::update_default(bool force) { - return meson.update_default_build(get_default_path(), force); + return meson.update_default_build(get_default_path(), force); } bool Project::MesonBuild::update_debug(bool force) { - return meson.update_debug_build(get_debug_path(), force); + return meson.update_debug_build(get_debug_path(), force); } std::string Project::MesonBuild::get_compile_command() { - return Config::get().project.meson.compile_command; + return Config::get().project.meson.compile_command; } boost::filesystem::path Project::MesonBuild::get_executable(const boost::filesystem::path &path) { - return meson.get_executable(get_default_path(), path); + return meson.get_executable(get_default_path(), path); } diff --git a/src/project_build.h b/src/project_build.h index d2128db8..0900a851 100644 --- a/src/project_build.h +++ b/src/project_build.h @@ -1,61 +1,79 @@ #pragma once + #include <boost/filesystem.hpp> -#include "cmake.h" -#include "meson.h" +#include "buildsystem/cmake.h" +#include "buildsystem/meson.h" namespace Project { - class Build { - public: - Build() {} - virtual ~Build() {} - - boost::filesystem::path project_path; - - virtual boost::filesystem::path get_default_path(); - virtual bool update_default(bool force=false) {return false;} - virtual boost::filesystem::path get_debug_path(); - virtual bool update_debug(bool force=false) {return false;} - - virtual std::string get_compile_command() { return std::string(); } - virtual boost::filesystem::path get_executable(const boost::filesystem::path &path) {return boost::filesystem::path();} - - static std::unique_ptr<Build> create(const boost::filesystem::path &path); - }; - - class CMakeBuild : public Build { - ::CMake cmake; - public: - CMakeBuild(const boost::filesystem::path &path); - - bool update_default(bool force=false) override; - bool update_debug(bool force=false) override; - - std::string get_compile_command() override; - boost::filesystem::path get_executable(const boost::filesystem::path &path) override; - }; - - class MesonBuild : public Build { - Meson meson; - public: - MesonBuild(const boost::filesystem::path &path); - - bool update_default(bool force=false) override; - bool update_debug(bool force=false) override; - - std::string get_compile_command() override; - boost::filesystem::path get_executable(const boost::filesystem::path &path) override; - }; - - class CargoBuild : public Build { - public: - boost::filesystem::path get_default_path() override { return project_path/"target"/"debug"; } - bool update_default(bool force=false) override { return true; } - boost::filesystem::path get_debug_path() override { return get_default_path(); } - bool update_debug(bool force=false) override { return true; } - - std::string get_compile_command() override { return "cargo build"; } - boost::filesystem::path get_executable(const boost::filesystem::path &path) override { return get_debug_path()/project_path.filename(); } - }; - - class NpmBuild : public Build {}; + class Build { + public: + Build() {} + + virtual ~Build() {} + + boost::filesystem::path project_path; + + virtual boost::filesystem::path get_default_path(); + + virtual bool update_default(bool force = false) { return false; } + + virtual boost::filesystem::path get_debug_path(); + + virtual bool update_debug(bool force = false) { return false; } + + virtual std::string get_compile_command() { return std::string(); } + + virtual boost::filesystem::path + get_executable(const boost::filesystem::path &path) { return boost::filesystem::path(); } + + static std::unique_ptr<Build> create(const boost::filesystem::path &path); + }; + + class CMakeBuild : public Build { + ::CMake cmake; + public: + CMakeBuild(const boost::filesystem::path &path); + + bool update_default(bool force = false) override; + + bool update_debug(bool force = false) override; + + std::string get_compile_command() override; + + boost::filesystem::path get_executable(const boost::filesystem::path &path) override; + }; + + class MesonBuild : public Build { + Meson meson; + public: + MesonBuild(const boost::filesystem::path &path); + + bool update_default(bool force = false) override; + + bool update_debug(bool force = false) override; + + std::string get_compile_command() override; + + boost::filesystem::path get_executable(const boost::filesystem::path &path) override; + }; + + class CargoBuild : public Build { + public: + boost::filesystem::path get_default_path() override { return project_path / "target" / "debug"; } + + bool update_default(bool force = false) override { return true; } + + boost::filesystem::path get_debug_path() override { return get_default_path(); } + + bool update_debug(bool force = false) override { return true; } + + std::string get_compile_command() override { return "cargo build"; } + + boost::filesystem::path get_executable(const boost::filesystem::path &path) override { + return get_debug_path() / project_path.filename(); + } + }; + + class NpmBuild : public Build { + }; } diff --git a/src/selection_dialog.cc b/src/selection_dialog.cc index 38535d66..5b0461c3 100644 --- a/src/selection_dialog.cc +++ b/src/selection_dialog.cc @@ -2,451 +2,471 @@ #include <algorithm> SelectionDialogBase::ListViewText::ListViewText(bool use_markup) : Gtk::TreeView(), use_markup(use_markup) { - list_store = Gtk::ListStore::create(column_record); - set_model(list_store); - append_column("", cell_renderer); - if(use_markup) - get_column(0)->add_attribute(cell_renderer.property_markup(), column_record.text); - else - get_column(0)->add_attribute(cell_renderer.property_text(), column_record.text); - - get_selection()->set_mode(Gtk::SelectionMode::SELECTION_BROWSE); - set_enable_search(true); - set_headers_visible(false); - set_hscroll_policy(Gtk::ScrollablePolicy::SCROLL_NATURAL); - set_activate_on_single_click(true); - set_hover_selection(false); - set_rules_hint(true); + list_store = Gtk::ListStore::create(column_record); + set_model(list_store); + append_column("", cell_renderer); + if (use_markup) + get_column(0)->add_attribute(cell_renderer.property_markup(), column_record.text); + else + get_column(0)->add_attribute(cell_renderer.property_text(), column_record.text); + + get_selection()->set_mode(Gtk::SelectionMode::SELECTION_BROWSE); + set_enable_search(true); + set_headers_visible(false); + set_hscroll_policy(Gtk::ScrollablePolicy::SCROLL_NATURAL); + set_activate_on_single_click(true); + set_hover_selection(false); + set_rules_hint(true); } -void SelectionDialogBase::ListViewText::append(const std::string& value) { - auto new_row=list_store->append(); - new_row->set_value(column_record.text, value); - new_row->set_value(column_record.index, size++); +void SelectionDialogBase::ListViewText::append(const std::string &value) { + auto new_row = list_store->append(); + new_row->set_value(column_record.text, value); + new_row->set_value(column_record.index, size++); } void SelectionDialogBase::ListViewText::erase_rows() { - list_store->clear(); - size=0; + list_store->clear(); + size = 0; } void SelectionDialogBase::ListViewText::clear() { - unset_model(); - list_store.reset(); - size=0; + unset_model(); + list_store.reset(); + size = 0; } -SelectionDialogBase::SelectionDialogBase(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, bool show_search_entry, bool use_markup): - start_mark(start_mark), text_view(text_view), window(Gtk::WindowType::WINDOW_POPUP), vbox(Gtk::Orientation::ORIENTATION_VERTICAL), list_view_text(use_markup), show_search_entry(show_search_entry) { - auto g_application=g_application_get_default(); - auto gio_application=Glib::wrap(g_application, true); - auto application=Glib::RefPtr<Gtk::Application>::cast_static(gio_application); - window.set_transient_for(*application->get_active_window()); - - window.set_type_hint(Gdk::WindowTypeHint::WINDOW_TYPE_HINT_COMBO); - - search_entry.signal_changed().connect([this] { - if(on_search_entry_changed) - on_search_entry_changed(search_entry.get_text()); - }, false); - - list_view_text.set_search_entry(search_entry); - - window.set_default_size(0, 0); - window.property_decorated()=false; - window.set_skip_taskbar_hint(true); - - scrolled_window.set_policy(Gtk::PolicyType::POLICY_AUTOMATIC, Gtk::PolicyType::POLICY_AUTOMATIC); - - scrolled_window.add(list_view_text); - if(show_search_entry) - vbox.pack_start(search_entry, false, false); - vbox.pack_start(scrolled_window, true, true); - window.add(vbox); - - list_view_text.signal_realize().connect([this](){ - auto g_application=g_application_get_default(); - auto gio_application=Glib::wrap(g_application, true); - auto application=Glib::RefPtr<Gtk::Application>::cast_static(gio_application); - auto application_window=application->get_active_window(); - - int row_width=0, row_height; - Gdk::Rectangle rect; - list_view_text.get_cell_area(list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin()), *(list_view_text.get_column(0)), rect); - row_width=rect.get_width(); - row_height=rect.get_height(); - - row_width+=rect.get_x()*2; //TODO: Add correct margin x and y - row_height+=rect.get_y()*2; - - if(this->text_view && row_width>this->text_view->get_width()*2/3) - row_width=this->text_view->get_width()*2/3; - else if(row_width>application_window->get_width()/2) - row_width=application_window->get_width()/2; - else - scrolled_window.set_policy(Gtk::PolicyType::POLICY_NEVER, Gtk::PolicyType::POLICY_AUTOMATIC); - - int window_height=std::min(row_height*static_cast<int>(list_view_text.get_model()->children().size()), row_height*10); - if(this->show_search_entry) - window_height+=search_entry.get_height(); - int window_width=row_width+1; - window.resize(window_width, window_height); - - if(this->text_view) { - Gdk::Rectangle iter_rect; - this->text_view->get_iter_location(this->start_mark->get_iter(), iter_rect); - Gdk::Rectangle visible_rect; - this->text_view->get_visible_rect(visible_rect); - int buffer_x=std::max(iter_rect.get_x(), visible_rect.get_x()); - int buffer_y=iter_rect.get_y()+iter_rect.get_height(); - int window_x, window_y; - this->text_view->buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, buffer_x, buffer_y, window_x, window_y); - int root_x, root_y; - this->text_view->get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->get_root_coords(window_x, window_y, root_x, root_y); - window.move(root_x, root_y+1); //TODO: replace 1 with some margin - } - else { - int root_x, root_y; - application_window->get_position(root_x, root_y); - root_x+=application_window->get_width()/2-window_width/2; - root_y+=application_window->get_height()/2-window_height/2; - window.move(root_x, root_y); - } - }); - - list_view_text.signal_cursor_changed().connect([this] { - cursor_changed(); - }); +SelectionDialogBase::SelectionDialogBase(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, + bool show_search_entry, bool use_markup) : + start_mark(start_mark), text_view(text_view), window(Gtk::WindowType::WINDOW_POPUP), + vbox(Gtk::Orientation::ORIENTATION_VERTICAL), list_view_text(use_markup), show_search_entry(show_search_entry) { + auto g_application = g_application_get_default(); + auto gio_application = Glib::wrap(g_application, true); + auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application); + window.set_transient_for(*application->get_active_window()); + + window.set_type_hint(Gdk::WindowTypeHint::WINDOW_TYPE_HINT_COMBO); + + search_entry.signal_changed().connect([this] { + if (on_search_entry_changed) + on_search_entry_changed(search_entry.get_text()); + }, false); + + list_view_text.set_search_entry(search_entry); + + window.set_default_size(0, 0); + window.property_decorated() = false; + window.set_skip_taskbar_hint(true); + + scrolled_window.set_policy(Gtk::PolicyType::POLICY_AUTOMATIC, Gtk::PolicyType::POLICY_AUTOMATIC); + + scrolled_window.add(list_view_text); + if (show_search_entry) + vbox.pack_start(search_entry, false, false); + vbox.pack_start(scrolled_window, true, true); + window.add(vbox); + + list_view_text.signal_realize().connect([this]() { + auto g_application = g_application_get_default(); + auto gio_application = Glib::wrap(g_application, true); + auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application); + auto application_window = application->get_active_window(); + + int row_width = 0, row_height; + Gdk::Rectangle rect; + list_view_text.get_cell_area( + list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin()), + *(list_view_text.get_column(0)), rect); + row_width = rect.get_width(); + row_height = rect.get_height(); + + row_width += rect.get_x() * 2; //TODO: Add correct margin x and y + row_height += rect.get_y() * 2; + + if (this->text_view && row_width > this->text_view->get_width() * 2 / 3) + row_width = this->text_view->get_width() * 2 / 3; + else if (row_width > application_window->get_width() / 2) + row_width = application_window->get_width() / 2; + else + scrolled_window.set_policy(Gtk::PolicyType::POLICY_NEVER, Gtk::PolicyType::POLICY_AUTOMATIC); + + int window_height = std::min(row_height * static_cast<int>(list_view_text.get_model()->children().size()), + row_height * 10); + if (this->show_search_entry) + window_height += search_entry.get_height(); + int window_width = row_width + 1; + window.resize(window_width, window_height); + + if (this->text_view) { + Gdk::Rectangle iter_rect; + this->text_view->get_iter_location(this->start_mark->get_iter(), iter_rect); + Gdk::Rectangle visible_rect; + this->text_view->get_visible_rect(visible_rect); + int buffer_x = std::max(iter_rect.get_x(), visible_rect.get_x()); + int buffer_y = iter_rect.get_y() + iter_rect.get_height(); + int window_x, window_y; + this->text_view->buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, buffer_x, buffer_y, + window_x, window_y); + int root_x, root_y; + this->text_view->get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->get_root_coords(window_x, window_y, + root_x, root_y); + window.move(root_x, root_y + 1); //TODO: replace 1 with some margin + } else { + int root_x, root_y; + application_window->get_position(root_x, root_y); + root_x += application_window->get_width() / 2 - window_width / 2; + root_y += application_window->get_height() / 2 - window_height / 2; + window.move(root_x, root_y); + } + }); + + list_view_text.signal_cursor_changed().connect([this] { + cursor_changed(); + }); } SelectionDialogBase::~SelectionDialogBase() { - if(text_view) - text_view->get_buffer()->delete_mark(start_mark); + if (text_view) + text_view->get_buffer()->delete_mark(start_mark); } void SelectionDialogBase::cursor_changed() { - if(!is_visible()) - return; - auto it=list_view_text.get_selection()->get_selected(); - unsigned int index=static_cast<unsigned int>(-1); - if(it) - index=it->get_value(list_view_text.column_record.index); - if(last_index==index) - return; - if(on_changed) { - std::string text; - if(it) - text=it->get_value(list_view_text.column_record.text); - on_changed(index, text); - } - last_index=index; + if (!is_visible()) + return; + auto it = list_view_text.get_selection()->get_selected(); + unsigned int index = static_cast<unsigned int>(-1); + if (it) + index = it->get_value(list_view_text.column_record.index); + if (last_index == index) + return; + if (on_changed) { + std::string text; + if (it) + text = it->get_value(list_view_text.column_record.text); + on_changed(index, text); + } + last_index = index; } -void SelectionDialogBase::add_row(const std::string& row) { - list_view_text.append(row); + +void SelectionDialogBase::add_row(const std::string &row) { + list_view_text.append(row); } void SelectionDialogBase::erase_rows() { - list_view_text.erase_rows(); + list_view_text.erase_rows(); } void SelectionDialogBase::show() { - window.show_all(); - if(text_view) - text_view->grab_focus(); - - if(list_view_text.get_model()->children().size()>0) { - if(!list_view_text.get_selection()->get_selected()) { - list_view_text.set_cursor(list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); - cursor_changed(); - } - else if(list_view_text.get_model()->children().begin()!=list_view_text.get_selection()->get_selected()) { - while(Gtk::Main::events_pending()) - Gtk::Main::iteration(false); - if(is_visible()) - list_view_text.scroll_to_row(list_view_text.get_model()->get_path(list_view_text.get_selection()->get_selected()), 0.5); + window.show_all(); + if (text_view) + text_view->grab_focus(); + + if (list_view_text.get_model()->children().size() > 0) { + if (!list_view_text.get_selection()->get_selected()) { + list_view_text.set_cursor( + list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); + cursor_changed(); + } else if (list_view_text.get_model()->children().begin() != list_view_text.get_selection()->get_selected()) { + while (Gtk::Main::events_pending()) + Gtk::Main::iteration(false); + if (is_visible()) + list_view_text.scroll_to_row( + list_view_text.get_model()->get_path(list_view_text.get_selection()->get_selected()), 0.5); + } } - } - if(on_show) - on_show(); + if (on_show) + on_show(); } void SelectionDialogBase::set_cursor_at_last_row() { - auto children=list_view_text.get_model()->children(); - if(children.size()>0) { - list_view_text.set_cursor(list_view_text.get_model()->get_path(children[children.size()-1])); - cursor_changed(); - } + auto children = list_view_text.get_model()->children(); + if (children.size() > 0) { + list_view_text.set_cursor(list_view_text.get_model()->get_path(children[children.size() - 1])); + cursor_changed(); + } } void SelectionDialogBase::hide() { - if(!is_visible()) - return; - window.hide(); - if(on_hide) - on_hide(); - list_view_text.clear(); - last_index=static_cast<unsigned int>(-1); + if (!is_visible()) + return; + window.hide(); + if (on_hide) + on_hide(); + list_view_text.clear(); + last_index = static_cast<unsigned int>(-1); } std::unique_ptr<SelectionDialog> SelectionDialog::instance; -SelectionDialog::SelectionDialog(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, bool show_search_entry, bool use_markup) : SelectionDialogBase(text_view, start_mark, show_search_entry, use_markup) { - auto search_key=std::make_shared<std::string>(); - auto filter_model=Gtk::TreeModelFilter::create(list_view_text.get_model()); - - filter_model->set_visible_func([this, search_key](const Gtk::TreeModel::const_iterator& iter){ - std::string row_lc; - iter->get_value(0, row_lc); - auto search_key_lc=*search_key; - std::transform(row_lc.begin(), row_lc.end(), row_lc.begin(), ::tolower); - std::transform(search_key_lc.begin(), search_key_lc.end(), search_key_lc.begin(), ::tolower); - if(list_view_text.use_markup) { - size_t pos=0; - while((pos=row_lc.find('<', pos))!=std::string::npos) { - auto pos2=row_lc.find('>', pos+1); - row_lc.erase(pos, pos2-pos+1); - } - search_key_lc=Glib::Markup::escape_text(search_key_lc); - } - if(row_lc.find(search_key_lc)!=std::string::npos) - return true; - return false; - }); - - list_view_text.set_model(filter_model); - - list_view_text.set_search_equal_func([](const Glib::RefPtr<Gtk::TreeModel>& model, int column, const Glib::ustring& key, const Gtk::TreeModel::iterator& iter) { - return false; - }); - - search_entry.signal_changed().connect([this, search_key, filter_model](){ - *search_key=search_entry.get_text(); - filter_model->refilter(); - list_view_text.set_search_entry(search_entry); //TODO:Report the need of this to GTK's git (bug) - if(search_key->empty()) { - if(list_view_text.get_model()->children().size()>0) - list_view_text.set_cursor(list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); - } - }); - - auto activate=[this](){ - auto it=list_view_text.get_selection()->get_selected(); - if(on_select && it) { - auto index=it->get_value(list_view_text.column_record.index); - auto text=it->get_value(list_view_text.column_record.text); - on_select(index, text, true); - } - hide(); - }; - search_entry.signal_activate().connect([activate](){ - activate(); - }); - list_view_text.signal_row_activated().connect([activate](const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn*) { - activate(); - }); +SelectionDialog::SelectionDialog(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, + bool show_search_entry, bool use_markup) : SelectionDialogBase(text_view, start_mark, + show_search_entry, + use_markup) { + auto search_key = std::make_shared<std::string>(); + auto filter_model = Gtk::TreeModelFilter::create(list_view_text.get_model()); + + filter_model->set_visible_func([this, search_key](const Gtk::TreeModel::const_iterator &iter) { + std::string row_lc; + iter->get_value(0, row_lc); + auto search_key_lc = *search_key; + std::transform(row_lc.begin(), row_lc.end(), row_lc.begin(), ::tolower); + std::transform(search_key_lc.begin(), search_key_lc.end(), search_key_lc.begin(), ::tolower); + if (list_view_text.use_markup) { + size_t pos = 0; + while ((pos = row_lc.find('<', pos)) != std::string::npos) { + auto pos2 = row_lc.find('>', pos + 1); + row_lc.erase(pos, pos2 - pos + 1); + } + search_key_lc = Glib::Markup::escape_text(search_key_lc); + } + if (row_lc.find(search_key_lc) != std::string::npos) + return true; + return false; + }); + + list_view_text.set_model(filter_model); + + list_view_text.set_search_equal_func( + [](const Glib::RefPtr<Gtk::TreeModel> &model, int column, const Glib::ustring &key, + const Gtk::TreeModel::iterator &iter) { + return false; + }); + + search_entry.signal_changed().connect([this, search_key, filter_model]() { + *search_key = search_entry.get_text(); + filter_model->refilter(); + list_view_text.set_search_entry(search_entry); //TODO:Report the need of this to GTK's git (bug) + if (search_key->empty()) { + if (list_view_text.get_model()->children().size() > 0) + list_view_text.set_cursor( + list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); + } + }); + + auto activate = [this]() { + auto it = list_view_text.get_selection()->get_selected(); + if (on_select && it) { + auto index = it->get_value(list_view_text.column_record.index); + auto text = it->get_value(list_view_text.column_record.text); + on_select(index, text, true); + } + hide(); + }; + search_entry.signal_activate().connect([activate]() { + activate(); + }); + list_view_text.signal_row_activated().connect([activate](const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *) { + activate(); + }); } -bool SelectionDialog::on_key_press(GdkEventKey* key) { - if((key->keyval==GDK_KEY_Down || key->keyval==GDK_KEY_KP_Down) && list_view_text.get_model()->children().size()>0) { - auto it=list_view_text.get_selection()->get_selected(); - if(it) { - it++; - if(it) - list_view_text.set_cursor(list_view_text.get_model()->get_path(it)); - } - return true; - } - else if((key->keyval==GDK_KEY_Up || key->keyval==GDK_KEY_KP_Up) && list_view_text.get_model()->children().size()>0) { - auto it=list_view_text.get_selection()->get_selected(); - if(it) { - it--; - if(it) - list_view_text.set_cursor(list_view_text.get_model()->get_path(it)); - } - return true; - } - else if(key->keyval==GDK_KEY_Return || key-> keyval==GDK_KEY_KP_Enter || key->keyval==GDK_KEY_ISO_Left_Tab || key->keyval==GDK_KEY_Tab) { - auto it=list_view_text.get_selection()->get_selected(); - auto column=list_view_text.get_column(0); - list_view_text.row_activated(list_view_text.get_model()->get_path(it), *column); - return true; - } - else if(key->keyval==GDK_KEY_Escape) { - hide(); - return true; - } - else if(key->keyval==GDK_KEY_Left || key->keyval==GDK_KEY_KP_Left || key->keyval==GDK_KEY_Right || key->keyval==GDK_KEY_KP_Right) { - hide(); - return false; - } - else if(show_search_entry) { -#ifdef __APPLE__ //OS X bug most likely: Gtk::Entry will not work if window is of type POPUP - int search_entry_length=search_entry.get_text_length(); - if(key->keyval==GDK_KEY_dead_tilde) { - search_entry.insert_text("~", 1, search_entry_length); - return true; - } - else if(key->keyval==GDK_KEY_dead_circumflex) { - search_entry.insert_text("^", 1, search_entry_length); - return true; - } - else if(key->is_modifier) - return true; - else if(key->keyval==GDK_KEY_BackSpace) { - auto length=search_entry.get_text_length(); - if(length>0) - search_entry.delete_text(length-1, length); - return true; - } - else { - gunichar unicode=gdk_keyval_to_unicode(key->keyval); - if(unicode>=32 && unicode!=126) { - auto ustr=Glib::ustring(1, unicode); - search_entry.insert_text(ustr, ustr.bytes(), search_entry_length); +bool SelectionDialog::on_key_press(GdkEventKey *key) { + if ((key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down) && + list_view_text.get_model()->children().size() > 0) { + auto it = list_view_text.get_selection()->get_selected(); + if (it) { + it++; + if (it) + list_view_text.set_cursor(list_view_text.get_model()->get_path(it)); + } return true; - } - } + } else if ((key->keyval == GDK_KEY_Up || key->keyval == GDK_KEY_KP_Up) && + list_view_text.get_model()->children().size() > 0) { + auto it = list_view_text.get_selection()->get_selected(); + if (it) { + it--; + if (it) + list_view_text.set_cursor(list_view_text.get_model()->get_path(it)); + } + return true; + } else if (key->keyval == GDK_KEY_Return || key->keyval == GDK_KEY_KP_Enter || + key->keyval == GDK_KEY_ISO_Left_Tab || key->keyval == GDK_KEY_Tab) { + auto it = list_view_text.get_selection()->get_selected(); + auto column = list_view_text.get_column(0); + list_view_text.row_activated(list_view_text.get_model()->get_path(it), *column); + return true; + } else if (key->keyval == GDK_KEY_Escape) { + hide(); + return true; + } else if (key->keyval == GDK_KEY_Left || key->keyval == GDK_KEY_KP_Left || key->keyval == GDK_KEY_Right || + key->keyval == GDK_KEY_KP_Right) { + hide(); + return false; + } else if (show_search_entry) { +#ifdef __APPLE__ //OS X bug most likely: Gtk::Entry will not work if window is of type POPUP + int search_entry_length=search_entry.get_text_length(); + if(key->keyval==GDK_KEY_dead_tilde) { + search_entry.insert_text("~", 1, search_entry_length); + return true; + } + else if(key->keyval==GDK_KEY_dead_circumflex) { + search_entry.insert_text("^", 1, search_entry_length); + return true; + } + else if(key->is_modifier) + return true; + else if(key->keyval==GDK_KEY_BackSpace) { + auto length=search_entry.get_text_length(); + if(length>0) + search_entry.delete_text(length-1, length); + return true; + } + else { + gunichar unicode=gdk_keyval_to_unicode(key->keyval); + if(unicode>=32 && unicode!=126) { + auto ustr=Glib::ustring(1, unicode); + search_entry.insert_text(ustr, ustr.bytes(), search_entry_length); + return true; + } + } #else - search_entry.on_key_press_event(key); - return true; + search_entry.on_key_press_event(key); + return true; #endif - } - hide(); - return false; + } + hide(); + return false; } std::unique_ptr<CompletionDialog> CompletionDialog::instance; -CompletionDialog::CompletionDialog(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark) : SelectionDialogBase(text_view, start_mark, false, false) { - show_offset=text_view->get_buffer()->get_insert()->get_iter().get_offset(); - - auto search_key=std::make_shared<std::string>(); - auto filter_model=Gtk::TreeModelFilter::create(list_view_text.get_model()); - if(show_offset==start_mark->get_iter().get_offset()) { - filter_model->set_visible_func([search_key](const Gtk::TreeModel::const_iterator& iter){ - std::string row_lc; - iter->get_value(0, row_lc); - auto search_key_lc=*search_key; - std::transform(row_lc.begin(), row_lc.end(), row_lc.begin(), ::tolower); - std::transform(search_key_lc.begin(), search_key_lc.end(), search_key_lc.begin(), ::tolower); - if(row_lc.find(search_key_lc)!=std::string::npos) - return true; - return false; +CompletionDialog::CompletionDialog(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark) + : SelectionDialogBase(text_view, start_mark, false, false) { + show_offset = text_view->get_buffer()->get_insert()->get_iter().get_offset(); + + auto search_key = std::make_shared<std::string>(); + auto filter_model = Gtk::TreeModelFilter::create(list_view_text.get_model()); + if (show_offset == start_mark->get_iter().get_offset()) { + filter_model->set_visible_func([search_key](const Gtk::TreeModel::const_iterator &iter) { + std::string row_lc; + iter->get_value(0, row_lc); + auto search_key_lc = *search_key; + std::transform(row_lc.begin(), row_lc.end(), row_lc.begin(), ::tolower); + std::transform(search_key_lc.begin(), search_key_lc.end(), search_key_lc.begin(), ::tolower); + if (row_lc.find(search_key_lc) != std::string::npos) + return true; + return false; + }); + } else { + filter_model->set_visible_func([search_key](const Gtk::TreeModel::const_iterator &iter) { + std::string row; + iter->get_value(0, row); + if (row.find(*search_key) == 0) + return true; + return false; + }); + } + list_view_text.set_model(filter_model); + search_entry.signal_changed().connect([this, search_key, filter_model]() { + *search_key = search_entry.get_text(); + filter_model->refilter(); + list_view_text.set_search_entry(search_entry); //TODO:Report the need of this to GTK's git (bug) }); - } - else { - filter_model->set_visible_func([search_key](const Gtk::TreeModel::const_iterator& iter){ - std::string row; - iter->get_value(0, row); - if(row.find(*search_key)==0) - return true; - return false; + + list_view_text.signal_row_activated().connect([this](const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *) { + select(); }); - } - list_view_text.set_model(filter_model); - search_entry.signal_changed().connect([this, search_key, filter_model](){ - *search_key=search_entry.get_text(); - filter_model->refilter(); - list_view_text.set_search_entry(search_entry); //TODO:Report the need of this to GTK's git (bug) - }); - - list_view_text.signal_row_activated().connect([this](const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn*) { - select(); - }); - - auto text=text_view->get_buffer()->get_text(start_mark->get_iter(), text_view->get_buffer()->get_insert()->get_iter()); - if(text.size()>0) { - search_entry.set_text(text); - list_view_text.set_search_entry(search_entry); - } + + auto text = text_view->get_buffer()->get_text(start_mark->get_iter(), + text_view->get_buffer()->get_insert()->get_iter()); + if (text.size() > 0) { + search_entry.set_text(text); + list_view_text.set_search_entry(search_entry); + } } void CompletionDialog::select(bool hide_window) { - row_in_entry=true; - - auto it=list_view_text.get_selection()->get_selected(); - if(on_select && it) { - auto index=it->get_value(list_view_text.column_record.index); - auto text=it->get_value(list_view_text.column_record.text); - on_select(index, text, hide_window); - } - if(hide_window) - hide(); -} + row_in_entry = true; -bool CompletionDialog::on_key_release(GdkEventKey* key) { - if(key->keyval==GDK_KEY_Down || key->keyval==GDK_KEY_KP_Down || key->keyval==GDK_KEY_Up || key->keyval==GDK_KEY_KP_Up) - return false; - - if(show_offset>text_view->get_buffer()->get_insert()->get_iter().get_offset()) { - hide(); - } - else { - auto text=text_view->get_buffer()->get_text(start_mark->get_iter(), text_view->get_buffer()->get_insert()->get_iter()); - search_entry.set_text(text); - list_view_text.set_search_entry(search_entry); - if(text=="") { - if(list_view_text.get_model()->children().size()>0) - list_view_text.set_cursor(list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); + auto it = list_view_text.get_selection()->get_selected(); + if (on_select && it) { + auto index = it->get_value(list_view_text.column_record.index); + auto text = it->get_value(list_view_text.column_record.text); + on_select(index, text, hide_window); } - cursor_changed(); - } - return false; + if (hide_window) + hide(); } -bool CompletionDialog::on_key_press(GdkEventKey* key) { - if((key->keyval>=GDK_KEY_0 && key->keyval<=GDK_KEY_9) || - (key->keyval>=GDK_KEY_A && key->keyval<=GDK_KEY_Z) || - (key->keyval>=GDK_KEY_a && key->keyval<=GDK_KEY_z) || - key->keyval==GDK_KEY_underscore || key->keyval==GDK_KEY_BackSpace) { - if(row_in_entry) { - text_view->get_buffer()->erase(start_mark->get_iter(), text_view->get_buffer()->get_insert()->get_iter()); - row_in_entry=false; - if(key->keyval==GDK_KEY_BackSpace) - return true; +bool CompletionDialog::on_key_release(GdkEventKey *key) { + if (key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down || key->keyval == GDK_KEY_Up || + key->keyval == GDK_KEY_KP_Up) + return false; + + if (show_offset > text_view->get_buffer()->get_insert()->get_iter().get_offset()) { + hide(); + } else { + auto text = text_view->get_buffer()->get_text(start_mark->get_iter(), + text_view->get_buffer()->get_insert()->get_iter()); + search_entry.set_text(text); + list_view_text.set_search_entry(search_entry); + if (text == "") { + if (list_view_text.get_model()->children().size() > 0) + list_view_text.set_cursor( + list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); + } + cursor_changed(); } return false; - } - if(key->keyval==GDK_KEY_Shift_L || key->keyval==GDK_KEY_Shift_R || key->keyval==GDK_KEY_Alt_L || key->keyval==GDK_KEY_Alt_R || key->keyval==GDK_KEY_Control_L || key->keyval==GDK_KEY_Control_R || key->keyval==GDK_KEY_Meta_L || key->keyval==GDK_KEY_Meta_R) - return false; - if((key->keyval==GDK_KEY_Down || key->keyval==GDK_KEY_KP_Down) && list_view_text.get_model()->children().size()>0) { - auto it=list_view_text.get_selection()->get_selected(); - if(it) { - it++; - if(it) { - list_view_text.set_cursor(list_view_text.get_model()->get_path(it)); - cursor_changed(); - } +} + +bool CompletionDialog::on_key_press(GdkEventKey *key) { + if ((key->keyval >= GDK_KEY_0 && key->keyval <= GDK_KEY_9) || + (key->keyval >= GDK_KEY_A && key->keyval <= GDK_KEY_Z) || + (key->keyval >= GDK_KEY_a && key->keyval <= GDK_KEY_z) || + key->keyval == GDK_KEY_underscore || key->keyval == GDK_KEY_BackSpace) { + if (row_in_entry) { + text_view->get_buffer()->erase(start_mark->get_iter(), text_view->get_buffer()->get_insert()->get_iter()); + row_in_entry = false; + if (key->keyval == GDK_KEY_BackSpace) + return true; + } + return false; } - else - list_view_text.set_cursor(list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); - select(false); - return true; - } - if((key->keyval==GDK_KEY_Up || key->keyval==GDK_KEY_KP_Up) && list_view_text.get_model()->children().size()>0) { - auto it=list_view_text.get_selection()->get_selected(); - if(it) { - it--; - if(it) { - list_view_text.set_cursor(list_view_text.get_model()->get_path(it)); - cursor_changed(); - } + if (key->keyval == GDK_KEY_Shift_L || key->keyval == GDK_KEY_Shift_R || key->keyval == GDK_KEY_Alt_L || + key->keyval == GDK_KEY_Alt_R || key->keyval == GDK_KEY_Control_L || key->keyval == GDK_KEY_Control_R || + key->keyval == GDK_KEY_Meta_L || key->keyval == GDK_KEY_Meta_R) + return false; + if ((key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down) && + list_view_text.get_model()->children().size() > 0) { + auto it = list_view_text.get_selection()->get_selected(); + if (it) { + it++; + if (it) { + list_view_text.set_cursor(list_view_text.get_model()->get_path(it)); + cursor_changed(); + } + } else + list_view_text.set_cursor( + list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); + select(false); + return true; } - else { - auto last_it=list_view_text.get_model()->children().end(); - last_it--; - if(last_it) - list_view_text.set_cursor(list_view_text.get_model()->get_path(last_it)); + if ((key->keyval == GDK_KEY_Up || key->keyval == GDK_KEY_KP_Up) && + list_view_text.get_model()->children().size() > 0) { + auto it = list_view_text.get_selection()->get_selected(); + if (it) { + it--; + if (it) { + list_view_text.set_cursor(list_view_text.get_model()->get_path(it)); + cursor_changed(); + } + } else { + auto last_it = list_view_text.get_model()->children().end(); + last_it--; + if (last_it) + list_view_text.set_cursor(list_view_text.get_model()->get_path(last_it)); + } + select(false); + return true; } - select(false); - return true; - } - if(key->keyval==GDK_KEY_Return || key->keyval==GDK_KEY_KP_Enter || key->keyval==GDK_KEY_ISO_Left_Tab || key->keyval==GDK_KEY_Tab) { - select(); - return true; - } - hide(); - if(key->keyval==GDK_KEY_Escape) - return true; - return false; + if (key->keyval == GDK_KEY_Return || key->keyval == GDK_KEY_KP_Enter || key->keyval == GDK_KEY_ISO_Left_Tab || + key->keyval == GDK_KEY_Tab) { + select(); + return true; + } + hide(); + if (key->keyval == GDK_KEY_Escape) + return true; + return false; } diff --git a/src/selection_dialog.h b/src/selection_dialog.h index c041f4a1..05d33d48 100644 --- a/src/selection_dialog.h +++ b/src/selection_dialog.h @@ -1,100 +1,129 @@ #pragma once + #include "gtkmm.h" #include <unordered_map> #include <functional> class SelectionDialogBase { - class ListViewText : public Gtk::TreeView { - class ColumnRecord : public Gtk::TreeModel::ColumnRecord { + class ListViewText : public Gtk::TreeView { + class ColumnRecord : public Gtk::TreeModel::ColumnRecord { + public: + ColumnRecord() { + add(text); + add(index); + } + + Gtk::TreeModelColumn<std::string> text; + Gtk::TreeModelColumn<unsigned int> index; + }; + public: - ColumnRecord() { - add(text); - add(index); - } - Gtk::TreeModelColumn<std::string> text; - Gtk::TreeModelColumn<unsigned int> index; + bool use_markup; + ColumnRecord column_record; + + ListViewText(bool use_markup); + + void append(const std::string &value); + + void erase_rows(); + + void clear(); + + private: + Glib::RefPtr<Gtk::ListStore> list_store; + Gtk::CellRendererText cell_renderer; + unsigned int size = 0; }; - public: - bool use_markup; - ColumnRecord column_record; - ListViewText(bool use_markup); - void append(const std::string& value); - void erase_rows(); - void clear(); - private: - Glib::RefPtr<Gtk::ListStore> list_store; - Gtk::CellRendererText cell_renderer; - unsigned int size=0; - }; - - class SearchEntry : public Gtk::Entry { - public: - SearchEntry() : Gtk::Entry() {} - bool on_key_press_event(GdkEventKey *event) override { return Gtk::Entry::on_key_press_event(event); }; - }; - + + class SearchEntry : public Gtk::Entry { + public: + SearchEntry() : Gtk::Entry() {} + + bool on_key_press_event(GdkEventKey *event) override { return Gtk::Entry::on_key_press_event(event); }; + }; + public: - SelectionDialogBase(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, bool show_search_entry, bool use_markup); - virtual ~SelectionDialogBase(); - void add_row(const std::string& row); - void erase_rows(); - void set_cursor_at_last_row(); - void show(); - void hide(); - - bool is_visible() {return window.is_visible();} - void get_position(int &root_x, int &root_y) {window.get_position(root_x, root_y);} - - std::function<void()> on_show; - std::function<void()> on_hide; - std::function<void(unsigned int index, const std::string &text, bool hide_window)> on_select; - std::function<void(unsigned int index, const std::string &text)> on_changed; - std::function<void(const std::string &text)> on_search_entry_changed; - Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark; - + SelectionDialogBase(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, + bool show_search_entry, bool use_markup); + + virtual ~SelectionDialogBase(); + + void add_row(const std::string &row); + + void erase_rows(); + + void set_cursor_at_last_row(); + + void show(); + + void hide(); + + bool is_visible() { return window.is_visible(); } + + void get_position(int &root_x, int &root_y) { window.get_position(root_x, root_y); } + + std::function<void()> on_show; + std::function<void()> on_hide; + std::function<void(unsigned int index, const std::string &text, bool hide_window)> on_select; + std::function<void(unsigned int index, const std::string &text)> on_changed; + std::function<void(const std::string &text)> on_search_entry_changed; + Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark; + protected: - void cursor_changed(); - - Gtk::TextView *text_view; - Gtk::Window window; - Gtk::Box vbox; - Gtk::ScrolledWindow scrolled_window; - ListViewText list_view_text; - SearchEntry search_entry; - bool show_search_entry; - - unsigned int last_index=static_cast<unsigned int>(-1); + void cursor_changed(); + + Gtk::TextView *text_view; + Gtk::Window window; + Gtk::Box vbox; + Gtk::ScrolledWindow scrolled_window; + ListViewText list_view_text; + SearchEntry search_entry; + bool show_search_entry; + + unsigned int last_index = static_cast<unsigned int>(-1); }; class SelectionDialog : public SelectionDialogBase { - SelectionDialog(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, bool show_search_entry, bool use_markup); - static std::unique_ptr<SelectionDialog> instance; + SelectionDialog(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, bool show_search_entry, + bool use_markup); + + static std::unique_ptr<SelectionDialog> instance; public: - bool on_key_press(GdkEventKey* key); - - static void create(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, bool show_search_entry=true, bool use_markup=false) { - instance=std::unique_ptr<SelectionDialog>(new SelectionDialog(text_view, start_mark, show_search_entry, use_markup)); - } - static void create(bool show_search_entry=true, bool use_markup=false) { - instance=std::unique_ptr<SelectionDialog>(new SelectionDialog(nullptr, Glib::RefPtr<Gtk::TextBuffer::Mark>(), show_search_entry, use_markup)); - } - static std::unique_ptr<SelectionDialog> &get() {return instance;} + bool on_key_press(GdkEventKey *key); + + static void + create(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, bool show_search_entry = true, + bool use_markup = false) { + instance = std::unique_ptr<SelectionDialog>( + new SelectionDialog(text_view, start_mark, show_search_entry, use_markup)); + } + + static void create(bool show_search_entry = true, bool use_markup = false) { + instance = std::unique_ptr<SelectionDialog>( + new SelectionDialog(nullptr, Glib::RefPtr<Gtk::TextBuffer::Mark>(), show_search_entry, use_markup)); + } + + static std::unique_ptr<SelectionDialog> &get() { return instance; } }; class CompletionDialog : public SelectionDialogBase { - CompletionDialog(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark); - static std::unique_ptr<CompletionDialog> instance; + CompletionDialog(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark); + + static std::unique_ptr<CompletionDialog> instance; public: - bool on_key_release(GdkEventKey* key); - bool on_key_press(GdkEventKey* key); - - static void create(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark) { - instance=std::unique_ptr<CompletionDialog>(new CompletionDialog(text_view, start_mark)); - } - static std::unique_ptr<CompletionDialog> &get() {return instance;} + bool on_key_release(GdkEventKey *key); + + bool on_key_press(GdkEventKey *key); + + static void create(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark) { + instance = std::unique_ptr<CompletionDialog>(new CompletionDialog(text_view, start_mark)); + } + + static std::unique_ptr<CompletionDialog> &get() { return instance; } + private: - void select(bool hide_window=true); - - int show_offset; - bool row_in_entry=false; + void select(bool hide_window = true); + + int show_offset; + bool row_in_entry = false; }; diff --git a/src/source.cc b/src/source.cc index 9b770af9..01f7bd06 100644 --- a/src/source.cc +++ b/src/source.cc @@ -19,2704 +19,2722 @@ #include <limits> Glib::RefPtr<Gsv::LanguageManager> Source::LanguageManager::get_default() { - static auto instance = Gsv::LanguageManager::create(); - return instance; + static auto instance = Gsv::LanguageManager::create(); + return instance; } + Glib::RefPtr<Gsv::StyleSchemeManager> Source::StyleSchemeManager::get_default() { - static auto instance = Gsv::StyleSchemeManager::create(); - static bool first = true; - if(first) { - instance->prepend_search_path((Config::get().home_juci_path/"styles").string()); - first=false; - } - return instance; + static auto instance = Gsv::StyleSchemeManager::create(); + static bool first = true; + if (first) { + instance->prepend_search_path((Config::get().home_juci_path / "styles").string()); + first = false; + } + return instance; } Glib::RefPtr<Gsv::Language> Source::guess_language(const boost::filesystem::path &file_path) { - auto language_manager=LanguageManager::get_default(); - bool result_uncertain = false; - auto content_type = Gio::content_type_guess(file_path.string(), nullptr, 0, result_uncertain); - if(result_uncertain) { - content_type.clear(); - } - auto language=language_manager->guess_language(file_path.string(), content_type); - if(!language) { - auto filename=file_path.filename().string(); - if(filename=="CMakeLists.txt") - language=language_manager->get_language("cmake"); - else if(filename=="Makefile") - language=language_manager->get_language("makefile"); - else if(file_path.extension()==".tcc") - language=language_manager->get_language("cpphdr"); - else if(file_path.extension()==".ts" || file_path.extension()==".jsx") - language=language_manager->get_language("js"); - else if(!file_path.has_extension()) { - for(auto &part: file_path) { - if(part=="include") { - language=language_manager->get_language("cpphdr"); - break; - } - } + auto language_manager = LanguageManager::get_default(); + bool result_uncertain = false; + auto content_type = Gio::content_type_guess(file_path.string(), nullptr, 0, result_uncertain); + if (result_uncertain) { + content_type.clear(); + } + auto language = language_manager->guess_language(file_path.string(), content_type); + if (!language) { + auto filename = file_path.filename().string(); + if (filename == "CMakeLists.txt") + language = language_manager->get_language("cmake"); + else if (filename == "Makefile") + language = language_manager->get_language("makefile"); + else if (file_path.extension() == ".tcc") + language = language_manager->get_language("cpphdr"); + else if (file_path.extension() == ".ts" || file_path.extension() == ".jsx") + language = language_manager->get_language("js"); + else if (!file_path.has_extension()) { + for (auto &part: file_path) { + if (part == "include") { + language = language_manager->get_language("cpphdr"); + break; + } + } + } + } else if (language->get_id() == "cuda") { + if (file_path.extension() == ".cuh") + language = language_manager->get_language("cpphdr"); + else + language = language_manager->get_language("cpp"); + } else if (language->get_id() == "opencl") { + language = language_manager->get_language("cpp"); } - } - else if(language->get_id()=="cuda") { - if(file_path.extension()==".cuh") - language=language_manager->get_language("cpphdr"); - else - language=language_manager->get_language("cpp"); - } - else if(language->get_id()=="opencl") { - language = language_manager->get_language("cpp"); - } - return language; + return language; } -Source::FixIt::FixIt(const std::string &source, const std::pair<Offset, Offset> &offsets) : source(source), offsets(offsets) { - if(source.size()==0) - type=Type::ERASE; - else { - if(this->offsets.first==this->offsets.second) - type=Type::INSERT; - else - type=Type::REPLACE; - } +Source::FixIt::FixIt(const std::string &source, const std::pair<Offset, Offset> &offsets) : source(source), + offsets(offsets) { + if (source.size() == 0) + type = Type::ERASE; + else { + if (this->offsets.first == this->offsets.second) + type = Type::INSERT; + else + type = Type::REPLACE; + } } std::string Source::FixIt::string(Glib::RefPtr<Gtk::TextBuffer> buffer) { - auto iter=buffer->get_iter_at_line_index(offsets.first.line, offsets.first.index); - unsigned first_line_offset=iter.get_line_offset()+1; - iter=buffer->get_iter_at_line_index(offsets.second.line, offsets.second.index); - unsigned second_line_offset=iter.get_line_offset()+1; - - std::string text; - if(type==Type::INSERT) { - text+="Insert "+source+" at "; - text+=std::to_string(offsets.first.line+1)+":"+std::to_string(first_line_offset); - } - else if(type==Type::REPLACE) { - text+="Replace "; - text+=std::to_string(offsets.first.line+1)+":"+std::to_string(first_line_offset)+" - "; - text+=std::to_string(offsets.second.line+1)+":"+std::to_string(second_line_offset); - text+=" with "+source; - } - else { - text+="Erase "; - text+=std::to_string(offsets.first.line+1)+":"+std::to_string(first_line_offset)+" - "; - text+=std::to_string(offsets.second.line+1)+":"+std::to_string(second_line_offset); - } - - return text; + auto iter = buffer->get_iter_at_line_index(offsets.first.line, offsets.first.index); + unsigned first_line_offset = iter.get_line_offset() + 1; + iter = buffer->get_iter_at_line_index(offsets.second.line, offsets.second.index); + unsigned second_line_offset = iter.get_line_offset() + 1; + + std::string text; + if (type == Type::INSERT) { + text += "Insert " + source + " at "; + text += std::to_string(offsets.first.line + 1) + ":" + std::to_string(first_line_offset); + } else if (type == Type::REPLACE) { + text += "Replace "; + text += std::to_string(offsets.first.line + 1) + ":" + std::to_string(first_line_offset) + " - "; + text += std::to_string(offsets.second.line + 1) + ":" + std::to_string(second_line_offset); + text += " with " + source; + } else { + text += "Erase "; + text += std::to_string(offsets.first.line + 1) + ":" + std::to_string(first_line_offset) + " - "; + text += std::to_string(offsets.second.line + 1) + ":" + std::to_string(second_line_offset); + } + + return text; } ////////////// //// View //// ////////////// -std::unordered_set<Source::View*> Source::View::non_deleted_views; -std::unordered_set<Source::View*> Source::View::views; - -Source::View::View(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language, bool is_generic_view): BaseView(file_path, language), SpellCheckView(file_path, language), DiffView(file_path, language) { - non_deleted_views.emplace(this); - views.emplace(this); - - search_settings = gtk_source_search_settings_new(); - gtk_source_search_settings_set_wrap_around(search_settings, true); - search_context = gtk_source_search_context_new(get_source_buffer()->gobj(), search_settings); - gtk_source_search_context_set_highlight(search_context, true); - //TODO: why does this not work?: Might be best to use the styles from sourceview. These has to be read from file, search-matches got style "search-match" - //TODO: in header if trying again: GtkSourceStyle* search_match_style; - //TODO: We can drop this, only work on newer versions of gtksourceview. - //search_match_style=(GtkSourceStyle*)g_object_new(GTK_SOURCE_TYPE_STYLE, "background-set", 1, "background", "#00FF00", nullptr); - //gtk_source_search_context_set_match_style(search_context, search_match_style); - - //TODO: either use lambda if possible or create a gtkmm wrapper around search_context (including search_settings): - //TODO: (gtkmm's Gtk::Object has connect_property_changed, so subclassing this might be an idea) - g_signal_connect(search_context, "notify::occurrences-count", G_CALLBACK(search_occurrences_updated), this); - - get_buffer()->create_tag("def:warning"); - get_buffer()->create_tag("def:warning_underline"); - get_buffer()->create_tag("def:error"); - get_buffer()->create_tag("def:error_underline"); - - auto mark_attr_debug_breakpoint=Gsv::MarkAttributes::create(); - Gdk::RGBA rgba; - rgba.set_red(1.0); - rgba.set_green(0.5); - rgba.set_blue(0.5); - rgba.set_alpha(0.3); - mark_attr_debug_breakpoint->set_background(rgba); - set_mark_attributes("debug_breakpoint", mark_attr_debug_breakpoint, 100); - auto mark_attr_debug_stop=Gsv::MarkAttributes::create(); - rgba.set_red(0.5); - rgba.set_green(0.5); - rgba.set_blue(1.0); - mark_attr_debug_stop->set_background(rgba); - set_mark_attributes("debug_stop", mark_attr_debug_stop, 101); - auto mark_attr_debug_breakpoint_and_stop=Gsv::MarkAttributes::create(); - rgba.set_red(0.75); - rgba.set_green(0.5); - rgba.set_blue(0.75); - mark_attr_debug_breakpoint_and_stop->set_background(rgba); - set_mark_attributes("debug_breakpoint_and_stop", mark_attr_debug_breakpoint_and_stop, 102); - - get_buffer()->signal_changed().connect([this](){ - if(update_status_location) - update_status_location(this); - }); - - signal_realize().connect([this] { - auto gutter=get_gutter(Gtk::TextWindowType::TEXT_WINDOW_LEFT); - auto renderer=gutter->get_renderer_at_pos(15, 0); - if(renderer) { - renderer_activate_connection.disconnect(); - renderer_activate_connection=renderer->signal_activate().connect([this](const Gtk::TextIter& iter, const Gdk::Rectangle&, GdkEvent*) { - if(toggle_breakpoint) - toggle_breakpoint(iter.get_line()); - }); - } - }); - - if(language && (language->get_id()=="chdr" || language->get_id()=="cpphdr" || language->get_id()=="c" || - language->get_id()=="cpp" || language->get_id()=="objc" || language->get_id()=="java" || - language->get_id()=="js" || language->get_id()=="ts" || language->get_id()=="proto" || - language->get_id()=="c-sharp" || language->get_id()=="html" || language->get_id()=="cuda" || - language->get_id()=="php" || language->get_id()=="rust" || language->get_id()=="swift" || - language->get_id()=="go" || language->get_id()=="scala" || language->get_id()=="opencl" || - language->get_id()=="json" || language->get_id()=="css")) - is_bracket_language=true; - - setup_tooltip_and_dialog_events(); - setup_format_style(is_generic_view); - +std::unordered_set<Source::View *> Source::View::non_deleted_views; +std::unordered_set<Source::View *> Source::View::views; + +Source::View::View(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language, bool is_generic_view) + : BaseView(file_path, language), SpellCheckView(file_path, language), DiffView(file_path, language) { + non_deleted_views.emplace(this); + views.emplace(this); + + search_settings = gtk_source_search_settings_new(); + gtk_source_search_settings_set_wrap_around(search_settings, true); + search_context = gtk_source_search_context_new(get_source_buffer()->gobj(), search_settings); + gtk_source_search_context_set_highlight(search_context, true); + //TODO: why does this not work?: Might be best to use the styles from sourceview. These has to be read from file, search-matches got style "search-match" + //TODO: in header if trying again: GtkSourceStyle* search_match_style; + //TODO: We can drop this, only work on newer versions of gtksourceview. + //search_match_style=(GtkSourceStyle*)g_object_new(GTK_SOURCE_TYPE_STYLE, "background-set", 1, "background", "#00FF00", nullptr); + //gtk_source_search_context_set_match_style(search_context, search_match_style); + + //TODO: either use lambda if possible or create a gtkmm wrapper around search_context (including search_settings): + //TODO: (gtkmm's Gtk::Object has connect_property_changed, so subclassing this might be an idea) + g_signal_connect(search_context, "notify::occurrences-count", G_CALLBACK(search_occurrences_updated), this); + + get_buffer()->create_tag("def:warning"); + get_buffer()->create_tag("def:warning_underline"); + get_buffer()->create_tag("def:error"); + get_buffer()->create_tag("def:error_underline"); + + auto mark_attr_debug_breakpoint = Gsv::MarkAttributes::create(); + Gdk::RGBA rgba; + rgba.set_red(1.0); + rgba.set_green(0.5); + rgba.set_blue(0.5); + rgba.set_alpha(0.3); + mark_attr_debug_breakpoint->set_background(rgba); + set_mark_attributes("debug_breakpoint", mark_attr_debug_breakpoint, 100); + auto mark_attr_debug_stop = Gsv::MarkAttributes::create(); + rgba.set_red(0.5); + rgba.set_green(0.5); + rgba.set_blue(1.0); + mark_attr_debug_stop->set_background(rgba); + set_mark_attributes("debug_stop", mark_attr_debug_stop, 101); + auto mark_attr_debug_breakpoint_and_stop = Gsv::MarkAttributes::create(); + rgba.set_red(0.75); + rgba.set_green(0.5); + rgba.set_blue(0.75); + mark_attr_debug_breakpoint_and_stop->set_background(rgba); + set_mark_attributes("debug_breakpoint_and_stop", mark_attr_debug_breakpoint_and_stop, 102); + + get_buffer()->signal_changed().connect([this]() { + if (update_status_location) + update_status_location(this); + }); + + signal_realize().connect([this] { + auto gutter = get_gutter(Gtk::TextWindowType::TEXT_WINDOW_LEFT); + auto renderer = gutter->get_renderer_at_pos(15, 0); + if (renderer) { + renderer_activate_connection.disconnect(); + renderer_activate_connection = renderer->signal_activate().connect( + [this](const Gtk::TextIter &iter, const Gdk::Rectangle &, GdkEvent *) { + if (toggle_breakpoint) + toggle_breakpoint(iter.get_line()); + }); + } + }); + + if (language && (language->get_id() == "chdr" || language->get_id() == "cpphdr" || language->get_id() == "c" || + language->get_id() == "cpp" || language->get_id() == "objc" || language->get_id() == "java" || + language->get_id() == "js" || language->get_id() == "ts" || language->get_id() == "proto" || + language->get_id() == "c-sharp" || language->get_id() == "html" || language->get_id() == "cuda" || + language->get_id() == "php" || language->get_id() == "rust" || language->get_id() == "swift" || + language->get_id() == "go" || language->get_id() == "scala" || language->get_id() == "opencl" || + language->get_id() == "json" || language->get_id() == "css")) + is_bracket_language = true; + + setup_tooltip_and_dialog_events(); + setup_format_style(is_generic_view); + #ifndef __APPLE__ - set_tab_width(4); //Visual size of a \t hardcoded to be equal to visual size of 4 spaces. Buggy on OS X + set_tab_width(4); //Visual size of a \t hardcoded to be equal to visual size of 4 spaces. Buggy on OS X #endif - tab_char=Config::get().source.default_tab_char; - tab_size=Config::get().source.default_tab_size; - if(Config::get().source.auto_tab_char_and_size) { - auto tab_char_and_size=find_tab_char_and_size(); - if(tab_char_and_size.second!=0) { - if(tab_char!=tab_char_and_size.first || tab_size!=tab_char_and_size.second) { - std::string tab_str; - if(tab_char_and_size.first==' ') - tab_str="<space>"; - else - tab_str="<tab>"; - } - - tab_char=tab_char_and_size.first; - tab_size=tab_char_and_size.second; + tab_char = Config::get().source.default_tab_char; + tab_size = Config::get().source.default_tab_size; + if (Config::get().source.auto_tab_char_and_size) { + auto tab_char_and_size = find_tab_char_and_size(); + if (tab_char_and_size.second != 0) { + if (tab_char != tab_char_and_size.first || tab_size != tab_char_and_size.second) { + std::string tab_str; + if (tab_char_and_size.first == ' ') + tab_str = "<space>"; + else + tab_str = "<tab>"; + } + + tab_char = tab_char_and_size.first; + tab_size = tab_char_and_size.second; + } } - } - set_tab_char_and_size(tab_char, tab_size); - - std::string comment_characters; - if(is_bracket_language) - comment_characters="//"; - else if(language) { - if(language->get_id()=="cmake" || language->get_id()=="makefile" || language->get_id()=="python" || - language->get_id()=="python3" || language->get_id()=="sh" || language->get_id()=="perl" || - language->get_id()=="ruby" || language->get_id()=="r" || language->get_id()=="asm" || - language->get_id()=="automake") - comment_characters="#"; - else if(language->get_id()=="latex" || language->get_id()=="matlab" || language->get_id()=="octave" || - language->get_id()=="bibtex") - comment_characters="%"; - else if(language->get_id()=="fortran") - comment_characters="!"; - else if(language->get_id()=="pascal") - comment_characters="//"; - else if(language->get_id()=="lua") - comment_characters="--"; - } - if(!comment_characters.empty()) { - toggle_comments=[this, comment_characters=std::move(comment_characters)] { - std::vector<int> lines; - Gtk::TextIter selection_start, selection_end; - get_buffer()->get_selection_bounds(selection_start, selection_end); - auto line_start=selection_start.get_line(); - auto line_end=selection_end.get_line(); - if(line_start!=line_end && selection_end.starts_line()) - --line_end; - bool lines_commented=true; - bool extra_spaces=true; - int min_indentation=-1; - for(auto line=line_start;line<=line_end;++line) { - auto iter=get_buffer()->get_iter_at_line(line); - bool line_added=false; - bool line_commented=false; - bool extra_space=false; - int indentation=0; - for(;;) { - if(iter.ends_line()) - break; - else if(*iter==' ' || *iter=='\t') { - ++indentation; - iter.forward_char(); - continue; - } - else { - lines.emplace_back(line); - line_added=true; - for(size_t c=0;c<comment_characters.size();++c) { - if(iter.ends_line()) { - break; - } - else if(*iter==static_cast<unsigned int>(comment_characters[c])) { - if(c<comment_characters.size()-1) { - iter.forward_char(); - continue; + set_tab_char_and_size(tab_char, tab_size); + + std::string comment_characters; + if (is_bracket_language) + comment_characters = "//"; + else if (language) { + if (language->get_id() == "cmake" || language->get_id() == "makefile" || language->get_id() == "python" || + language->get_id() == "python3" || language->get_id() == "sh" || language->get_id() == "perl" || + language->get_id() == "ruby" || language->get_id() == "r" || language->get_id() == "asm" || + language->get_id() == "automake") + comment_characters = "#"; + else if (language->get_id() == "latex" || language->get_id() == "matlab" || language->get_id() == "octave" || + language->get_id() == "bibtex") + comment_characters = "%"; + else if (language->get_id() == "fortran") + comment_characters = "!"; + else if (language->get_id() == "pascal") + comment_characters = "//"; + else if (language->get_id() == "lua") + comment_characters = "--"; + } + if (!comment_characters.empty()) { + toggle_comments = [this, comment_characters = std::move(comment_characters)] { + std::vector<int> lines; + Gtk::TextIter selection_start, selection_end; + get_buffer()->get_selection_bounds(selection_start, selection_end); + auto line_start = selection_start.get_line(); + auto line_end = selection_end.get_line(); + if (line_start != line_end && selection_end.starts_line()) + --line_end; + bool lines_commented = true; + bool extra_spaces = true; + int min_indentation = -1; + for (auto line = line_start; line <= line_end; ++line) { + auto iter = get_buffer()->get_iter_at_line(line); + bool line_added = false; + bool line_commented = false; + bool extra_space = false; + int indentation = 0; + for (;;) { + if (iter.ends_line()) + break; + else if (*iter == ' ' || *iter == '\t') { + ++indentation; + iter.forward_char(); + continue; + } else { + lines.emplace_back(line); + line_added = true; + for (size_t c = 0; c < comment_characters.size(); ++c) { + if (iter.ends_line()) { + break; + } else if (*iter == static_cast<unsigned int>(comment_characters[c])) { + if (c < comment_characters.size() - 1) { + iter.forward_char(); + continue; + } else { + line_commented = true; + if (!iter.ends_line()) { + iter.forward_char(); + if (*iter == ' ') + extra_space = true; + } + break; + } + } else + break; + } + break; + } } - else { - line_commented=true; - if(!iter.ends_line()) { - iter.forward_char(); - if(*iter==' ') - extra_space=true; - } - break; + if (line_added) { + lines_commented &= line_commented; + extra_spaces &= extra_space; + if (min_indentation == -1 || indentation < min_indentation) + min_indentation = indentation; } - } - else - break; } - break; - } - } - if(line_added) { - lines_commented&=line_commented; - extra_spaces&=extra_space; - if(min_indentation==-1 || indentation<min_indentation) - min_indentation=indentation; - } - } - if(lines.size()) { - auto comment_characters_and_space=comment_characters+' '; - get_buffer()->begin_user_action(); - for(auto &line: lines) { - auto iter=get_buffer()->get_iter_at_line(line); - iter.forward_chars(min_indentation); - if(lines_commented) { - auto end_iter=iter; - end_iter.forward_chars(comment_characters.size()+static_cast<int>(extra_spaces)); - while(*iter==' ' || *iter=='\t') { - iter.forward_char(); - end_iter.forward_char(); - } - get_buffer()->erase(iter, end_iter); - } - else - get_buffer()->insert(iter, comment_characters_and_space); - } - get_buffer()->end_user_action(); - } - }; - } + if (lines.size()) { + auto comment_characters_and_space = comment_characters + ' '; + get_buffer()->begin_user_action(); + for (auto &line: lines) { + auto iter = get_buffer()->get_iter_at_line(line); + iter.forward_chars(min_indentation); + if (lines_commented) { + auto end_iter = iter; + end_iter.forward_chars(comment_characters.size() + static_cast<int>(extra_spaces)); + while (*iter == ' ' || *iter == '\t') { + iter.forward_char(); + end_iter.forward_char(); + } + get_buffer()->erase(iter, end_iter); + } else + get_buffer()->insert(iter, comment_characters_and_space); + } + get_buffer()->end_user_action(); + } + }; + } } void Source::View::set_tab_char_and_size(char tab_char, unsigned tab_size) { - this->tab_char=tab_char; - this->tab_size=tab_size; - - tab.clear(); - for(unsigned c=0;c<tab_size;c++) - tab+=tab_char; + this->tab_char = tab_char; + this->tab_size = tab_size; + + tab.clear(); + for (unsigned c = 0; c < tab_size; c++) + tab += tab_char; } void Source::View::cleanup_whitespace_characters() { - auto buffer=get_buffer(); - buffer->begin_user_action(); - for(int line=0;line<buffer->get_line_count();line++) { - auto iter=buffer->get_iter_at_line(line); - auto end_iter=get_iter_at_line_end(line); - if(iter==end_iter) - continue; - iter=end_iter; - while(!iter.starts_line() && (*iter==' ' || *iter=='\t' || iter.ends_line())) - iter.backward_char(); - if(*iter!=' ' && *iter!='\t') - iter.forward_char(); - if(iter==end_iter) - continue; - buffer->erase(iter, end_iter); - } - auto iter=buffer->end(); - if(!iter.starts_line()) - buffer->insert(buffer->end(), "\n"); - buffer->end_user_action(); + auto buffer = get_buffer(); + buffer->begin_user_action(); + for (int line = 0; line < buffer->get_line_count(); line++) { + auto iter = buffer->get_iter_at_line(line); + auto end_iter = get_iter_at_line_end(line); + if (iter == end_iter) + continue; + iter = end_iter; + while (!iter.starts_line() && (*iter == ' ' || *iter == '\t' || iter.ends_line())) + iter.backward_char(); + if (*iter != ' ' && *iter != '\t') + iter.forward_char(); + if (iter == end_iter) + continue; + buffer->erase(iter, end_iter); + } + auto iter = buffer->end(); + if (!iter.starts_line()) + buffer->insert(buffer->end(), "\n"); + buffer->end_user_action(); } Gsv::DrawSpacesFlags Source::View::parse_show_whitespace_characters(const std::string &text) { - namespace qi = boost::spirit::qi; - - qi::symbols<char, Gsv::DrawSpacesFlags> options; - options.add - ("space", Gsv::DRAW_SPACES_SPACE) - ("tab", Gsv::DRAW_SPACES_TAB) - ("newline", Gsv::DRAW_SPACES_NEWLINE) - ("nbsp", Gsv::DRAW_SPACES_NBSP) - ("leading", Gsv::DRAW_SPACES_LEADING) - ("text", Gsv::DRAW_SPACES_TEXT) - ("trailing", Gsv::DRAW_SPACES_TRAILING) - ("all", Gsv::DRAW_SPACES_ALL); - - std::set<Gsv::DrawSpacesFlags> out; - - // parse comma-separated list of options - qi::phrase_parse(text.begin(), text.end(), options % ',', qi::space, out); - - return out.count(Gsv::DRAW_SPACES_ALL)>0 ? - Gsv::DRAW_SPACES_ALL : - static_cast<Gsv::DrawSpacesFlags>(std::accumulate(out.begin(), out.end(), 0)); + namespace qi = boost::spirit::qi; + + qi::symbols<char, Gsv::DrawSpacesFlags> options; + options.add + ("space", Gsv::DRAW_SPACES_SPACE) + ("tab", Gsv::DRAW_SPACES_TAB) + ("newline", Gsv::DRAW_SPACES_NEWLINE) + ("nbsp", Gsv::DRAW_SPACES_NBSP) + ("leading", Gsv::DRAW_SPACES_LEADING) + ("text", Gsv::DRAW_SPACES_TEXT) + ("trailing", Gsv::DRAW_SPACES_TRAILING) + ("all", Gsv::DRAW_SPACES_ALL); + + std::set<Gsv::DrawSpacesFlags> out; + + // parse comma-separated list of options + qi::phrase_parse(text.begin(), text.end(), options % ',', qi::space, out); + + return out.count(Gsv::DRAW_SPACES_ALL) > 0 ? + Gsv::DRAW_SPACES_ALL : + static_cast<Gsv::DrawSpacesFlags>(std::accumulate(out.begin(), out.end(), 0)); } bool Source::View::save() { - if(file_path.empty() || !get_buffer()->get_modified()) - return false; - if(Config::get().source.cleanup_whitespace_characters) - cleanup_whitespace_characters(); - - if(format_style) { - if(Config::get().source.format_style_on_save) - format_style(true); - else if(Config::get().source.format_style_on_save_if_style_file_found) - format_style(false); - hide_tooltips(); - } - - std::ofstream output(file_path.string(), std::ofstream::binary); - if(output) { - auto start_iter=get_buffer()->begin(); - auto end_iter=start_iter; - bool end_reached=false; - while(!end_reached) { - for(size_t c=0;c<131072;c++) { - if(!end_iter.forward_char()) { - end_reached=true; - break; - } - } - output << get_buffer()->get_text(start_iter, end_iter).c_str(); - start_iter=end_iter; - } - output.close(); - boost::system::error_code ec; - last_write_time=boost::filesystem::last_write_time(file_path, ec); - if(ec) - last_write_time=static_cast<std::time_t>(-1); - // Remonitor file in case it did not exist before - monitor_file(); - get_buffer()->set_modified(false); - Directories::get().on_save_file(file_path); - return true; - } - else { - Terminal::get().print("Error: could not save file "+file_path.string()+"\n", true); - return false; - } + if (file_path.empty() || !get_buffer()->get_modified()) + return false; + if (Config::get().source.cleanup_whitespace_characters) + cleanup_whitespace_characters(); + + if (format_style) { + if (Config::get().source.format_style_on_save) + format_style(true); + else if (Config::get().source.format_style_on_save_if_style_file_found) + format_style(false); + } + + std::ofstream output(file_path.string(), std::ofstream::binary); + if (output) { + auto start_iter = get_buffer()->begin(); + auto end_iter = start_iter; + bool end_reached = false; + while (!end_reached) { + for (size_t c = 0; c < 131072; c++) { + if (!end_iter.forward_char()) { + end_reached = true; + break; + } + } + output << get_buffer()->get_text(start_iter, end_iter).c_str(); + start_iter = end_iter; + } + output.close(); + boost::system::error_code ec; + last_write_time = boost::filesystem::last_write_time(file_path, ec); + if (ec) + last_write_time = static_cast<std::time_t>(-1); + // Remonitor file in case it did not exist before + monitor_file(); + get_buffer()->set_modified(false); + Directories::get().on_save_file(file_path); + return true; + } else { + Terminal::get().print("Error: could not save file " + file_path.string() + "\n", true); + return false; + } } void Source::View::configure() { - SpellCheckView::configure(); - DiffView::configure(); - - if(Config::get().source.style.size()>0) { - auto scheme = StyleSchemeManager::get_default()->get_scheme(Config::get().source.style); - if(scheme) - get_source_buffer()->set_style_scheme(scheme); - } - - set_draw_spaces(parse_show_whitespace_characters(Config::get().source.show_whitespace_characters)); - - if(Config::get().source.wrap_lines) - set_wrap_mode(Gtk::WrapMode::WRAP_CHAR); - else - set_wrap_mode(Gtk::WrapMode::WRAP_NONE); - property_highlight_current_line() = Config::get().source.highlight_current_line; - property_show_line_numbers() = Config::get().source.show_line_numbers; - if(Config::get().source.font.size()>0) - override_font(Pango::FontDescription(Config::get().source.font)); - if(Config::get().source.show_background_pattern) - gtk_source_view_set_background_pattern(this->gobj(), GTK_SOURCE_BACKGROUND_PATTERN_TYPE_GRID); - else - gtk_source_view_set_background_pattern(this->gobj(), GTK_SOURCE_BACKGROUND_PATTERN_TYPE_NONE); - property_show_right_margin() = Config::get().source.show_right_margin; - property_right_margin_position() = Config::get().source.right_margin_position; - - //Create tags for diagnostic warnings and errors: - auto scheme = get_source_buffer()->get_style_scheme(); - auto tag_table=get_buffer()->get_tag_table(); - auto style=scheme->get_style("def:warning"); - auto diagnostic_tag=get_buffer()->get_tag_table()->lookup("def:warning"); - auto diagnostic_tag_underline=get_buffer()->get_tag_table()->lookup("def:warning_underline"); - if(style && (style->property_foreground_set() || style->property_background_set())) { - Glib::ustring warning_property; - if(style->property_foreground_set()) { - warning_property=style->property_foreground().get_value(); - diagnostic_tag->property_foreground() = warning_property; - } - else if(style->property_background_set()) - warning_property=style->property_background().get_value(); - - diagnostic_tag_underline->property_underline()=Pango::Underline::UNDERLINE_ERROR; - auto tag_class=G_OBJECT_GET_CLASS(diagnostic_tag_underline->gobj()); //For older GTK+ 3 versions: - auto param_spec=g_object_class_find_property(tag_class, "underline-rgba"); - if(param_spec!=nullptr) { - diagnostic_tag_underline->set_property("underline-rgba", Gdk::RGBA(warning_property)); + SpellCheckView::configure(); + DiffView::configure(); + + if (Config::get().source.style.size() > 0) { + auto scheme = StyleSchemeManager::get_default()->get_scheme(Config::get().source.style); + if (scheme) + get_source_buffer()->set_style_scheme(scheme); + } + + set_draw_spaces(parse_show_whitespace_characters(Config::get().source.show_whitespace_characters)); + + if (Config::get().source.wrap_lines) + set_wrap_mode(Gtk::WrapMode::WRAP_CHAR); + else + set_wrap_mode(Gtk::WrapMode::WRAP_NONE); + property_highlight_current_line() = Config::get().source.highlight_current_line; + property_show_line_numbers() = Config::get().source.show_line_numbers; + if (Config::get().source.font.size() > 0) + override_font(Pango::FontDescription(Config::get().source.font)); + if (Config::get().source.show_background_pattern) + gtk_source_view_set_background_pattern(this->gobj(), GTK_SOURCE_BACKGROUND_PATTERN_TYPE_GRID); + else + gtk_source_view_set_background_pattern(this->gobj(), GTK_SOURCE_BACKGROUND_PATTERN_TYPE_NONE); + property_show_right_margin() = Config::get().source.show_right_margin; + property_right_margin_position() = Config::get().source.right_margin_position; + + //Create tags for diagnostic warnings and errors: + auto scheme = get_source_buffer()->get_style_scheme(); + auto tag_table = get_buffer()->get_tag_table(); + auto style = scheme->get_style("def:warning"); + auto diagnostic_tag = get_buffer()->get_tag_table()->lookup("def:warning"); + auto diagnostic_tag_underline = get_buffer()->get_tag_table()->lookup("def:warning_underline"); + if (style && (style->property_foreground_set() || style->property_background_set())) { + Glib::ustring warning_property; + if (style->property_foreground_set()) { + warning_property = style->property_foreground().get_value(); + diagnostic_tag->property_foreground() = warning_property; + } else if (style->property_background_set()) + warning_property = style->property_background().get_value(); + + diagnostic_tag_underline->property_underline() = Pango::Underline::UNDERLINE_ERROR; + auto tag_class = G_OBJECT_GET_CLASS(diagnostic_tag_underline->gobj()); //For older GTK+ 3 versions: + auto param_spec = g_object_class_find_property(tag_class, "underline-rgba"); + if (param_spec != nullptr) { + diagnostic_tag_underline->set_property("underline-rgba", Gdk::RGBA(warning_property)); + } + } + style = scheme->get_style("def:error"); + diagnostic_tag = get_buffer()->get_tag_table()->lookup("def:error"); + diagnostic_tag_underline = get_buffer()->get_tag_table()->lookup("def:error_underline"); + if (style && (style->property_foreground_set() || style->property_background_set())) { + Glib::ustring error_property; + if (style->property_foreground_set()) { + error_property = style->property_foreground().get_value(); + diagnostic_tag->property_foreground() = error_property; + } else if (style->property_background_set()) + error_property = style->property_background().get_value(); + + diagnostic_tag_underline->property_underline() = Pango::Underline::UNDERLINE_ERROR; + diagnostic_tag_underline->set_property("underline-rgba", Gdk::RGBA(error_property)); + } + //TODO: clear tag_class and param_spec? + + if (Config::get().menu.keys["source_show_completion"].empty()) { + get_completion()->unblock_interactive(); + interactive_completion = true; + } else { + get_completion()->block_interactive(); + interactive_completion = false; } - } - style=scheme->get_style("def:error"); - diagnostic_tag=get_buffer()->get_tag_table()->lookup("def:error"); - diagnostic_tag_underline=get_buffer()->get_tag_table()->lookup("def:error_underline"); - if(style && (style->property_foreground_set() || style->property_background_set())) { - Glib::ustring error_property; - if(style->property_foreground_set()) { - error_property=style->property_foreground().get_value(); - diagnostic_tag->property_foreground() = error_property; - } - else if(style->property_background_set()) - error_property=style->property_background().get_value(); - - diagnostic_tag_underline->property_underline()=Pango::Underline::UNDERLINE_ERROR; - diagnostic_tag_underline->set_property("underline-rgba", Gdk::RGBA(error_property)); - } - //TODO: clear tag_class and param_spec? - - if(Config::get().menu.keys["source_show_completion"].empty()) { - get_completion()->unblock_interactive(); - interactive_completion=true; - } - else { - get_completion()->block_interactive(); - interactive_completion=false; - } } void Source::View::setup_tooltip_and_dialog_events() { - type_tooltips.on_motion=[this] { - delayed_tooltips_connection.disconnect(); - }; - diagnostic_tooltips.on_motion=[this] { - delayed_tooltips_connection.disconnect(); - }; - - get_buffer()->signal_changed().connect([this] { - hide_tooltips(); - }); - - signal_motion_notify_event().connect([this](GdkEventMotion* event) { - if(on_motion_last_x!=event->x || on_motion_last_y!=event->y) { - delayed_tooltips_connection.disconnect(); - if((event->state&GDK_BUTTON1_MASK)==0) { - gdouble x=event->x; - gdouble y=event->y; - delayed_tooltips_connection=Glib::signal_timeout().connect([this, x, y]() { - type_tooltips.hide(); - diagnostic_tooltips.hide(); - Tooltips::init(); - Gdk::Rectangle rectangle(x, y, 1, 1); - if(parsed) { - show_type_tooltips(rectangle); - show_diagnostic_tooltips(rectangle); - } - return false; - }, 100); - } - auto last_mouse_pos = std::make_pair(on_motion_last_x, on_motion_last_y); - auto mouse_pos = std::make_pair(event->x, event->y); - type_tooltips.hide(last_mouse_pos, mouse_pos); - diagnostic_tooltips.hide(last_mouse_pos, mouse_pos); - } - on_motion_last_x=event->x; - on_motion_last_y=event->y; - return false; - }); - - get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator& iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark>& mark) { - if(get_buffer()->get_has_selection() && mark->get_name()=="selection_bound") - delayed_tooltips_connection.disconnect(); - - if(mark->get_name()=="insert") { - hide_tooltips(); - delayed_tooltips_connection=Glib::signal_timeout().connect([this]() { - Tooltips::init(); - Gdk::Rectangle rectangle; - get_iter_location(get_buffer()->get_insert()->get_iter(), rectangle); - int location_window_x, location_window_y; - buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, rectangle.get_x(), rectangle.get_y(), location_window_x, location_window_y); - rectangle.set_x(location_window_x-2); - rectangle.set_y(location_window_y); - rectangle.set_width(5); - if(parsed) { - show_type_tooltips(rectangle); - show_diagnostic_tooltips(rectangle); + type_tooltips.on_motion = [this] { + delayed_tooltips_connection.disconnect(); + }; + diagnostic_tooltips.on_motion = [this] { + delayed_tooltips_connection.disconnect(); + }; + + get_buffer()->signal_changed().connect([this] { + hide_tooltips(); + }); + + signal_motion_notify_event().connect([this](GdkEventMotion *event) { + if (on_motion_last_x != event->x || on_motion_last_y != event->y) { + delayed_tooltips_connection.disconnect(); + if ((event->state & GDK_BUTTON1_MASK) == 0) { + gdouble x = event->x; + gdouble y = event->y; + delayed_tooltips_connection = Glib::signal_timeout().connect([this, x, y]() { + type_tooltips.hide(); + diagnostic_tooltips.hide(); + Tooltips::init(); + Gdk::Rectangle rectangle(x, y, 1, 1); + if (parsed) { + show_type_tooltips(rectangle); + show_diagnostic_tooltips(rectangle); + } + return false; + }, 100); + } + auto last_mouse_pos = std::make_pair(on_motion_last_x, on_motion_last_y); + auto mouse_pos = std::make_pair(event->x, event->y); + type_tooltips.hide(last_mouse_pos, mouse_pos); + diagnostic_tooltips.hide(last_mouse_pos, mouse_pos); } + on_motion_last_x = event->x; + on_motion_last_y = event->y; return false; - }, 500); - - if(SelectionDialog::get()) - SelectionDialog::get()->hide(); - if(CompletionDialog::get()) - CompletionDialog::get()->hide(); - - if(update_status_location) - update_status_location(this); - } - }); + }); - signal_scroll_event().connect([this](GdkEventScroll* event) { - hide_tooltips(); - hide_dialogs(); - return false; - }); - - signal_focus_out_event().connect([this](GdkEventFocus* event) { - hide_tooltips(); - return false; - }); - - signal_leave_notify_event().connect([this](GdkEventCrossing*) { - delayed_tooltips_connection.disconnect(); - return false; - }); + get_buffer()->signal_mark_set().connect( + [this](const Gtk::TextBuffer::iterator &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { + if (get_buffer()->get_has_selection() && mark->get_name() == "selection_bound") + delayed_tooltips_connection.disconnect(); + + if (mark->get_name() == "insert") { + hide_tooltips(); + delayed_tooltips_connection = Glib::signal_timeout().connect([this]() { + Tooltips::init(); + Gdk::Rectangle rectangle; + get_iter_location(get_buffer()->get_insert()->get_iter(), rectangle); + int location_window_x, location_window_y; + buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, rectangle.get_x(), + rectangle.get_y(), location_window_x, location_window_y); + rectangle.set_x(location_window_x - 2); + rectangle.set_y(location_window_y); + rectangle.set_width(5); + if (parsed) { + show_type_tooltips(rectangle); + show_diagnostic_tooltips(rectangle); + } + return false; + }, 500); + + if (SelectionDialog::get()) + SelectionDialog::get()->hide(); + if (CompletionDialog::get()) + CompletionDialog::get()->hide(); + + if (update_status_location) + update_status_location(this); + } + }); + + signal_scroll_event().connect([this](GdkEventScroll *event) { + hide_tooltips(); + hide_dialogs(); + return false; + }); + + signal_focus_out_event().connect([this](GdkEventFocus *event) { + hide_tooltips(); + return false; + }); + + signal_leave_notify_event().connect([this](GdkEventCrossing *) { + delayed_tooltips_connection.disconnect(); + return false; + }); } void Source::View::setup_format_style(bool is_generic_view) { - static auto prettier = filesystem::find_executable("prettier"); - if(!prettier.empty() && language && - (language->get_id()=="js" || language->get_id()=="json" || language->get_id()=="css")) { - if(is_generic_view) { - goto_next_diagnostic=[this] { - place_cursor_at_next_diagnostic(); - }; - get_buffer()->signal_changed().connect([this] { - clear_diagnostic_tooltips(); - status_diagnostics=std::make_tuple<size_t, size_t, size_t>(0, 0, 0); - if(update_status_diagnostics) - update_status_diagnostics(this); - }); - } - format_style=[this, is_generic_view](bool continue_without_style_file) { - auto command=prettier.string(); - if(!continue_without_style_file) { - std::stringstream stdin_stream, stdout_stream; - auto exit_status=Terminal::get().process(stdin_stream, stdout_stream, command+" --find-config-path "+this->file_path.string()); - if(exit_status==0) { - if(stdout_stream.tellp()==0) - return; - } - else - return; - } - - command+=" --stdin-filepath "+this->file_path.string()+" --print-width 120 --config-precedence prefer-file"; - - if(get_buffer()->get_has_selection()) { // Cannot be used together with --cursor-offset - Gtk::TextIter start, end; - get_buffer()->get_selection_bounds(start, end); - command+=" --range-start "+std::to_string(start.get_offset()); - command+=" --range-end "+std::to_string(end.get_offset()); - } - else - command+=" --cursor-offset "+std::to_string(get_buffer()->get_insert()->get_iter().get_offset()); - - size_t num_warnings=0, num_errors=0, num_fix_its=0; - if(is_generic_view) - clear_diagnostic_tooltips(); - - std::stringstream stdin_stream(get_buffer()->get_text()), stdout_stream, stderr_stream; - auto exit_status=Terminal::get().process(stdin_stream, stdout_stream, command, this->file_path.parent_path(), &stderr_stream); - if(exit_status==0) { - replace_text(stdout_stream.str()); - std::string line; - std::getline(stderr_stream, line); - if(!line.empty() && line!="NaN") { - try { - auto offset=atoi(line.c_str()); - if(offset<get_buffer()->size()) { - get_buffer()->place_cursor(get_buffer()->get_iter_at_offset(offset)); - hide_tooltips(); - } - } - catch(...) {} - } - } - else if(is_generic_view) { - static std::regex regex("^\\[error\\] stdin: (.*) \\(([0-9]*):([0-9]*)\\)$"); - std::string line; - std::getline(stderr_stream, line); - std::smatch sm; - if(std::regex_match(line, sm, regex)) { - try { - auto start=get_iter_at_line_offset(atoi(sm[2].str().c_str())-1, atoi(sm[3].str().c_str())-1); - ++num_errors; - if(start.ends_line()) - start.backward_char(); - auto end=start; - end.forward_char(); - if(start==end) - start.forward_char(); - - add_diagnostic_tooltip(start, end, sm[1].str(), true); - } - catch(...) {} - } - } - if(is_generic_view) { - status_diagnostics=std::make_tuple(num_warnings, num_errors, num_fix_its); - if(update_status_diagnostics) - update_status_diagnostics(this); - } - }; - } - else if(is_bracket_language) { - format_style=[this](bool continue_without_style_file) { - static auto clang_format_command = filesystem::get_executable("clang-format").string(); - - auto command=clang_format_command+" -output-replacements-xml -assume-filename="+filesystem::escape_argument(this->file_path.string()); - - if(get_buffer()->get_has_selection()) { - Gtk::TextIter start, end; - get_buffer()->get_selection_bounds(start, end); - command+=" -lines="+std::to_string(start.get_line()+1)+':'+std::to_string(end.get_line()+1); - } - - bool use_style_file=false; - auto style_file_search_path=this->file_path.parent_path(); - while(true) { - if(boost::filesystem::exists(style_file_search_path/".clang-format") || boost::filesystem::exists(style_file_search_path/"_clang-format")) { - use_style_file=true; - break; - } - if(style_file_search_path==style_file_search_path.root_directory()) - break; - style_file_search_path=style_file_search_path.parent_path(); - } - - if(use_style_file) - command+=" -style=file"; - else { - if(!continue_without_style_file) - return; - unsigned indent_width; - std::string tab_style; - if(tab_char=='\t') { - indent_width=tab_size*8; - tab_style="UseTab: Always"; + static auto prettier = filesystem::find_executable("prettier"); + if (!prettier.empty() && language && + (language->get_id() == "js" || language->get_id() == "json" || language->get_id() == "css")) { + if (is_generic_view) { + goto_next_diagnostic = [this] { + place_cursor_at_next_diagnostic(); + }; + get_buffer()->signal_changed().connect([this] { + clear_diagnostic_tooltips(); + status_diagnostics = std::make_tuple<size_t, size_t, size_t>(0, 0, 0); + if (update_status_diagnostics) + update_status_diagnostics(this); + }); } - else { - indent_width=tab_size; - tab_style="UseTab: Never"; - } - command+=" -style=\"{IndentWidth: "+std::to_string(indent_width); - command+=", "+tab_style; - command+=", "+std::string("AccessModifierOffset: -")+std::to_string(indent_width); - if(Config::get().source.clang_format_style!="") - command+=", "+Config::get().source.clang_format_style; - command+="}\""; - } - - std::stringstream stdin_stream(get_buffer()->get_text()), stdout_stream; - - auto exit_status=Terminal::get().process(stdin_stream, stdout_stream, command, this->file_path.parent_path()); - if(exit_status==0) { - // The following code is complex due to clang-format returning offsets in byte offsets instead of char offsets - - // Create bytes_in_lines cache to significantly speed up the processing of finding iterators from byte offsets - std::vector<size_t> bytes_in_lines; - auto line_count=get_buffer()->get_line_count(); - for(int line_nr=0;line_nr<line_count;++line_nr) { - auto iter=get_buffer()->get_iter_at_line(line_nr); - bytes_in_lines.emplace_back(iter.get_bytes_in_line()); - } - - get_buffer()->begin_user_action(); - try { - boost::property_tree::ptree pt; - boost::property_tree::xml_parser::read_xml(stdout_stream, pt); - auto replacements_pt=pt.get_child("replacements"); - for(auto it=replacements_pt.rbegin();it!=replacements_pt.rend();++it) { - if(it->first=="replacement") { - auto offset=it->second.get<size_t>("<xmlattr>.offset"); - auto length=it->second.get<size_t>("<xmlattr>.length"); - auto replacement_str=it->second.get<std::string>(""); - - size_t bytes=0; - for(size_t c=0;c<bytes_in_lines.size();++c) { - auto previous_bytes=bytes; - bytes+=bytes_in_lines[c]; - if(offset<bytes || (c==bytes_in_lines.size()-1 && offset==bytes)) { - std::pair<size_t, size_t> line_index(c, offset-previous_bytes); - auto start=get_buffer()->get_iter_at_line_index(line_index.first, line_index.second); - - // Use left gravity insert to avoid moving cursor from end of line - bool left_gravity_insert=false; - if(get_buffer()->get_insert()->get_iter()==start) { - auto iter=start; - do { - if(*iter!=' ' && *iter!='\t') { - left_gravity_insert=iter.ends_line(); - break; - } - } while(iter.forward_char()); - } - - if(length>0) { - auto offset_end=offset+length; - size_t bytes=0; - for(size_t c=0;c<bytes_in_lines.size();++c) { - auto previous_bytes=bytes; - bytes+=bytes_in_lines[c]; - if(offset_end<bytes || (c==bytes_in_lines.size()-1 && offset_end==bytes)) { - auto end=get_buffer()->get_iter_at_line_index(c, offset_end-previous_bytes); - get_buffer()->erase(start, end); - start=get_buffer()->get_iter_at_line_index(line_index.first, line_index.second); - break; - } + format_style = [this, is_generic_view](bool continue_without_style_file) { + auto command = prettier.string(); + if (!continue_without_style_file) { + std::stringstream stdin_stream, stdout_stream; + auto exit_status = Terminal::get().process(stdin_stream, stdout_stream, + command + " --find-config-path " + this->file_path.string()); + if (exit_status == 0) { + if (stdout_stream.tellp() == 0) + return; + } else + return; + } + + command += " --stdin-filepath " + this->file_path.string() + + " --print-width 120 --config-precedence prefer-file"; + + if (get_buffer()->get_has_selection()) { // Cannot be used together with --cursor-offset + Gtk::TextIter start, end; + get_buffer()->get_selection_bounds(start, end); + command += " --range-start " + std::to_string(start.get_offset()); + command += " --range-end " + std::to_string(end.get_offset()); + } else + command += " --cursor-offset " + std::to_string(get_buffer()->get_insert()->get_iter().get_offset()); + + size_t num_warnings = 0, num_errors = 0, num_fix_its = 0; + if (is_generic_view) + clear_diagnostic_tooltips(); + + std::stringstream stdin_stream(get_buffer()->get_text()), stdout_stream, stderr_stream; + auto exit_status = Terminal::get().process(stdin_stream, stdout_stream, command, + this->file_path.parent_path(), &stderr_stream); + if (exit_status == 0) { + replace_text(stdout_stream.str()); + std::string line; + std::getline(stderr_stream, line); + if (!line.empty() && line != "NaN") { + try { + auto offset = atoi(line.c_str()); + if (offset < get_buffer()->size()) { + get_buffer()->place_cursor(get_buffer()->get_iter_at_offset(offset)); + hide_tooltips(); + } + } + catch (...) {} + } + } else if (is_generic_view) { + static std::regex regex("^\\[error\\] stdin: (.*) \\(([0-9]*):([0-9]*)\\)$"); + std::string line; + std::getline(stderr_stream, line); + std::smatch sm; + if (std::regex_match(line, sm, regex)) { + try { + auto start = get_iter_at_line_offset(atoi(sm[2].str().c_str()) - 1, + atoi(sm[3].str().c_str()) - 1); + ++num_errors; + if (start.ends_line()) + start.backward_char(); + auto end = start; + end.forward_char(); + if (start == end) + start.forward_char(); + + add_diagnostic_tooltip(start, end, sm[1].str(), true); } - } - if(left_gravity_insert) { - auto mark=get_buffer()->create_mark(start); - get_buffer()->insert(start, replacement_str); - get_buffer()->place_cursor(mark->get_iter()); - get_buffer()->delete_mark(mark); - } - else - get_buffer()->insert(start, replacement_str); - break; + catch (...) {} } - } } - } - } - catch(const std::exception &e) { - Terminal::get().print(std::string("Error: error parsing clang-format output: ")+e.what()+'\n', true); - } - get_buffer()->end_user_action(); - } - }; - } - else if(language && language->get_id()=="markdown") { - // The style file currently has no options, but checking if it exists - format_style=[this](bool continue_without_style_file) { - bool has_style_file=false; - auto style_file_search_path=this->file_path.parent_path(); - while(true) { - if(boost::filesystem::exists(style_file_search_path/".markdown-format")) { - has_style_file=true; - break; - } - if(style_file_search_path==style_file_search_path.root_directory()) - break; - style_file_search_path=style_file_search_path.parent_path(); - } - if(!has_style_file && !continue_without_style_file) - return; - - auto special_character=[](Gtk::TextIter iter) { - if(*iter=='*' || *iter=='#' || *iter=='<' || *iter=='>' || *iter==' ' || *iter=='=' || *iter=='`' || *iter=='-') - return true; - // Tests if a line starts with for instance: 2. - if(*iter>='0' && *iter<='9' && iter.forward_char() && - *iter=='.' && iter.forward_char() && - *iter==' ') - return true; - return false; - }; - - get_buffer()->begin_user_action(); - disable_spellcheck=true; - cleanup_whitespace_characters(); - - auto iter=get_buffer()->begin(); - size_t last_space_offset=-1; - bool headline=false; - bool monospace=false; - bool script=false; - bool html_tag=false; - int square_brackets=0; - do { - if(iter.starts_line()) { - last_space_offset=-1; - auto next_line_iter=iter; - if(*iter=='#' || (next_line_iter.forward_line() && *next_line_iter=='=')) - headline=true; - else - headline=false; - auto test_iter=iter; - if(*test_iter=='`' && test_iter.forward_char() && - *test_iter=='`' && test_iter.forward_char() && - *test_iter=='`') { - script=!script; - iter.forward_chars(3); - continue; - } - } - if(!script && *iter=='`') - monospace=!monospace; - if(!script && !monospace) { - if(*iter=='<') - html_tag=true; - else if(*iter=='>') - html_tag=false; - else if(*iter=='[') - ++square_brackets; - else if(*iter==']') - --square_brackets; - } - if(!headline && !script && !monospace && !html_tag && square_brackets==0) { - if(*iter==' ' && iter.get_line_offset()<=80) - last_space_offset=iter.get_offset(); - // Insert newline on long lines - else if((*iter==' ' || iter.ends_line()) && iter.get_line_offset()>80 && last_space_offset!=static_cast<size_t>(-1)) { - auto stored_iter=iter; - iter=get_buffer()->get_iter_at_offset(last_space_offset); - auto next_iter=iter; - next_iter.forward_char(); - // Do not add newline if the next iter is a special character - if(special_character(next_iter)) { - iter=stored_iter; - if(*iter==' ') - last_space_offset=iter.get_offset(); - continue; - } - iter=get_buffer()->erase(iter, next_iter); - iter=get_buffer()->insert(iter, "\n"); - iter.backward_char(); - } - // Remove newline on short lines - else if(iter.ends_line() && !iter.starts_line() && iter.get_line_offset()<=80) { - auto next_line_iter=iter; - // Do not remove newline if the next line for instance is a header - if(next_line_iter.forward_char() && !next_line_iter.ends_line() && !special_character(next_line_iter)) { - auto end_word_iter=next_line_iter; - // Do not remove newline if the word on the next line is too long - size_t diff=0; - while(*end_word_iter!=' ' && !end_word_iter.ends_line() && end_word_iter.forward_char()) - ++diff; - if(iter.get_line_offset()+diff+1<=80) { - iter=get_buffer()->erase(iter, next_line_iter); - iter=get_buffer()->insert(iter, " "); - iter.backward_char(); - if(iter.get_line_offset()<=80) - last_space_offset=iter.get_offset(); - } + if (is_generic_view) { + status_diagnostics = std::make_tuple(num_warnings, num_errors, num_fix_its); + if (update_status_diagnostics) + update_status_diagnostics(this); } - } - } - } while(iter.forward_char()); - disable_spellcheck=false; - get_buffer()->end_user_action(); - }; - } + hide_tooltips(); + }; + } else if (is_bracket_language) { + format_style = [this](bool continue_without_style_file) { + static auto clang_format_command = filesystem::get_executable("clang-format").string(); + + auto command = clang_format_command + " -output-replacements-xml -assume-filename=" + + filesystem::escape_argument(this->file_path.string()); + + if (get_buffer()->get_has_selection()) { + Gtk::TextIter start, end; + get_buffer()->get_selection_bounds(start, end); + command += " -lines=" + std::to_string(start.get_line() + 1) + ':' + std::to_string(end.get_line() + 1); + } + + bool use_style_file = false; + auto style_file_search_path = this->file_path.parent_path(); + while (true) { + if (boost::filesystem::exists(style_file_search_path / ".clang-format") || + boost::filesystem::exists(style_file_search_path / "_clang-format")) { + use_style_file = true; + break; + } + if (style_file_search_path == style_file_search_path.root_directory()) + break; + style_file_search_path = style_file_search_path.parent_path(); + } + + if (use_style_file) + command += " -style=file"; + else { + if (!continue_without_style_file) + return; + unsigned indent_width; + std::string tab_style; + if (tab_char == '\t') { + indent_width = tab_size * 8; + tab_style = "UseTab: Always"; + } else { + indent_width = tab_size; + tab_style = "UseTab: Never"; + } + command += " -style=\"{IndentWidth: " + std::to_string(indent_width); + command += ", " + tab_style; + command += ", " + std::string("AccessModifierOffset: -") + std::to_string(indent_width); + if (Config::get().source.clang_format_style != "") + command += ", " + Config::get().source.clang_format_style; + command += "}\""; + } + + std::stringstream stdin_stream(get_buffer()->get_text()), stdout_stream; + + auto exit_status = Terminal::get().process(stdin_stream, stdout_stream, command, + this->file_path.parent_path()); + if (exit_status == 0) { + // The following code is complex due to clang-format returning offsets in byte offsets instead of char offsets + + // Create bytes_in_lines cache to significantly speed up the processing of finding iterators from byte offsets + std::vector<size_t> bytes_in_lines; + auto line_count = get_buffer()->get_line_count(); + for (int line_nr = 0; line_nr < line_count; ++line_nr) { + auto iter = get_buffer()->get_iter_at_line(line_nr); + bytes_in_lines.emplace_back(iter.get_bytes_in_line()); + } + + get_buffer()->begin_user_action(); + try { + boost::property_tree::ptree pt; + boost::property_tree::xml_parser::read_xml(stdout_stream, pt); + auto replacements_pt = pt.get_child("replacements"); + for (auto it = replacements_pt.rbegin(); it != replacements_pt.rend(); ++it) { + if (it->first == "replacement") { + auto offset = it->second.get<size_t>("<xmlattr>.offset"); + auto length = it->second.get<size_t>("<xmlattr>.length"); + auto replacement_str = it->second.get<std::string>(""); + + size_t bytes = 0; + for (size_t c = 0; c < bytes_in_lines.size(); ++c) { + auto previous_bytes = bytes; + bytes += bytes_in_lines[c]; + if (offset < bytes || (c == bytes_in_lines.size() - 1 && offset == bytes)) { + std::pair<size_t, size_t> line_index(c, offset - previous_bytes); + auto start = get_buffer()->get_iter_at_line_index(line_index.first, + line_index.second); + + // Use left gravity insert to avoid moving cursor from end of line + bool left_gravity_insert = false; + if (get_buffer()->get_insert()->get_iter() == start) { + auto iter = start; + do { + if (*iter != ' ' && *iter != '\t') { + left_gravity_insert = iter.ends_line(); + break; + } + } while (iter.forward_char()); + } + + if (length > 0) { + auto offset_end = offset + length; + size_t bytes = 0; + for (size_t c = 0; c < bytes_in_lines.size(); ++c) { + auto previous_bytes = bytes; + bytes += bytes_in_lines[c]; + if (offset_end < bytes || + (c == bytes_in_lines.size() - 1 && offset_end == bytes)) { + auto end = get_buffer()->get_iter_at_line_index(c, offset_end - + previous_bytes); + get_buffer()->erase(start, end); + start = get_buffer()->get_iter_at_line_index(line_index.first, + line_index.second); + break; + } + } + } + if (left_gravity_insert) { + auto mark = get_buffer()->create_mark(start); + get_buffer()->insert(start, replacement_str); + get_buffer()->place_cursor(mark->get_iter()); + get_buffer()->delete_mark(mark); + } else + get_buffer()->insert(start, replacement_str); + break; + } + } + } + } + } + catch (const std::exception &e) { + Terminal::get().print(std::string("Error: error parsing clang-format output: ") + e.what() + '\n', + true); + } + get_buffer()->end_user_action(); + } + }; + } else if (language && language->get_id() == "markdown") { + // The style file currently has no options, but checking if it exists + format_style = [this](bool continue_without_style_file) { + bool has_style_file = false; + auto style_file_search_path = this->file_path.parent_path(); + while (true) { + if (boost::filesystem::exists(style_file_search_path / ".markdown-format")) { + has_style_file = true; + break; + } + if (style_file_search_path == style_file_search_path.root_directory()) + break; + style_file_search_path = style_file_search_path.parent_path(); + } + if (!has_style_file && !continue_without_style_file) + return; + + auto special_character = [](Gtk::TextIter iter) { + if (*iter == '*' || *iter == '#' || *iter == '<' || *iter == '>' || *iter == ' ' || *iter == '=' || + *iter == '`' || *iter == '-') + return true; + // Tests if a line starts with for instance: 2. + if (*iter >= '0' && *iter <= '9' && iter.forward_char() && + *iter == '.' && iter.forward_char() && + *iter == ' ') + return true; + return false; + }; + + get_buffer()->begin_user_action(); + disable_spellcheck = true; + cleanup_whitespace_characters(); + + auto iter = get_buffer()->begin(); + size_t last_space_offset = -1; + bool headline = false; + bool monospace = false; + bool script = false; + bool html_tag = false; + int square_brackets = 0; + do { + if (iter.starts_line()) { + last_space_offset = -1; + auto next_line_iter = iter; + if (*iter == '#' || (next_line_iter.forward_line() && *next_line_iter == '=')) + headline = true; + else + headline = false; + auto test_iter = iter; + if (*test_iter == '`' && test_iter.forward_char() && + *test_iter == '`' && test_iter.forward_char() && + *test_iter == '`') { + script = !script; + iter.forward_chars(3); + continue; + } + } + if (!script && *iter == '`') + monospace = !monospace; + if (!script && !monospace) { + if (*iter == '<') + html_tag = true; + else if (*iter == '>') + html_tag = false; + else if (*iter == '[') + ++square_brackets; + else if (*iter == ']') + --square_brackets; + } + if (!headline && !script && !monospace && !html_tag && square_brackets == 0) { + if (*iter == ' ' && iter.get_line_offset() <= 80) + last_space_offset = iter.get_offset(); + // Insert newline on long lines + else if ((*iter == ' ' || iter.ends_line()) && iter.get_line_offset() > 80 && + last_space_offset != static_cast<size_t>(-1)) { + auto stored_iter = iter; + iter = get_buffer()->get_iter_at_offset(last_space_offset); + auto next_iter = iter; + next_iter.forward_char(); + // Do not add newline if the next iter is a special character + if (special_character(next_iter)) { + iter = stored_iter; + if (*iter == ' ') + last_space_offset = iter.get_offset(); + continue; + } + iter = get_buffer()->erase(iter, next_iter); + iter = get_buffer()->insert(iter, "\n"); + iter.backward_char(); + } + // Remove newline on short lines + else if (iter.ends_line() && !iter.starts_line() && iter.get_line_offset() <= 80) { + auto next_line_iter = iter; + // Do not remove newline if the next line for instance is a header + if (next_line_iter.forward_char() && !next_line_iter.ends_line() && + !special_character(next_line_iter)) { + auto end_word_iter = next_line_iter; + // Do not remove newline if the word on the next line is too long + size_t diff = 0; + while (*end_word_iter != ' ' && !end_word_iter.ends_line() && end_word_iter.forward_char()) + ++diff; + if (iter.get_line_offset() + diff + 1 <= 80) { + iter = get_buffer()->erase(iter, next_line_iter); + iter = get_buffer()->insert(iter, " "); + iter.backward_char(); + if (iter.get_line_offset() <= 80) + last_space_offset = iter.get_offset(); + } + } + } + } + } while (iter.forward_char()); + disable_spellcheck = false; + get_buffer()->end_user_action(); + }; + } } -void Source::View::search_occurrences_updated(GtkWidget* widget, GParamSpec* property, gpointer data) { - auto view=static_cast<Source::View*>(data); - if(view->update_search_occurrences) - view->update_search_occurrences(gtk_source_search_context_get_occurrences_count(view->search_context)); +void Source::View::search_occurrences_updated(GtkWidget *widget, GParamSpec *property, gpointer data) { + auto view = static_cast<Source::View *>(data); + if (view->update_search_occurrences) + view->update_search_occurrences(gtk_source_search_context_get_occurrences_count(view->search_context)); } Source::View::~View() { - g_clear_object(&search_context); - g_clear_object(&search_settings); - - delayed_tooltips_connection.disconnect(); - delayed_tag_similar_symbols_connection.disconnect(); - renderer_activate_connection.disconnect(); - - non_deleted_views.erase(this); - views.erase(this); + g_clear_object(&search_context); + g_clear_object(&search_settings); + + delayed_tooltips_connection.disconnect(); + renderer_activate_connection.disconnect(); + + non_deleted_views.erase(this); + views.erase(this); } void Source::View::search_highlight(const std::string &text, bool case_sensitive, bool regex) { - gtk_source_search_settings_set_case_sensitive(search_settings, case_sensitive); - gtk_source_search_settings_set_regex_enabled(search_settings, regex); - gtk_source_search_settings_set_search_text(search_settings, text.c_str()); - search_occurrences_updated(nullptr, nullptr, this); + gtk_source_search_settings_set_case_sensitive(search_settings, case_sensitive); + gtk_source_search_settings_set_regex_enabled(search_settings, regex); + gtk_source_search_settings_set_search_text(search_settings, text.c_str()); + search_occurrences_updated(nullptr, nullptr, this); } void Source::View::search_forward() { - Gtk::TextIter insert, selection_bound; - get_buffer()->get_selection_bounds(insert, selection_bound); - auto& start=selection_bound; - Gtk::TextIter match_start, match_end; + Gtk::TextIter insert, selection_bound; + get_buffer()->get_selection_bounds(insert, selection_bound); + auto &start = selection_bound; + Gtk::TextIter match_start, match_end; #if defined(GTK_SOURCE_MAJOR_VERSION) && (GTK_SOURCE_MAJOR_VERSION > 3 || (GTK_SOURCE_MAJOR_VERSION == 3 && GTK_SOURCE_MINOR_VERSION >= 22)) - gboolean has_wrapped_around; - if(gtk_source_search_context_forward2(search_context, start.gobj(), match_start.gobj(), match_end.gobj(), &has_wrapped_around)) { + gboolean has_wrapped_around; + if (gtk_source_search_context_forward2(search_context, start.gobj(), match_start.gobj(), match_end.gobj(), + &has_wrapped_around)) { #else - if(gtk_source_search_context_forward(search_context, start.gobj(), match_start.gobj(), match_end.gobj())) { + if(gtk_source_search_context_forward(search_context, start.gobj(), match_start.gobj(), match_end.gobj())) { #endif - get_buffer()->select_range(match_start, match_end); - scroll_to(get_buffer()->get_insert()); - } + get_buffer()->select_range(match_start, match_end); + scroll_to(get_buffer()->get_insert()); + } } void Source::View::search_backward() { - Gtk::TextIter insert, selection_bound; - get_buffer()->get_selection_bounds(insert, selection_bound); - auto &start=insert; - Gtk::TextIter match_start, match_end; + Gtk::TextIter insert, selection_bound; + get_buffer()->get_selection_bounds(insert, selection_bound); + auto &start = insert; + Gtk::TextIter match_start, match_end; #if defined(GTK_SOURCE_MAJOR_VERSION) && (GTK_SOURCE_MAJOR_VERSION > 3 || (GTK_SOURCE_MAJOR_VERSION == 3 && GTK_SOURCE_MINOR_VERSION >= 22)) - gboolean has_wrapped_around; - if(gtk_source_search_context_backward2(search_context, start.gobj(), match_start.gobj(), match_end.gobj(), &has_wrapped_around)) { + gboolean has_wrapped_around; + if (gtk_source_search_context_backward2(search_context, start.gobj(), match_start.gobj(), match_end.gobj(), + &has_wrapped_around)) { #else - if(gtk_source_search_context_backward(search_context, start.gobj(), match_start.gobj(), match_end.gobj())) { + if(gtk_source_search_context_backward(search_context, start.gobj(), match_start.gobj(), match_end.gobj())) { #endif - get_buffer()->select_range(match_start, match_end); - scroll_to(get_buffer()->get_insert()); - } + get_buffer()->select_range(match_start, match_end); + scroll_to(get_buffer()->get_insert()); + } } void Source::View::replace_forward(const std::string &replacement) { - Gtk::TextIter insert, selection_bound; - get_buffer()->get_selection_bounds(insert, selection_bound); - auto &start=insert; - Gtk::TextIter match_start, match_end; + Gtk::TextIter insert, selection_bound; + get_buffer()->get_selection_bounds(insert, selection_bound); + auto &start = insert; + Gtk::TextIter match_start, match_end; #if defined(GTK_SOURCE_MAJOR_VERSION) && (GTK_SOURCE_MAJOR_VERSION > 3 || (GTK_SOURCE_MAJOR_VERSION == 3 && GTK_SOURCE_MINOR_VERSION >= 22)) - gboolean has_wrapped_around; - if(gtk_source_search_context_forward2(search_context, start.gobj(), match_start.gobj(), match_end.gobj(), &has_wrapped_around)) { + gboolean has_wrapped_around; + if (gtk_source_search_context_forward2(search_context, start.gobj(), match_start.gobj(), match_end.gobj(), + &has_wrapped_around)) { #else - if(gtk_source_search_context_forward(search_context, start.gobj(), match_start.gobj(), match_end.gobj())) { + if(gtk_source_search_context_forward(search_context, start.gobj(), match_start.gobj(), match_end.gobj())) { #endif - auto offset=match_start.get_offset(); + auto offset = match_start.get_offset(); #if defined(GTK_SOURCE_MAJOR_VERSION) && (GTK_SOURCE_MAJOR_VERSION > 3 || (GTK_SOURCE_MAJOR_VERSION == 3 && GTK_SOURCE_MINOR_VERSION >= 22)) - gtk_source_search_context_replace2(search_context, match_start.gobj(), match_end.gobj(), replacement.c_str(), replacement.size(), nullptr); + gtk_source_search_context_replace2(search_context, match_start.gobj(), match_end.gobj(), replacement.c_str(), + replacement.size(), nullptr); #else - gtk_source_search_context_replace(search_context, match_start.gobj(), match_end.gobj(), replacement.c_str(), replacement.size(), nullptr); + gtk_source_search_context_replace(search_context, match_start.gobj(), match_end.gobj(), replacement.c_str(), replacement.size(), nullptr); #endif - - Glib::ustring replacement_ustring=replacement; - get_buffer()->select_range(get_buffer()->get_iter_at_offset(offset), get_buffer()->get_iter_at_offset(offset+replacement_ustring.size())); - scroll_to(get_buffer()->get_insert()); - } + + Glib::ustring replacement_ustring = replacement; + get_buffer()->select_range(get_buffer()->get_iter_at_offset(offset), + get_buffer()->get_iter_at_offset(offset + replacement_ustring.size())); + scroll_to(get_buffer()->get_insert()); + } } void Source::View::replace_backward(const std::string &replacement) { - Gtk::TextIter insert, selection_bound; - get_buffer()->get_selection_bounds(insert, selection_bound); - auto &start=selection_bound; - Gtk::TextIter match_start, match_end; + Gtk::TextIter insert, selection_bound; + get_buffer()->get_selection_bounds(insert, selection_bound); + auto &start = selection_bound; + Gtk::TextIter match_start, match_end; #if defined(GTK_SOURCE_MAJOR_VERSION) && (GTK_SOURCE_MAJOR_VERSION > 3 || (GTK_SOURCE_MAJOR_VERSION == 3 && GTK_SOURCE_MINOR_VERSION >= 22)) - gboolean has_wrapped_around; - if(gtk_source_search_context_backward2(search_context, start.gobj(), match_start.gobj(), match_end.gobj(), &has_wrapped_around)) { + gboolean has_wrapped_around; + if (gtk_source_search_context_backward2(search_context, start.gobj(), match_start.gobj(), match_end.gobj(), + &has_wrapped_around)) { #else - if(gtk_source_search_context_backward(search_context, start.gobj(), match_start.gobj(), match_end.gobj())) { + if(gtk_source_search_context_backward(search_context, start.gobj(), match_start.gobj(), match_end.gobj())) { #endif - auto offset=match_start.get_offset(); + auto offset = match_start.get_offset(); #if defined(GTK_SOURCE_MAJOR_VERSION) && (GTK_SOURCE_MAJOR_VERSION > 3 || (GTK_SOURCE_MAJOR_VERSION == 3 && GTK_SOURCE_MINOR_VERSION >= 22)) - gtk_source_search_context_replace2(search_context, match_start.gobj(), match_end.gobj(), replacement.c_str(), replacement.size(), nullptr); + gtk_source_search_context_replace2(search_context, match_start.gobj(), match_end.gobj(), replacement.c_str(), + replacement.size(), nullptr); #else - gtk_source_search_context_replace(search_context, match_start.gobj(), match_end.gobj(), replacement.c_str(), replacement.size(), nullptr); + gtk_source_search_context_replace(search_context, match_start.gobj(), match_end.gobj(), replacement.c_str(), replacement.size(), nullptr); #endif - get_buffer()->select_range(get_buffer()->get_iter_at_offset(offset), get_buffer()->get_iter_at_offset(offset+replacement.size())); - scroll_to(get_buffer()->get_insert()); - } + get_buffer()->select_range(get_buffer()->get_iter_at_offset(offset), + get_buffer()->get_iter_at_offset(offset + replacement.size())); + scroll_to(get_buffer()->get_insert()); + } } void Source::View::replace_all(const std::string &replacement) { - gtk_source_search_context_replace_all(search_context, replacement.c_str(), replacement.size(), nullptr); + gtk_source_search_context_replace_all(search_context, replacement.c_str(), replacement.size(), nullptr); } void Source::View::paste() { - class Guard { - public: - bool &value; - Guard(bool &value_) : value(value_) { value = true; } - ~Guard() { value = false; } - }; - Guard guard{multiple_cursors_use}; - - std::string text=Gtk::Clipboard::get()->wait_for_text(); - - //Replace carriage returns (which leads to crash) with newlines - for(size_t c=0;c<text.size();c++) { - if(text[c]=='\r') { - if((c+1)<text.size() && text[c+1]=='\n') - text.replace(c, 2, "\n"); - else - text.replace(c, 1, "\n"); - } - } - - //Exception for when pasted text is only whitespaces - bool only_whitespaces=true; - for(auto &chr: text) { - if(chr!='\n' && chr!='\r' && chr!=' ' && chr!='\t') { - only_whitespaces=false; - break; + class Guard { + public: + bool &value; + + Guard(bool &value_) : value(value_) { value = true; } + + ~Guard() { value = false; } + }; + Guard guard{multiple_cursors_use}; + + std::string text = Gtk::Clipboard::get()->wait_for_text(); + + //Replace carriage returns (which leads to crash) with newlines + for (size_t c = 0; c < text.size(); c++) { + if (text[c] == '\r') { + if ((c + 1) < text.size() && text[c + 1] == '\n') + text.replace(c, 2, "\n"); + else + text.replace(c, 1, "\n"); + } } - } - if(only_whitespaces) { - Gtk::Clipboard::get()->set_text(text); - get_buffer()->paste_clipboard(Gtk::Clipboard::get()); - scroll_to_cursor_delayed(this, false, false); - return; - } - - get_buffer()->begin_user_action(); - if(get_buffer()->get_has_selection()) { - Gtk::TextIter start, end; - get_buffer()->get_selection_bounds(start, end); - get_buffer()->erase(start, end); - } - auto iter=get_buffer()->get_insert()->get_iter(); - auto tabs_end_iter=get_tabs_end_iter(); - auto prefix_tabs=get_line_before(iter<tabs_end_iter?iter:tabs_end_iter); - - size_t start_line=0; - size_t end_line=0; - bool paste_line=false; - bool first_paste_line=true; - size_t paste_line_tabs=-1; - bool first_paste_line_has_tabs=false; - for(size_t c=0;c<text.size();c++) { - if(text[c]=='\n') { - end_line=c; - paste_line=true; - } - else if(c==text.size()-1) { - end_line=c+1; - paste_line=true; - } - if(paste_line) { - bool empty_line=true; - std::string line=text.substr(start_line, end_line-start_line); - size_t tabs=0; - for(auto chr: line) { - if(chr==tab_char) - tabs++; - else { - empty_line=false; - break; + + //Exception for when pasted text is only whitespaces + bool only_whitespaces = true; + for (auto &chr: text) { + if (chr != '\n' && chr != '\r' && chr != ' ' && chr != '\t') { + only_whitespaces = false; + break; } - } - if(first_paste_line) { - if(tabs!=0) { - first_paste_line_has_tabs=true; - paste_line_tabs=tabs; + } + if (only_whitespaces) { + Gtk::Clipboard::get()->set_text(text); + get_buffer()->paste_clipboard(Gtk::Clipboard::get()); + scroll_to_cursor_delayed(this, false, false); + return; + } + + get_buffer()->begin_user_action(); + if (get_buffer()->get_has_selection()) { + Gtk::TextIter start, end; + get_buffer()->get_selection_bounds(start, end); + get_buffer()->erase(start, end); + } + auto iter = get_buffer()->get_insert()->get_iter(); + auto tabs_end_iter = get_tabs_end_iter(); + auto prefix_tabs = get_line_before(iter < tabs_end_iter ? iter : tabs_end_iter); + + size_t start_line = 0; + size_t end_line = 0; + bool paste_line = false; + bool first_paste_line = true; + size_t paste_line_tabs = -1; + bool first_paste_line_has_tabs = false; + for (size_t c = 0; c < text.size(); c++) { + if (text[c] == '\n') { + end_line = c; + paste_line = true; + } else if (c == text.size() - 1) { + end_line = c + 1; + paste_line = true; } - first_paste_line=false; - } - else if(!empty_line) - paste_line_tabs=std::min(paste_line_tabs, tabs); + if (paste_line) { + bool empty_line = true; + std::string line = text.substr(start_line, end_line - start_line); + size_t tabs = 0; + for (auto chr: line) { + if (chr == tab_char) + tabs++; + else { + empty_line = false; + break; + } + } + if (first_paste_line) { + if (tabs != 0) { + first_paste_line_has_tabs = true; + paste_line_tabs = tabs; + } + first_paste_line = false; + } else if (!empty_line) + paste_line_tabs = std::min(paste_line_tabs, tabs); - start_line=end_line+1; - paste_line=false; + start_line = end_line + 1; + paste_line = false; + } } - } - if(paste_line_tabs==static_cast<size_t>(-1)) - paste_line_tabs=0; - start_line=0; - end_line=0; - paste_line=false; - first_paste_line=true; - for(size_t c=0;c<text.size();c++) { - if(text[c]=='\n') { - end_line=c; - paste_line=true; - } - else if(c==text.size()-1) { - end_line=c+1; - paste_line=true; - } - if(paste_line) { - std::string line=text.substr(start_line, end_line-start_line); - size_t line_tabs=0; - for(auto chr: line) { - if(chr==tab_char) - line_tabs++; - else - break; - } - auto tabs=paste_line_tabs; - if(!(first_paste_line && !first_paste_line_has_tabs) && line_tabs<paste_line_tabs) { - tabs=line_tabs; - } - - if(first_paste_line) { - if(first_paste_line_has_tabs) - get_buffer()->insert_at_cursor(text.substr(start_line+tabs, end_line-start_line-tabs)); - else - get_buffer()->insert_at_cursor(text.substr(start_line, end_line-start_line)); - first_paste_line=false; - } - else - get_buffer()->insert_at_cursor('\n'+prefix_tabs+text.substr(start_line+tabs, end_line-start_line-tabs)); - start_line=end_line+1; - paste_line=false; + if (paste_line_tabs == static_cast<size_t>(-1)) + paste_line_tabs = 0; + start_line = 0; + end_line = 0; + paste_line = false; + first_paste_line = true; + for (size_t c = 0; c < text.size(); c++) { + if (text[c] == '\n') { + end_line = c; + paste_line = true; + } else if (c == text.size() - 1) { + end_line = c + 1; + paste_line = true; + } + if (paste_line) { + std::string line = text.substr(start_line, end_line - start_line); + size_t line_tabs = 0; + for (auto chr: line) { + if (chr == tab_char) + line_tabs++; + else + break; + } + auto tabs = paste_line_tabs; + if (!(first_paste_line && !first_paste_line_has_tabs) && line_tabs < paste_line_tabs) { + tabs = line_tabs; + } + + if (first_paste_line) { + if (first_paste_line_has_tabs) + get_buffer()->insert_at_cursor(text.substr(start_line + tabs, end_line - start_line - tabs)); + else + get_buffer()->insert_at_cursor(text.substr(start_line, end_line - start_line)); + first_paste_line = false; + } else + get_buffer()->insert_at_cursor( + '\n' + prefix_tabs + text.substr(start_line + tabs, end_line - start_line - tabs)); + start_line = end_line + 1; + paste_line = false; + } } - } - // add final newline if present in text - if(text.size()>0 && text.back()=='\n') - get_buffer()->insert_at_cursor('\n'+prefix_tabs); - get_buffer()->place_cursor(get_buffer()->get_insert()->get_iter()); - get_buffer()->end_user_action(); - scroll_to_cursor_delayed(this, false, false); + // add final newline if present in text + if (text.size() > 0 && text.back() == '\n') + get_buffer()->insert_at_cursor('\n' + prefix_tabs); + get_buffer()->place_cursor(get_buffer()->get_insert()->get_iter()); + get_buffer()->end_user_action(); + scroll_to_cursor_delayed(this, false, false); } void Source::View::hide_tooltips() { - delayed_tooltips_connection.disconnect(); - delayed_tag_similar_symbols_connection.disconnect(); - type_tooltips.hide(); - diagnostic_tooltips.hide(); + delayed_tooltips_connection.disconnect(); + type_tooltips.hide(); + diagnostic_tooltips.hide(); } -void Source::View::add_diagnostic_tooltip(const Gtk::TextIter &start, const Gtk::TextIter &end, std::string spelling, bool error) { - diagnostic_offsets.emplace(start.get_offset()); - - std::string severity_tag_name = error ? "def:error" : "def:warning"; - - auto create_tooltip_buffer=[this, spelling=std::move(spelling), error, severity_tag_name]() { - auto tooltip_buffer=Gtk::TextBuffer::create(get_buffer()->get_tag_table()); - tooltip_buffer->insert_with_tag(tooltip_buffer->get_insert()->get_iter(), error ? "Error" : "Warning", severity_tag_name); - tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), ":\n"+spelling); - return tooltip_buffer; - }; - diagnostic_tooltips.emplace_back(create_tooltip_buffer, this, get_buffer()->create_mark(start), get_buffer()->create_mark(end)); - - get_buffer()->apply_tag_by_name(severity_tag_name+"_underline", start, end); - - auto iter=get_buffer()->get_insert()->get_iter(); - if(iter.ends_line()) { - auto next_iter=iter; - if(next_iter.forward_char()) - get_buffer()->remove_tag_by_name(severity_tag_name+"_underline", iter, next_iter); - } +void Source::View::add_diagnostic_tooltip(const Gtk::TextIter &start, const Gtk::TextIter &end, std::string spelling, + bool error) { + diagnostic_offsets.emplace(start.get_offset()); + + std::string severity_tag_name = error ? "def:error" : "def:warning"; + + auto create_tooltip_buffer = [this, spelling = std::move(spelling), error, severity_tag_name]() { + auto tooltip_buffer = Gtk::TextBuffer::create(get_buffer()->get_tag_table()); + tooltip_buffer->insert_with_tag(tooltip_buffer->get_insert()->get_iter(), error ? "Error" : "Warning", + severity_tag_name); + tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), ":\n" + spelling); + return tooltip_buffer; + }; + diagnostic_tooltips.emplace_back(create_tooltip_buffer, this, get_buffer()->create_mark(start), + get_buffer()->create_mark(end)); + + get_buffer()->apply_tag_by_name(severity_tag_name + "_underline", start, end); + + auto iter = get_buffer()->get_insert()->get_iter(); + if (iter.ends_line()) { + auto next_iter = iter; + if (next_iter.forward_char()) + get_buffer()->remove_tag_by_name(severity_tag_name + "_underline", iter, next_iter); + } } void Source::View::clear_diagnostic_tooltips() { - diagnostic_offsets.clear(); - diagnostic_tooltips.clear(); - get_buffer()->remove_tag_by_name("def:warning_underline", get_buffer()->begin(), get_buffer()->end()); - get_buffer()->remove_tag_by_name("def:error_underline", get_buffer()->begin(), get_buffer()->end()); + diagnostic_offsets.clear(); + diagnostic_tooltips.clear(); + get_buffer()->remove_tag_by_name("def:warning_underline", get_buffer()->begin(), get_buffer()->end()); + get_buffer()->remove_tag_by_name("def:error_underline", get_buffer()->begin(), get_buffer()->end()); } void Source::View::hide_dialogs() { - SpellCheckView::hide_dialogs(); - if(SelectionDialog::get()) - SelectionDialog::get()->hide(); - if(CompletionDialog::get()) - CompletionDialog::get()->hide(); + SpellCheckView::hide_dialogs(); + if (SelectionDialog::get()) + SelectionDialog::get()->hide(); + if (CompletionDialog::get()) + CompletionDialog::get()->hide(); } bool Source::View::find_open_non_curly_bracket_backward(Gtk::TextIter iter, Gtk::TextIter &found_iter) { - long para_count=0; - long square_count=0; - long curly_count=0; - - do { - if(*iter=='(' && is_code_iter(iter)) - para_count++; - else if(*iter==')' && is_code_iter(iter)) - para_count--; - else if(*iter=='[' && is_code_iter(iter)) - square_count++; - else if(*iter==']' && is_code_iter(iter)) - square_count--; - else if(*iter=='{' && is_code_iter(iter)) - curly_count++; - else if(*iter=='}' && is_code_iter(iter)) - curly_count--; - - if(curly_count>0) - break; - - if(para_count>0 || square_count>0) { - found_iter=iter; - return true; - } - } while(iter.backward_char()); - return false; -} + long para_count = 0; + long square_count = 0; + long curly_count = 0; -Gtk::TextIter Source::View::find_start_of_sentence(Gtk::TextIter iter) { - if(iter.starts_line()) - return iter; - - bool stream_operator_test=false; - bool colon_test=false; - - if(*iter==';') - stream_operator_test=true; - if(*iter=='{') { - iter.backward_char(); - colon_test=true; - } - - int para_count=0; - int square_count=0; - long curly_count=0; - - do { - if(*iter=='(' && is_code_iter(iter)) - para_count++; - else if(*iter==')' && is_code_iter(iter)) - para_count--; - else if(*iter=='[' && is_code_iter(iter)) - square_count++; - else if(*iter==']' && is_code_iter(iter)) - square_count--; - else if(*iter=='{' && is_code_iter(iter)) - curly_count++; - else if(*iter=='}' && is_code_iter(iter)) - curly_count--; - - if(curly_count>0) - break; - - if(iter.starts_line() && para_count==0 && square_count==0) { - bool stream_operator_found=false; - bool colon_found=false; - // Handle << at the beginning of the sentence if iter initially started with ; - if(stream_operator_test) { - auto test_iter=get_tabs_end_iter(iter); - if(!test_iter.starts_line() && *test_iter=='<' && is_code_iter(test_iter) && - test_iter.forward_char() && *test_iter=='<') - stream_operator_found=true; - } - // Handle : at the beginning of the sentence if iter initially started with { - else if(colon_test) { - auto test_iter=get_tabs_end_iter(iter); - if(!test_iter.starts_line() && *test_iter==':' && is_code_iter(test_iter)) - colon_found=true; - } - // Handle : and , on previous line - if(!stream_operator_found && !colon_found) { - auto previous_iter=iter; - previous_iter.backward_char(); - while(!previous_iter.starts_line() && (*previous_iter==' ' || previous_iter.ends_line()) && previous_iter.backward_char()) {} - if(*previous_iter!=',' && *previous_iter!=':') - return iter; - else if(*previous_iter==':') { - previous_iter.backward_char(); - while(!previous_iter.starts_line() && *previous_iter==' ' && previous_iter.backward_char()) {} - if(*previous_iter==')') { - auto token=get_token(get_tabs_end_iter(get_buffer()->get_iter_at_line(previous_iter.get_line()))); - if(token=="case") - return iter; - } - else - return iter; - } - } - } - } while(iter.backward_char()); - - return iter; -} + do { + if (*iter == '(' && is_code_iter(iter)) + para_count++; + else if (*iter == ')' && is_code_iter(iter)) + para_count--; + else if (*iter == '[' && is_code_iter(iter)) + square_count++; + else if (*iter == ']' && is_code_iter(iter)) + square_count--; + else if (*iter == '{' && is_code_iter(iter)) + curly_count++; + else if (*iter == '}' && is_code_iter(iter)) + curly_count--; + + if (curly_count > 0) + break; -bool Source::View::find_open_curly_bracket_backward(Gtk::TextIter iter, Gtk::TextIter &found_iter) { - long count=0; - - do { - if(*iter=='{') { - if(count==0 && is_code_iter(iter)) { - found_iter=iter; - return true; - } - count++; - } - else if(*iter=='}' && is_code_iter(iter)) - count--; - } while(iter.backward_char()); - return false; + if (para_count > 0 || square_count > 0) { + found_iter = iter; + return true; + } + } while (iter.backward_char()); + return false; } -bool Source::View::find_close_curly_bracket_forward(Gtk::TextIter iter, Gtk::TextIter &found_iter) { - long count=0; - - do { - if(*iter=='}' && is_code_iter(iter)) { - if(count==0) { - found_iter=iter; - return true; - } - count--; +Gtk::TextIter Source::View::find_start_of_sentence(Gtk::TextIter iter) { + if (iter.starts_line()) + return iter; + + bool stream_operator_test = false; + bool colon_test = false; + + if (*iter == ';') + stream_operator_test = true; + if (*iter == '{') { + iter.backward_char(); + colon_test = true; } - else if(*iter=='{' && is_code_iter(iter)) - count++; - } while(iter.forward_char()); - return false; + + int para_count = 0; + int square_count = 0; + long curly_count = 0; + + do { + if (*iter == '(' && is_code_iter(iter)) + para_count++; + else if (*iter == ')' && is_code_iter(iter)) + para_count--; + else if (*iter == '[' && is_code_iter(iter)) + square_count++; + else if (*iter == ']' && is_code_iter(iter)) + square_count--; + else if (*iter == '{' && is_code_iter(iter)) + curly_count++; + else if (*iter == '}' && is_code_iter(iter)) + curly_count--; + + if (curly_count > 0) + break; + + if (iter.starts_line() && para_count == 0 && square_count == 0) { + bool stream_operator_found = false; + bool colon_found = false; + // Handle << at the beginning of the sentence if iter initially started with ; + if (stream_operator_test) { + auto test_iter = get_tabs_end_iter(iter); + if (!test_iter.starts_line() && *test_iter == '<' && is_code_iter(test_iter) && + test_iter.forward_char() && *test_iter == '<') + stream_operator_found = true; + } + // Handle : at the beginning of the sentence if iter initially started with { + else if (colon_test) { + auto test_iter = get_tabs_end_iter(iter); + if (!test_iter.starts_line() && *test_iter == ':' && is_code_iter(test_iter)) + colon_found = true; + } + // Handle : and , on previous line + if (!stream_operator_found && !colon_found) { + auto previous_iter = iter; + previous_iter.backward_char(); + while (!previous_iter.starts_line() && (*previous_iter == ' ' || previous_iter.ends_line()) && + previous_iter.backward_char()) {} + if (*previous_iter != ',' && *previous_iter != ':') + return iter; + else if (*previous_iter == ':') { + previous_iter.backward_char(); + while (!previous_iter.starts_line() && *previous_iter == ' ' && previous_iter.backward_char()) {} + if (*previous_iter == ')') { + auto token = get_token( + get_tabs_end_iter(get_buffer()->get_iter_at_line(previous_iter.get_line()))); + if (token == "case") + return iter; + } else + return iter; + } + } + } + } while (iter.backward_char()); + + return iter; +} + +bool Source::View::find_open_curly_bracket_backward(Gtk::TextIter iter, Gtk::TextIter &found_iter) { + long count = 0; + + do { + if (*iter == '{') { + if (count == 0 && is_code_iter(iter)) { + found_iter = iter; + return true; + } + count++; + } else if (*iter == '}' && is_code_iter(iter)) + count--; + } while (iter.backward_char()); + return false; +} + +bool Source::View::find_close_curly_bracket_forward(Gtk::TextIter iter, Gtk::TextIter &found_iter) { + long count = 0; + + do { + if (*iter == '}' && is_code_iter(iter)) { + if (count == 0) { + found_iter = iter; + return true; + } + count--; + } else if (*iter == '{' && is_code_iter(iter)) + count++; + } while (iter.forward_char()); + return false; } long Source::View::symbol_count(Gtk::TextIter iter, unsigned int positive_char, unsigned int negative_char) { - auto iter_stored=iter; - long symbol_count=0; - long curly_count=0; - bool break_on_curly=true; - if(positive_char=='{' || negative_char=='}') - break_on_curly=false; - bool check_if_next_iter_is_code_iter=false; - if(positive_char=='\'' || negative_char=='\'' || positive_char=='"' || negative_char=='"') - check_if_next_iter_is_code_iter=true; - - Gtk::TextIter previous_iter; - do { - if(*iter==positive_char && is_code_iter(iter)) - symbol_count++; - else if(*iter==negative_char && is_code_iter(iter)) - symbol_count--; - else if(*iter=='{' && is_code_iter(iter)) - curly_count++; - else if(*iter=='}' && is_code_iter(iter)) - curly_count--; - else if(check_if_next_iter_is_code_iter) { - auto next_iter=iter; - next_iter.forward_char(); - if(*iter==positive_char && is_code_iter(next_iter)) - symbol_count++; - else if(*iter==negative_char && is_code_iter(next_iter)) - symbol_count--; - } - - if(break_on_curly && curly_count>0) - break; - } while(iter.backward_char()); - - iter=iter_stored; - if(!iter.forward_char()) { + auto iter_stored = iter; + long symbol_count = 0; + long curly_count = 0; + bool break_on_curly = true; + if (positive_char == '{' || negative_char == '}') + break_on_curly = false; + bool check_if_next_iter_is_code_iter = false; + if (positive_char == '\'' || negative_char == '\'' || positive_char == '"' || negative_char == '"') + check_if_next_iter_is_code_iter = true; + + Gtk::TextIter previous_iter; + do { + if (*iter == positive_char && is_code_iter(iter)) + symbol_count++; + else if (*iter == negative_char && is_code_iter(iter)) + symbol_count--; + else if (*iter == '{' && is_code_iter(iter)) + curly_count++; + else if (*iter == '}' && is_code_iter(iter)) + curly_count--; + else if (check_if_next_iter_is_code_iter) { + auto next_iter = iter; + next_iter.forward_char(); + if (*iter == positive_char && is_code_iter(next_iter)) + symbol_count++; + else if (*iter == negative_char && is_code_iter(next_iter)) + symbol_count--; + } + + if (break_on_curly && curly_count > 0) + break; + } while (iter.backward_char()); + + iter = iter_stored; + if (!iter.forward_char()) { + return symbol_count; + } + + curly_count = 0; + do { + if (*iter == positive_char && is_code_iter(iter)) + symbol_count++; + else if (*iter == negative_char && is_code_iter(iter)) + symbol_count--; + else if (*iter == '{' && is_code_iter(iter)) + curly_count++; + else if (*iter == '}' && is_code_iter(iter)) + curly_count--; + else if (check_if_next_iter_is_code_iter) { + auto next_iter = iter; + next_iter.forward_char(); + if (*iter == positive_char && is_code_iter(next_iter)) + symbol_count++; + else if (*iter == negative_char && is_code_iter(next_iter)) + symbol_count--; + } + + if (break_on_curly && curly_count < 0) + break; + } while (iter.forward_char()); + return symbol_count; - } - - curly_count=0; - do { - if(*iter==positive_char && is_code_iter(iter)) - symbol_count++; - else if(*iter==negative_char && is_code_iter(iter)) - symbol_count--; - else if(*iter=='{' && is_code_iter(iter)) - curly_count++; - else if(*iter=='}' && is_code_iter(iter)) - curly_count--; - else if(check_if_next_iter_is_code_iter) { - auto next_iter=iter; - next_iter.forward_char(); - if(*iter==positive_char && is_code_iter(next_iter)) - symbol_count++; - else if(*iter==negative_char && is_code_iter(next_iter)) - symbol_count--; - } - - if(break_on_curly && curly_count<0) - break; - } while(iter.forward_char()); - - return symbol_count; } bool Source::View::is_templated_function(Gtk::TextIter iter, Gtk::TextIter &parenthesis_end_iter) { - auto iter_stored=iter; - long bracket_count=0; - long curly_count=0; - - if(!(iter.backward_char() && *iter=='>' && *iter_stored=='(')) - return false; - - do { - if(*iter=='<' && is_code_iter(iter)) - bracket_count++; - else if(*iter=='>' && is_code_iter(iter)) - bracket_count--; - else if(*iter=='{' && is_code_iter(iter)) - curly_count++; - else if(*iter=='}' && is_code_iter(iter)) - curly_count--; - - if(bracket_count==0) - break; - - if(curly_count>0) - break; - } while(iter.backward_char()); - - if(bracket_count!=0) + auto iter_stored = iter; + long bracket_count = 0; + long curly_count = 0; + + if (!(iter.backward_char() && *iter == '>' && *iter_stored == '(')) + return false; + + do { + if (*iter == '<' && is_code_iter(iter)) + bracket_count++; + else if (*iter == '>' && is_code_iter(iter)) + bracket_count--; + else if (*iter == '{' && is_code_iter(iter)) + curly_count++; + else if (*iter == '}' && is_code_iter(iter)) + curly_count--; + + if (bracket_count == 0) + break; + + if (curly_count > 0) + break; + } while (iter.backward_char()); + + if (bracket_count != 0) + return false; + + iter = iter_stored; + bracket_count = 0; + curly_count = 0; + do { + if (*iter == '(' && is_code_iter(iter)) + bracket_count++; + else if (*iter == ')' && is_code_iter(iter)) + bracket_count--; + else if (*iter == '{' && is_code_iter(iter)) + curly_count++; + else if (*iter == '}' && is_code_iter(iter)) + curly_count--; + + if (bracket_count == 0) { + parenthesis_end_iter = iter; + return true; + } + + if (curly_count < 0) + return false; + } while (iter.forward_char()); + return false; - - iter=iter_stored; - bracket_count=0; - curly_count=0; - do { - if(*iter=='(' && is_code_iter(iter)) - bracket_count++; - else if(*iter==')' && is_code_iter(iter)) - bracket_count--; - else if(*iter=='{' && is_code_iter(iter)) - curly_count++; - else if(*iter=='}' && is_code_iter(iter)) - curly_count--; - - if(bracket_count==0) { - parenthesis_end_iter=iter; - return true; - } - - if(curly_count<0) - return false; - } while(iter.forward_char()); - - return false; } std::string Source::View::get_token(Gtk::TextIter iter) { - auto start=iter; - auto end=iter; - - while((*iter>='A' && *iter<='Z') || (*iter>='a' && *iter<='z') || (*iter>='0' && *iter<='9') || *iter=='_') { - start=iter; - if(!iter.backward_char()) - break; - } - while((*end>='A' && *end<='Z') || (*end>='a' && *end<='z') || (*end>='0' && *end<='9') || *end=='_') { - if(!end.forward_char()) - break; - } - - return get_buffer()->get_text(start, end); -} + auto start = iter; + auto end = iter; -void Source::View::cleanup_whitespace_characters_on_return(const Gtk::TextIter &iter) { - auto start_blank_iter=iter; - auto end_blank_iter=iter; - while((*end_blank_iter==' ' || *end_blank_iter=='\t') && - !end_blank_iter.ends_line() && end_blank_iter.forward_char()) {} - if(!start_blank_iter.starts_line()) { - start_blank_iter.backward_char(); - while((*start_blank_iter==' ' || *start_blank_iter=='\t') && - !start_blank_iter.starts_line() && start_blank_iter.backward_char()) {} - if(*start_blank_iter!=' ' && *start_blank_iter!='\t') - start_blank_iter.forward_char(); - } + while ((*iter >= 'A' && *iter <= 'Z') || (*iter >= 'a' && *iter <= 'z') || (*iter >= '0' && *iter <= '9') || + *iter == '_') { + start = iter; + if (!iter.backward_char()) + break; + } + while ((*end >= 'A' && *end <= 'Z') || (*end >= 'a' && *end <= 'z') || (*end >= '0' && *end <= '9') || + *end == '_') { + if (!end.forward_char()) + break; + } - if(start_blank_iter.starts_line()) - get_buffer()->erase(iter, end_blank_iter); - else - get_buffer()->erase(start_blank_iter, end_blank_iter); + return get_buffer()->get_text(start, end); } -bool Source::View::on_key_press_event(GdkEventKey* key) { - class Guard { - public: - bool &value; - Guard(bool &value_) : value(value_) { value = true; } - ~Guard() { value = false; } - }; - Guard guard{multiple_cursors_use}; - - if(SelectionDialog::get() && SelectionDialog::get()->is_visible()) { - if(SelectionDialog::get()->on_key_press(key)) - return true; - } - if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) { - if(CompletionDialog::get()->on_key_press(key)) - return true; - } - - if(last_keyval<GDK_KEY_Shift_L || last_keyval>GDK_KEY_Hyper_R) - previous_non_modifier_keyval=last_keyval; - last_keyval=key->keyval; - - if(Config::get().source.enable_multiple_cursors && on_key_press_event_multiple_cursors(key)) - return true; - - //Move cursor one paragraph down - if((key->keyval==GDK_KEY_Down || key->keyval==GDK_KEY_KP_Down) && (key->state&GDK_CONTROL_MASK)>0) { - auto selection_start_iter=get_buffer()->get_selection_bound()->get_iter(); - auto iter=get_buffer()->get_iter_at_line(get_buffer()->get_insert()->get_iter().get_line()); - bool empty_line=false; - bool text_found=false; - for(;;) { - if(!iter) - break; - if(iter.starts_line()) - empty_line=true; - if(empty_line && !iter.ends_line() && *iter!='\n' && *iter!=' ' && *iter!='\t') - empty_line=false; - if(!text_found && !iter.ends_line() && *iter!='\n' && *iter!=' ' && *iter!='\t') - text_found=true; - if(empty_line && text_found && iter.ends_line()) - break; - iter.forward_char(); - } - iter=get_buffer()->get_iter_at_line(iter.get_line()); - if((key->state&GDK_SHIFT_MASK)>0) - get_buffer()->select_range(iter, selection_start_iter); - else - get_buffer()->place_cursor(iter); - scroll_to(get_buffer()->get_insert()); - return true; - } - //Move cursor one paragraph up - else if((key->keyval==GDK_KEY_Up || key->keyval==GDK_KEY_KP_Up) && (key->state&GDK_CONTROL_MASK)>0) { - auto selection_start_iter=get_buffer()->get_selection_bound()->get_iter(); - auto iter=get_buffer()->get_iter_at_line(get_buffer()->get_insert()->get_iter().get_line()); - iter.backward_char(); - bool empty_line=false; - bool text_found=false; - bool move_to_start=false; - for(;;) { - if(!iter) - break; - if(iter.ends_line()) - empty_line=true; - if(empty_line && !iter.ends_line() && *iter!='\n' && *iter!=' ' && *iter!='\t') - empty_line=false; - if(!text_found && !iter.ends_line() && *iter!='\n' && *iter!=' ' && *iter!='\t') - text_found=true; - if(empty_line && text_found && iter.starts_line()) - break; - if(iter.is_start()) { - move_to_start=true; - break; - } - iter.backward_char(); - } - if(empty_line && !move_to_start) { - iter=get_iter_at_line_end(iter.get_line()); - iter.forward_char(); - if(!iter.starts_line()) // For CR+LF - iter.forward_char(); - } - if((key->state&GDK_SHIFT_MASK)>0) - get_buffer()->select_range(iter, selection_start_iter); +void Source::View::cleanup_whitespace_characters_on_return(const Gtk::TextIter &iter) { + auto start_blank_iter = iter; + auto end_blank_iter = iter; + while ((*end_blank_iter == ' ' || *end_blank_iter == '\t') && + !end_blank_iter.ends_line() && end_blank_iter.forward_char()) {} + if (!start_blank_iter.starts_line()) { + start_blank_iter.backward_char(); + while ((*start_blank_iter == ' ' || *start_blank_iter == '\t') && + !start_blank_iter.starts_line() && start_blank_iter.backward_char()) {} + if (*start_blank_iter != ' ' && *start_blank_iter != '\t') + start_blank_iter.forward_char(); + } + + if (start_blank_iter.starts_line()) + get_buffer()->erase(iter, end_blank_iter); else - get_buffer()->place_cursor(iter); - scroll_to(get_buffer()->get_insert()); - return true; - } + get_buffer()->erase(start_blank_iter, end_blank_iter); +} + +bool Source::View::on_key_press_event(GdkEventKey *key) { + class Guard { + public: + bool &value; + + Guard(bool &value_) : value(value_) { value = true; } + + ~Guard() { value = false; } + }; + Guard guard{multiple_cursors_use}; - get_buffer()->begin_user_action(); - - // Shift+enter: go to end of line and enter - if((key->keyval==GDK_KEY_Return || key->keyval==GDK_KEY_KP_Enter) && (key->state&GDK_SHIFT_MASK)>0) { - auto iter=get_buffer()->get_insert()->get_iter(); - if(!iter.ends_line()) { - iter.forward_to_line_end(); - get_buffer()->place_cursor(iter); + if (SelectionDialog::get() && SelectionDialog::get()->is_visible()) { + if (SelectionDialog::get()->on_key_press(key)) + return true; + } + if (CompletionDialog::get() && CompletionDialog::get()->is_visible()) { + if (CompletionDialog::get()->on_key_press(key)) + return true; } - } - - if(Config::get().source.smart_brackets && on_key_press_event_smart_brackets(key)) { - get_buffer()->end_user_action(); - return true; - } - if(Config::get().source.smart_inserts && on_key_press_event_smart_inserts(key)) { - get_buffer()->end_user_action(); - return true; - } - - if(is_bracket_language && on_key_press_event_bracket_language(key)) { - get_buffer()->end_user_action(); - return true; - } - else if(on_key_press_event_basic(key)) { - get_buffer()->end_user_action(); - return true; - } - else { - get_buffer()->end_user_action(); - return Gsv::View::on_key_press_event(key); - } -} -//Basic indentation -bool Source::View::on_key_press_event_basic(GdkEventKey* key) { - auto iter=get_buffer()->get_insert()->get_iter(); - - //Indent as in next or previous line - if((key->keyval==GDK_KEY_Return || key->keyval==GDK_KEY_KP_Enter) && !get_buffer()->get_has_selection() && !iter.starts_line()) { - cleanup_whitespace_characters_on_return(iter); - - iter=get_buffer()->get_insert()->get_iter(); - auto tabs=get_line_before(get_tabs_end_iter(iter)); - - int line_nr=iter.get_line(); - if(iter.ends_line() && (line_nr+1)<get_buffer()->get_line_count()) { - auto next_line_tabs=get_line_before(get_tabs_end_iter(line_nr+1)); - if(next_line_tabs.size()>tabs.size()) { - get_buffer()->insert_at_cursor("\n"+next_line_tabs); + if (last_keyval < GDK_KEY_Shift_L || last_keyval > GDK_KEY_Hyper_R) + previous_non_modifier_keyval = last_keyval; + last_keyval = key->keyval; + + if (Config::get().source.enable_multiple_cursors && on_key_press_event_multiple_cursors(key)) + return true; + + //Move cursor one paragraph down + if ((key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down) && (key->state & GDK_CONTROL_MASK) > 0) { + auto selection_start_iter = get_buffer()->get_selection_bound()->get_iter(); + auto iter = get_buffer()->get_iter_at_line(get_buffer()->get_insert()->get_iter().get_line()); + bool empty_line = false; + bool text_found = false; + for (;;) { + if (!iter) + break; + if (iter.starts_line()) + empty_line = true; + if (empty_line && !iter.ends_line() && *iter != '\n' && *iter != ' ' && *iter != '\t') + empty_line = false; + if (!text_found && !iter.ends_line() && *iter != '\n' && *iter != ' ' && *iter != '\t') + text_found = true; + if (empty_line && text_found && iter.ends_line()) + break; + iter.forward_char(); + } + iter = get_buffer()->get_iter_at_line(iter.get_line()); + if ((key->state & GDK_SHIFT_MASK) > 0) + get_buffer()->select_range(iter, selection_start_iter); + else + get_buffer()->place_cursor(iter); scroll_to(get_buffer()->get_insert()); return true; - } } - get_buffer()->insert_at_cursor("\n"+tabs); - scroll_to(get_buffer()->get_insert()); - return true; - } - else if(key->keyval==GDK_KEY_Tab && (key->state&GDK_SHIFT_MASK)==0) { - if(!Config::get().source.tab_indents_line && !get_buffer()->get_has_selection()) { - get_buffer()->insert_at_cursor(tab); - return true; - } - //Indent right when clicking tab, no matter where in the line the cursor is. Also works on selected text. - //Special case if insert is at beginning of empty line: - if(iter.starts_line() && iter.ends_line() && !get_buffer()->get_has_selection()) { - auto prev_line_iter=iter; - while(prev_line_iter.starts_line() && prev_line_iter.backward_char()) {} - auto prev_line_tabs_end_iter=get_tabs_end_iter(prev_line_iter); - auto previous_line_tabs=get_line_before(prev_line_tabs_end_iter); - - auto next_line_iter=iter; - while(next_line_iter.starts_line() && next_line_iter.forward_char()) {} - auto next_line_tabs_end_iter=get_tabs_end_iter(next_line_iter); - auto next_line_tabs=get_line_before(next_line_tabs_end_iter); - - std::string tabs; - if(previous_line_tabs.size()<next_line_tabs.size()) - tabs=previous_line_tabs; - else - tabs=next_line_tabs; - if(tabs.size()>=tab_size) { - get_buffer()->insert_at_cursor(tabs); - return true; - } - } - - Gtk::TextIter selection_start, selection_end; - get_buffer()->get_selection_bounds(selection_start, selection_end); - auto selection_end_mark=get_buffer()->create_mark(selection_end); - int line_start=selection_start.get_line(); - int line_end=selection_end.get_line(); - for(int line=line_start;line<=line_end;line++) { - Gtk::TextIter line_it = get_buffer()->get_iter_at_line(line); - if(!get_buffer()->get_has_selection() || line_it!=selection_end_mark->get_iter()) - get_buffer()->insert(line_it, tab); - } - get_buffer()->delete_mark(selection_end_mark); - return true; - } - //Indent left when clicking shift-tab, no matter where in the line the cursor is. Also works on selected text. - else if((key->keyval==GDK_KEY_ISO_Left_Tab || key->keyval==GDK_KEY_Tab) && (key->state&GDK_SHIFT_MASK)>0) { - Gtk::TextIter selection_start, selection_end; - get_buffer()->get_selection_bounds(selection_start, selection_end); - int line_start=selection_start.get_line(); - int line_end=selection_end.get_line(); - - unsigned indent_left_steps=tab_size; - std::vector<bool> ignore_line; - for(int line_nr=line_start;line_nr<=line_end;line_nr++) { - auto line_it = get_buffer()->get_iter_at_line(line_nr); - if(!get_buffer()->get_has_selection() || line_it!=selection_end) { - auto tabs_end_iter=get_tabs_end_iter(line_nr); - if(tabs_end_iter.starts_line() && tabs_end_iter.ends_line()) - ignore_line.push_back(true); - else { - auto line_tabs=get_line_before(tabs_end_iter); - - if(line_tabs.size()>0) { - indent_left_steps=std::min(indent_left_steps, static_cast<unsigned>(line_tabs.size())); - ignore_line.push_back(false); - } - else - return true; + //Move cursor one paragraph up + else if ((key->keyval == GDK_KEY_Up || key->keyval == GDK_KEY_KP_Up) && (key->state & GDK_CONTROL_MASK) > 0) { + auto selection_start_iter = get_buffer()->get_selection_bound()->get_iter(); + auto iter = get_buffer()->get_iter_at_line(get_buffer()->get_insert()->get_iter().get_line()); + iter.backward_char(); + bool empty_line = false; + bool text_found = false; + bool move_to_start = false; + for (;;) { + if (!iter) + break; + if (iter.ends_line()) + empty_line = true; + if (empty_line && !iter.ends_line() && *iter != '\n' && *iter != ' ' && *iter != '\t') + empty_line = false; + if (!text_found && !iter.ends_line() && *iter != '\n' && *iter != ' ' && *iter != '\t') + text_found = true; + if (empty_line && text_found && iter.starts_line()) + break; + if (iter.is_start()) { + move_to_start = true; + break; + } + iter.backward_char(); } - } - } - - for(int line_nr=line_start;line_nr<=line_end;line_nr++) { - Gtk::TextIter line_it = get_buffer()->get_iter_at_line(line_nr); - Gtk::TextIter line_plus_it=line_it; - if(!get_buffer()->get_has_selection() || line_it!=selection_end) { - line_plus_it.forward_chars(indent_left_steps); - if(!ignore_line.at(line_nr-line_start)) - get_buffer()->erase(line_it, line_plus_it); - } - } - return true; - } - //"Smart" backspace key - else if(key->keyval==GDK_KEY_BackSpace && !get_buffer()->get_has_selection()) { - auto line=get_line_before(); - bool do_smart_backspace=true; - for(auto &chr: line) { - if(chr!=' ' && chr!='\t') { - do_smart_backspace=false; - break; - } - } - if(iter.get_line()==0) // Special case since there are no previous line - do_smart_backspace=false; - if(do_smart_backspace) { - auto previous_line_end_iter=iter; - if(previous_line_end_iter.backward_chars(line.size()+1)) { - if(!previous_line_end_iter.ends_line()) // For CR+LF - previous_line_end_iter.backward_char(); - if(previous_line_end_iter.starts_line()) // When previous line is empty, keep tabs in current line - get_buffer()->erase(previous_line_end_iter, get_buffer()->get_iter_at_line(iter.get_line())); + if (empty_line && !move_to_start) { + iter = get_iter_at_line_end(iter.get_line()); + iter.forward_char(); + if (!iter.starts_line()) // For CR+LF + iter.forward_char(); + } + if ((key->state & GDK_SHIFT_MASK) > 0) + get_buffer()->select_range(iter, selection_start_iter); else - get_buffer()->erase(previous_line_end_iter, iter); + get_buffer()->place_cursor(iter); + scroll_to(get_buffer()->get_insert()); return true; - } } - } - //"Smart" delete key - else if(key->keyval==GDK_KEY_Delete && !get_buffer()->get_has_selection()) { - auto insert_iter=iter; - bool do_smart_delete=true; - do { - if(*iter!=' ' && *iter!='\t' && !iter.ends_line()) { - do_smart_delete=false; - break; - } - if(iter.ends_line()) { - if(*iter=='\r') // For CR+LF - iter.forward_char(); - if(!iter.forward_char()) - do_smart_delete=false; - break; - } - } while(iter.forward_char()); - if(do_smart_delete) { - if(!insert_iter.starts_line()) { - while((*iter==' ' || *iter=='\t') && iter.forward_char()) {} - } - get_buffer()->erase(insert_iter, iter); - return true; + + get_buffer()->begin_user_action(); + + // Shift+enter: go to end of line and enter + if ((key->keyval == GDK_KEY_Return || key->keyval == GDK_KEY_KP_Enter) && (key->state & GDK_SHIFT_MASK) > 0) { + auto iter = get_buffer()->get_insert()->get_iter(); + if (!iter.ends_line()) { + iter.forward_to_line_end(); + get_buffer()->place_cursor(iter); + } } - } - // Smart Home/End-keys - else if((key->keyval==GDK_KEY_Home || key->keyval==GDK_KEY_KP_Home) && (key->state&GDK_CONTROL_MASK)==0) { - if((key->state&GDK_SHIFT_MASK)>0) - get_buffer()->move_mark_by_name("insert", get_smart_home_iter(iter)); - else - get_buffer()->place_cursor(get_smart_home_iter(iter)); - scroll_to(get_buffer()->get_insert()); - return true; - } - else if((key->keyval==GDK_KEY_End || key->keyval==GDK_KEY_KP_End) && (key->state&GDK_CONTROL_MASK)==0) { - if((key->state&GDK_SHIFT_MASK)>0) - get_buffer()->move_mark_by_name("insert", get_smart_end_iter(iter)); - else - get_buffer()->place_cursor(get_smart_end_iter(iter)); - scroll_to(get_buffer()->get_insert()); - return true; - } - //Workaround for TextView::on_key_press_event bug sometimes causing segmentation faults - //TODO: figure out the bug and create pull request to gtk - //Have only experienced this on OS X - //Note: valgrind reports issues on TextView::on_key_press_event as well - auto unicode=gdk_keyval_to_unicode(key->keyval); - if((key->state&(GDK_CONTROL_MASK|GDK_META_MASK))==0 && unicode>=32 && unicode!=127 && - (previous_non_modifier_keyval<GDK_KEY_dead_grave || previous_non_modifier_keyval>GDK_KEY_dead_greek)) { - if(get_buffer()->get_has_selection()) { - Gtk::TextIter selection_start, selection_end; - get_buffer()->get_selection_bounds(selection_start, selection_end); - get_buffer()->erase(selection_start, selection_end); - } - get_buffer()->insert_at_cursor(Glib::ustring(1, unicode)); - scroll_to(get_buffer()->get_insert()); - - //Trick to make the cursor visible right after insertion: - set_cursor_visible(false); - set_cursor_visible(); - - return true; - } + if (Config::get().source.smart_brackets && on_key_press_event_smart_brackets(key)) { + get_buffer()->end_user_action(); + return true; + } + if (Config::get().source.smart_inserts && on_key_press_event_smart_inserts(key)) { + get_buffer()->end_user_action(); + return true; + } - return false; + if (is_bracket_language && on_key_press_event_bracket_language(key)) { + get_buffer()->end_user_action(); + return true; + } else if (on_key_press_event_basic(key)) { + get_buffer()->end_user_action(); + return true; + } else { + get_buffer()->end_user_action(); + return Gsv::View::on_key_press_event(key); + } } -//Bracket language indentation -bool Source::View::on_key_press_event_bracket_language(GdkEventKey* key) { - const static std::regex no_bracket_statement_regex("^ *(if|for|while) *\\(.*[^;}{] *$|" - "^[}]? *else if *\\(.*[^;}{] *$|" - "^[}]? *else *$", std::regex::extended); - - auto iter=get_buffer()->get_insert()->get_iter(); +//Basic indentation +bool Source::View::on_key_press_event_basic(GdkEventKey *key) { + auto iter = get_buffer()->get_insert()->get_iter(); - if(get_buffer()->get_has_selection()) - return false; - - if(!is_code_iter(iter)) { - // Add * at start of line in comment blocks - if(key->keyval==GDK_KEY_Return || key->keyval==GDK_KEY_KP_Enter) { - if(!iter.starts_line() && (!string_tag || (!iter.has_tag(string_tag) && !iter.ends_tag(string_tag)))) { + //Indent as in next or previous line + if ((key->keyval == GDK_KEY_Return || key->keyval == GDK_KEY_KP_Enter) && !get_buffer()->get_has_selection() && + !iter.starts_line()) { cleanup_whitespace_characters_on_return(iter); - iter=get_buffer()->get_insert()->get_iter(); - - auto start_iter=get_tabs_end_iter(iter.get_line()); - auto end_iter=start_iter; - end_iter.forward_chars(2); - auto start_of_sentence=get_buffer()->get_text(start_iter, end_iter); - if(!start_of_sentence.empty()) { - if(start_of_sentence=="/*" || start_of_sentence[0]=='*') { - auto tabs=get_line_before(start_iter); - auto insert_str="\n"+tabs; - if(start_of_sentence[0]=='/') - insert_str+=' '; - insert_str+="* "; - - get_buffer()->insert_at_cursor(insert_str); - return true; - } + + iter = get_buffer()->get_insert()->get_iter(); + auto tabs = get_line_before(get_tabs_end_iter(iter)); + + int line_nr = iter.get_line(); + if (iter.ends_line() && (line_nr + 1) < get_buffer()->get_line_count()) { + auto next_line_tabs = get_line_before(get_tabs_end_iter(line_nr + 1)); + if (next_line_tabs.size() > tabs.size()) { + get_buffer()->insert_at_cursor("\n" + next_line_tabs); + scroll_to(get_buffer()->get_insert()); + return true; + } } - } - else if(!comment_tag || !iter.ends_tag(comment_tag)) - return false; - } - else - return false; - } - - // get iter for if expressions below, which is moved backwards past any comment - auto get_condition_iter=[this](const Gtk::TextIter &iter) { - auto condition_iter=iter; - condition_iter.backward_char(); - if(!comment_tag) - return condition_iter; - while(!condition_iter.starts_line() && (condition_iter.has_tag(comment_tag) || -#if GTKMM_MAJOR_VERSION>3 || (GTKMM_MAJOR_VERSION==3 && GTKMM_MINOR_VERSION>=20) - condition_iter.starts_tag(comment_tag) || -#else - *condition_iter=='/' || -#endif - *condition_iter==' ' || *condition_iter=='\t') && - condition_iter.backward_char()) {} - return condition_iter; - }; - - //Indent depending on if/else/etc and brackets - if((key->keyval==GDK_KEY_Return || key->keyval==GDK_KEY_KP_Enter) && !iter.starts_line()) { - cleanup_whitespace_characters_on_return(iter); - iter=get_buffer()->get_insert()->get_iter(); - - auto condition_iter=get_condition_iter(iter); - auto start_iter=condition_iter; - if(*start_iter=='{') - start_iter.backward_char(); - Gtk::TextIter open_non_curly_bracket_iter; - bool open_non_curly_bracket_iter_found=false; - if(find_open_non_curly_bracket_backward(start_iter, open_non_curly_bracket_iter)) { - open_non_curly_bracket_iter_found=true; - start_iter=get_tabs_end_iter(get_buffer()->get_iter_at_line(open_non_curly_bracket_iter.get_line())); - } - else - start_iter=get_tabs_end_iter(get_buffer()->get_iter_at_line(find_start_of_sentence(condition_iter).get_line())); - auto tabs=get_line_before(start_iter); - - /* - * Change tabs after ending comment block with an extra space (as in this case) - */ - if(tabs.size()%tab_size==1 && !start_iter.ends_line() && !is_code_iter(start_iter)) { - auto end_of_line_iter=start_iter; - end_of_line_iter.forward_to_line_end(); - auto line=get_buffer()->get_text(start_iter, end_of_line_iter); - if(!line.empty() && line.compare(0, 2, "*/")==0) { - tabs.pop_back(); - get_buffer()->insert_at_cursor("\n"+tabs); + get_buffer()->insert_at_cursor("\n" + tabs); scroll_to(get_buffer()->get_insert()); return true; - } - } - - if(*condition_iter=='{' && is_code_iter(condition_iter)) { - Gtk::TextIter found_iter; - // Check if an '}' is needed - bool has_right_curly_bracket=false; - bool found_right_bracket=find_close_curly_bracket_forward(iter, found_iter); - if(found_right_bracket) { - auto tabs_end_iter=get_tabs_end_iter(found_iter); - auto line_tabs=get_line_before(tabs_end_iter); - if(tabs.size()==line_tabs.size()) - has_right_curly_bracket=true; - } - // special case for functions and classes with no indentation after: namespace { - if(tabs.empty() && has_right_curly_bracket) - has_right_curly_bracket=symbol_count(iter, '{', '}')!=1; - - if(*get_buffer()->get_insert()->get_iter()=='}') { - get_buffer()->insert_at_cursor("\n"+tabs+tab+"\n"+tabs); - auto insert_it = get_buffer()->get_insert()->get_iter(); - if(insert_it.backward_chars(tabs.size()+1)) { - scroll_to(get_buffer()->get_insert()); - get_buffer()->place_cursor(insert_it); + } else if (key->keyval == GDK_KEY_Tab && (key->state & GDK_SHIFT_MASK) == 0) { + if (!Config::get().source.tab_indents_line && !get_buffer()->get_has_selection()) { + get_buffer()->insert_at_cursor(tab); + return true; } + //Indent right when clicking tab, no matter where in the line the cursor is. Also works on selected text. + //Special case if insert is at beginning of empty line: + if (iter.starts_line() && iter.ends_line() && !get_buffer()->get_has_selection()) { + auto prev_line_iter = iter; + while (prev_line_iter.starts_line() && prev_line_iter.backward_char()) {} + auto prev_line_tabs_end_iter = get_tabs_end_iter(prev_line_iter); + auto previous_line_tabs = get_line_before(prev_line_tabs_end_iter); + + auto next_line_iter = iter; + while (next_line_iter.starts_line() && next_line_iter.forward_char()) {} + auto next_line_tabs_end_iter = get_tabs_end_iter(next_line_iter); + auto next_line_tabs = get_line_before(next_line_tabs_end_iter); + + std::string tabs; + if (previous_line_tabs.size() < next_line_tabs.size()) + tabs = previous_line_tabs; + else + tabs = next_line_tabs; + if (tabs.size() >= tab_size) { + get_buffer()->insert_at_cursor(tabs); + return true; + } + } + + Gtk::TextIter selection_start, selection_end; + get_buffer()->get_selection_bounds(selection_start, selection_end); + auto selection_end_mark = get_buffer()->create_mark(selection_end); + int line_start = selection_start.get_line(); + int line_end = selection_end.get_line(); + for (int line = line_start; line <= line_end; line++) { + Gtk::TextIter line_it = get_buffer()->get_iter_at_line(line); + if (!get_buffer()->get_has_selection() || line_it != selection_end_mark->get_iter()) + get_buffer()->insert(line_it, tab); + } + get_buffer()->delete_mark(selection_end_mark); return true; - } - else if(!has_right_curly_bracket) { - //Insert new lines with bracket end - bool add_semicolon=false; - if(language && (language->get_id()=="chdr" || language->get_id()=="cpphdr" || - language->get_id()=="c" || language->get_id()=="cpp")) { - auto token=get_token(start_iter); - if(token.empty()) { - auto iter=start_iter; - while(!iter.starts_line() && iter.backward_char()) {} - if(iter.backward_char()) - token=get_token(get_tabs_end_iter(get_buffer()->get_iter_at_line(iter.get_line()))); - } - //Add semicolon after class or struct - if(token=="class" || token=="struct") - add_semicolon=true; - //Add semicolon after lambda unless it's a parameter - else if(!open_non_curly_bracket_iter_found) { - auto it=condition_iter; - long para_count=0; - long square_count=0; - bool square_outside_para_found=false; - while(it.backward_char()) { - if(*it==']' && is_code_iter(it)) { - --square_count; - if(para_count==0) - square_outside_para_found=true; - } - else if(*it=='[' && is_code_iter(it)) - ++square_count; - else if(*it==')' && is_code_iter(it)) - --para_count; - else if(*it=='(' && is_code_iter(it)) - ++para_count; - - if(square_outside_para_found && square_count==0 && para_count==0) { - add_semicolon=true; - break; - } - if(it==start_iter) - break; - if(!square_outside_para_found && square_count==0 && para_count==0) { - if((*it>='A' && *it<='Z') || (*it>='a' && *it<='z') || (*it>='0' && *it<='9') || *it=='_' || - *it=='-' || *it==' ' || *it=='\t' || *it=='<' || *it=='>' || *it=='(' || *it==':' || - *it=='*' || *it=='&' || *it=='/' || it.ends_line() || !is_code_iter(it)) { - continue; + } + //Indent left when clicking shift-tab, no matter where in the line the cursor is. Also works on selected text. + else if ((key->keyval == GDK_KEY_ISO_Left_Tab || key->keyval == GDK_KEY_Tab) && (key->state & GDK_SHIFT_MASK) > 0) { + Gtk::TextIter selection_start, selection_end; + get_buffer()->get_selection_bounds(selection_start, selection_end); + int line_start = selection_start.get_line(); + int line_end = selection_end.get_line(); + + unsigned indent_left_steps = tab_size; + std::vector<bool> ignore_line; + for (int line_nr = line_start; line_nr <= line_end; line_nr++) { + auto line_it = get_buffer()->get_iter_at_line(line_nr); + if (!get_buffer()->get_has_selection() || line_it != selection_end) { + auto tabs_end_iter = get_tabs_end_iter(line_nr); + if (tabs_end_iter.starts_line() && tabs_end_iter.ends_line()) + ignore_line.push_back(true); + else { + auto line_tabs = get_line_before(tabs_end_iter); + + if (line_tabs.size() > 0) { + indent_left_steps = std::min(indent_left_steps, static_cast<unsigned>(line_tabs.size())); + ignore_line.push_back(false); + } else + return true; } + } + } + + for (int line_nr = line_start; line_nr <= line_end; line_nr++) { + Gtk::TextIter line_it = get_buffer()->get_iter_at_line(line_nr); + Gtk::TextIter line_plus_it = line_it; + if (!get_buffer()->get_has_selection() || line_it != selection_end) { + line_plus_it.forward_chars(indent_left_steps); + if (!ignore_line.at(line_nr - line_start)) + get_buffer()->erase(line_it, line_plus_it); + } + } + return true; + } + //"Smart" backspace key + else if (key->keyval == GDK_KEY_BackSpace && !get_buffer()->get_has_selection()) { + auto line = get_line_before(); + bool do_smart_backspace = true; + for (auto &chr: line) { + if (chr != ' ' && chr != '\t') { + do_smart_backspace = false; + break; + } + } + if (iter.get_line() == 0) // Special case since there are no previous line + do_smart_backspace = false; + if (do_smart_backspace) { + auto previous_line_end_iter = iter; + if (previous_line_end_iter.backward_chars(line.size() + 1)) { + if (!previous_line_end_iter.ends_line()) // For CR+LF + previous_line_end_iter.backward_char(); + if (previous_line_end_iter.starts_line()) // When previous line is empty, keep tabs in current line + get_buffer()->erase(previous_line_end_iter, get_buffer()->get_iter_at_line(iter.get_line())); else - break; - } + get_buffer()->erase(previous_line_end_iter, iter); + return true; } - } } - get_buffer()->insert_at_cursor("\n"+tabs+tab+"\n"+tabs+(add_semicolon?"};":"}")); - auto insert_it = get_buffer()->get_insert()->get_iter(); - if(insert_it.backward_chars(tabs.size()+(add_semicolon?3:2))) { - scroll_to(get_buffer()->get_insert()); - get_buffer()->place_cursor(insert_it); + } + //"Smart" delete key + else if (key->keyval == GDK_KEY_Delete && !get_buffer()->get_has_selection()) { + auto insert_iter = iter; + bool do_smart_delete = true; + do { + if (*iter != ' ' && *iter != '\t' && !iter.ends_line()) { + do_smart_delete = false; + break; + } + if (iter.ends_line()) { + if (*iter == '\r') // For CR+LF + iter.forward_char(); + if (!iter.forward_char()) + do_smart_delete = false; + break; + } + } while (iter.forward_char()); + if (do_smart_delete) { + if (!insert_iter.starts_line()) { + while ((*iter == ' ' || *iter == '\t') && iter.forward_char()) {} + } + get_buffer()->erase(insert_iter, iter); + return true; } + } + // Smart Home/End-keys + else if ((key->keyval == GDK_KEY_Home || key->keyval == GDK_KEY_KP_Home) && (key->state & GDK_CONTROL_MASK) == 0) { + if ((key->state & GDK_SHIFT_MASK) > 0) + get_buffer()->move_mark_by_name("insert", get_smart_home_iter(iter)); + else + get_buffer()->place_cursor(get_smart_home_iter(iter)); + scroll_to(get_buffer()->get_insert()); return true; - } - else { - get_buffer()->insert_at_cursor("\n"+tabs+tab); + } else if ((key->keyval == GDK_KEY_End || key->keyval == GDK_KEY_KP_End) && (key->state & GDK_CONTROL_MASK) == 0) { + if ((key->state & GDK_SHIFT_MASK) > 0) + get_buffer()->move_mark_by_name("insert", get_smart_end_iter(iter)); + else + get_buffer()->place_cursor(get_smart_end_iter(iter)); scroll_to(get_buffer()->get_insert()); return true; - } - } - - //Indent multiline expressions - if(open_non_curly_bracket_iter_found) { - auto tabs_end_iter=get_tabs_end_iter(open_non_curly_bracket_iter); - auto tabs=get_line_before(get_tabs_end_iter(open_non_curly_bracket_iter)); - auto iter=tabs_end_iter; - while(iter<=open_non_curly_bracket_iter) { - tabs+=' '; - iter.forward_char(); - } - get_buffer()->insert_at_cursor("\n"+tabs); - scroll_to(get_buffer()->get_insert()); - return true; - } - auto after_condition_iter=condition_iter; - after_condition_iter.forward_char(); - std::string sentence=get_buffer()->get_text(start_iter, after_condition_iter); - std::smatch sm; - if(std::regex_match(sentence, sm, no_bracket_statement_regex)) { - get_buffer()->insert_at_cursor("\n"+tabs+tab); - scroll_to(get_buffer()->get_insert()); - return true; - } - //Indenting after for instance if(...)\n...;\n - else if(*condition_iter==';' && condition_iter.get_line()>0 && is_code_iter(condition_iter)) { - auto previous_end_iter=start_iter; - while(previous_end_iter.backward_char() && !previous_end_iter.ends_line()) {} - auto condition_iter=get_condition_iter(previous_end_iter); - auto previous_start_iter=get_tabs_end_iter(get_buffer()->get_iter_at_line(find_start_of_sentence(condition_iter).get_line())); - auto previous_tabs=get_line_before(previous_start_iter); - auto after_condition_iter=condition_iter; - after_condition_iter.forward_char(); - std::string previous_sentence=get_buffer()->get_text(previous_start_iter, after_condition_iter); - std::smatch sm2; - if(std::regex_match(previous_sentence, sm2, no_bracket_statement_regex)) { - get_buffer()->insert_at_cursor("\n"+previous_tabs); + } + + //Workaround for TextView::on_key_press_event bug sometimes causing segmentation faults + //TODO: figure out the bug and create pull request to gtk + //Have only experienced this on OS X + //Note: valgrind reports issues on TextView::on_key_press_event as well + auto unicode = gdk_keyval_to_unicode(key->keyval); + if ((key->state & (GDK_CONTROL_MASK | GDK_META_MASK)) == 0 && unicode >= 32 && unicode != 127 && + (previous_non_modifier_keyval < GDK_KEY_dead_grave || previous_non_modifier_keyval > GDK_KEY_dead_greek)) { + if (get_buffer()->get_has_selection()) { + Gtk::TextIter selection_start, selection_end; + get_buffer()->get_selection_bounds(selection_start, selection_end); + get_buffer()->erase(selection_start, selection_end); + } + get_buffer()->insert_at_cursor(Glib::ustring(1, unicode)); scroll_to(get_buffer()->get_insert()); + + //Trick to make the cursor visible right after insertion: + set_cursor_visible(false); + set_cursor_visible(); + return true; - } - } - //Indenting after ':' - else if(*condition_iter==':' && is_code_iter(condition_iter)) { - bool perform_indent=true; - auto iter=condition_iter; - while(!iter.starts_line() && *iter==' ' && iter.backward_char()) {} - if(*iter==')') { - auto token=get_token(get_tabs_end_iter(get_buffer()->get_iter_at_line(iter.get_line()))); - if(token!="case") - perform_indent=false; - } - if(perform_indent) { - Gtk::TextIter found_curly_iter; - if(find_open_curly_bracket_backward(iter, found_curly_iter)) { - auto tabs_end_iter=get_tabs_end_iter(get_buffer()->get_iter_at_line(found_curly_iter.get_line())); - auto tabs_start_of_sentence=get_line_before(tabs_end_iter); - if(tabs.size()==(tabs_start_of_sentence.size()+tab_size)) { - auto start_line_iter=get_buffer()->get_iter_at_line(iter.get_line()); - auto start_line_plus_tab_size=start_line_iter; - for(size_t c=0;c<tab_size;c++) - start_line_plus_tab_size.forward_char(); - get_buffer()->erase(start_line_iter, start_line_plus_tab_size); - } - else { - get_buffer()->insert_at_cursor("\n"+tabs+tab); + } + + return false; +} + +//Bracket language indentation +bool Source::View::on_key_press_event_bracket_language(GdkEventKey *key) { + const static std::regex no_bracket_statement_regex("^ *(if|for|while) *\\(.*[^;}{] *$|" + "^[}]? *else if *\\(.*[^;}{] *$|" + "^[}]? *else *$", std::regex::extended); + + auto iter = get_buffer()->get_insert()->get_iter(); + + if (get_buffer()->get_has_selection()) + return false; + + if (!is_code_iter(iter)) { + // Add * at start of line in comment blocks + if (key->keyval == GDK_KEY_Return || key->keyval == GDK_KEY_KP_Enter) { + if (!iter.starts_line() && (!string_tag || (!iter.has_tag(string_tag) && !iter.ends_tag(string_tag)))) { + cleanup_whitespace_characters_on_return(iter); + iter = get_buffer()->get_insert()->get_iter(); + + auto start_iter = get_tabs_end_iter(iter.get_line()); + auto end_iter = start_iter; + end_iter.forward_chars(2); + auto start_of_sentence = get_buffer()->get_text(start_iter, end_iter); + if (!start_of_sentence.empty()) { + if (start_of_sentence == "/*" || start_of_sentence[0] == '*') { + auto tabs = get_line_before(start_iter); + auto insert_str = "\n" + tabs; + if (start_of_sentence[0] == '/') + insert_str += ' '; + insert_str += "* "; + + get_buffer()->insert_at_cursor(insert_str); + return true; + } + } + } else if (!comment_tag || !iter.ends_tag(comment_tag)) + return false; + } else + return false; + } + + // get iter for if expressions below, which is moved backwards past any comment + auto get_condition_iter = [this](const Gtk::TextIter &iter) { + auto condition_iter = iter; + condition_iter.backward_char(); + if (!comment_tag) + return condition_iter; + while (!condition_iter.starts_line() && (condition_iter.has_tag(comment_tag) || + #if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION >= 20) + condition_iter.starts_tag(comment_tag) || + #else + *condition_iter=='/' || + #endif + *condition_iter == ' ' || *condition_iter == '\t') && + condition_iter.backward_char()) {} + return condition_iter; + }; + + //Indent depending on if/else/etc and brackets + if ((key->keyval == GDK_KEY_Return || key->keyval == GDK_KEY_KP_Enter) && !iter.starts_line()) { + cleanup_whitespace_characters_on_return(iter); + iter = get_buffer()->get_insert()->get_iter(); + + auto condition_iter = get_condition_iter(iter); + auto start_iter = condition_iter; + if (*start_iter == '{') + start_iter.backward_char(); + Gtk::TextIter open_non_curly_bracket_iter; + bool open_non_curly_bracket_iter_found = false; + if (find_open_non_curly_bracket_backward(start_iter, open_non_curly_bracket_iter)) { + open_non_curly_bracket_iter_found = true; + start_iter = get_tabs_end_iter(get_buffer()->get_iter_at_line(open_non_curly_bracket_iter.get_line())); + } else + start_iter = get_tabs_end_iter( + get_buffer()->get_iter_at_line(find_start_of_sentence(condition_iter).get_line())); + auto tabs = get_line_before(start_iter); + + /* + * Change tabs after ending comment block with an extra space (as in this case) + */ + if (tabs.size() % tab_size == 1 && !start_iter.ends_line() && !is_code_iter(start_iter)) { + auto end_of_line_iter = start_iter; + end_of_line_iter.forward_to_line_end(); + auto line = get_buffer()->get_text(start_iter, end_of_line_iter); + if (!line.empty() && line.compare(0, 2, "*/") == 0) { + tabs.pop_back(); + get_buffer()->insert_at_cursor("\n" + tabs); + scroll_to(get_buffer()->get_insert()); + return true; + } + } + + if (*condition_iter == '{' && is_code_iter(condition_iter)) { + Gtk::TextIter found_iter; + // Check if an '}' is needed + bool has_right_curly_bracket = false; + bool found_right_bracket = find_close_curly_bracket_forward(iter, found_iter); + if (found_right_bracket) { + auto tabs_end_iter = get_tabs_end_iter(found_iter); + auto line_tabs = get_line_before(tabs_end_iter); + if (tabs.size() == line_tabs.size()) + has_right_curly_bracket = true; + } + // special case for functions and classes with no indentation after: namespace { + if (tabs.empty() && has_right_curly_bracket) + has_right_curly_bracket = symbol_count(iter, '{', '}') != 1; + + if (*get_buffer()->get_insert()->get_iter() == '}') { + get_buffer()->insert_at_cursor("\n" + tabs + tab + "\n" + tabs); + auto insert_it = get_buffer()->get_insert()->get_iter(); + if (insert_it.backward_chars(tabs.size() + 1)) { + scroll_to(get_buffer()->get_insert()); + get_buffer()->place_cursor(insert_it); + } + return true; + } else if (!has_right_curly_bracket) { + //Insert new lines with bracket end + bool add_semicolon = false; + if (language && (language->get_id() == "chdr" || language->get_id() == "cpphdr" || + language->get_id() == "c" || language->get_id() == "cpp")) { + auto token = get_token(start_iter); + if (token.empty()) { + auto iter = start_iter; + while (!iter.starts_line() && iter.backward_char()) {} + if (iter.backward_char()) + token = get_token(get_tabs_end_iter(get_buffer()->get_iter_at_line(iter.get_line()))); + } + //Add semicolon after class or struct + if (token == "class" || token == "struct") + add_semicolon = true; + //Add semicolon after lambda unless it's a parameter + else if (!open_non_curly_bracket_iter_found) { + auto it = condition_iter; + long para_count = 0; + long square_count = 0; + bool square_outside_para_found = false; + while (it.backward_char()) { + if (*it == ']' && is_code_iter(it)) { + --square_count; + if (para_count == 0) + square_outside_para_found = true; + } else if (*it == '[' && is_code_iter(it)) + ++square_count; + else if (*it == ')' && is_code_iter(it)) + --para_count; + else if (*it == '(' && is_code_iter(it)) + ++para_count; + + if (square_outside_para_found && square_count == 0 && para_count == 0) { + add_semicolon = true; + break; + } + if (it == start_iter) + break; + if (!square_outside_para_found && square_count == 0 && para_count == 0) { + if ((*it >= 'A' && *it <= 'Z') || (*it >= 'a' && *it <= 'z') || + (*it >= '0' && *it <= '9') || *it == '_' || + *it == '-' || *it == ' ' || *it == '\t' || *it == '<' || *it == '>' || *it == '(' || + *it == ':' || + *it == '*' || *it == '&' || *it == '/' || it.ends_line() || !is_code_iter(it)) { + continue; + } else + break; + } + } + } + } + get_buffer()->insert_at_cursor("\n" + tabs + tab + "\n" + tabs + (add_semicolon ? "};" : "}")); + auto insert_it = get_buffer()->get_insert()->get_iter(); + if (insert_it.backward_chars(tabs.size() + (add_semicolon ? 3 : 2))) { + scroll_to(get_buffer()->get_insert()); + get_buffer()->place_cursor(insert_it); + } + return true; + } else { + get_buffer()->insert_at_cursor("\n" + tabs + tab); + scroll_to(get_buffer()->get_insert()); + return true; + } + } + + //Indent multiline expressions + if (open_non_curly_bracket_iter_found) { + auto tabs_end_iter = get_tabs_end_iter(open_non_curly_bracket_iter); + auto tabs = get_line_before(get_tabs_end_iter(open_non_curly_bracket_iter)); + auto iter = tabs_end_iter; + while (iter <= open_non_curly_bracket_iter) { + tabs += ' '; + iter.forward_char(); + } + get_buffer()->insert_at_cursor("\n" + tabs); scroll_to(get_buffer()->get_insert()); return true; - } } - } - } - get_buffer()->insert_at_cursor("\n"+tabs); - scroll_to(get_buffer()->get_insert()); - return true; - } - //Indent left when writing } on a new line - else if(key->keyval==GDK_KEY_braceright) { - std::string line=get_line_before(); - if(line.size()>=tab_size && iter.ends_line()) { - bool indent_left=true; - for(auto c: line) { - if(c!=tab_char) { - indent_left=false; - break; - } - } - if(indent_left) { - Gtk::TextIter insert_it = get_buffer()->get_insert()->get_iter(); - Gtk::TextIter line_it = get_buffer()->get_iter_at_line(insert_it.get_line()); - Gtk::TextIter line_plus_it=line_it; - line_plus_it.forward_chars(tab_size); - get_buffer()->erase(line_it, line_plus_it); - get_buffer()->insert_at_cursor("}"); - return true; - } - } - } - //Indent left when writing { on a new line after for instance if(...)\n... - else if(key->keyval==GDK_KEY_braceleft) { - auto tabs_end_iter=get_tabs_end_iter(); - auto tabs=get_line_before(tabs_end_iter); - size_t line_nr=iter.get_line(); - if(line_nr>0 && tabs.size()>=tab_size && iter==tabs_end_iter) { - auto previous_end_iter=iter; - while(previous_end_iter.backward_char() && !previous_end_iter.ends_line()) {} - auto condition_iter=get_condition_iter(previous_end_iter); - auto previous_start_iter=get_tabs_end_iter(get_buffer()->get_iter_at_line(find_start_of_sentence(condition_iter).get_line())); - auto previous_tabs=get_line_before(previous_start_iter); - auto after_condition_iter=condition_iter; - after_condition_iter.forward_char(); - if((tabs.size()-tab_size)==previous_tabs.size()) { - std::string previous_sentence=get_buffer()->get_text(previous_start_iter, after_condition_iter); + auto after_condition_iter = condition_iter; + after_condition_iter.forward_char(); + std::string sentence = get_buffer()->get_text(start_iter, after_condition_iter); std::smatch sm; - if(std::regex_match(previous_sentence, sm, no_bracket_statement_regex)) { - auto start_iter=iter; - start_iter.backward_chars(tab_size); - get_buffer()->erase(start_iter, iter); - get_buffer()->insert_at_cursor("{"); - scroll_to(get_buffer()->get_insert()); - return true; + if (std::regex_match(sentence, sm, no_bracket_statement_regex)) { + get_buffer()->insert_at_cursor("\n" + tabs + tab); + scroll_to(get_buffer()->get_insert()); + return true; } - } - } - } - // Mark parameters of templated functions after pressing tab and after writing template argument - else if(key->keyval==GDK_KEY_Tab && (key->state&GDK_SHIFT_MASK)==0) { - if(*iter=='>') { - iter.forward_char(); - Gtk::TextIter parenthesis_end_iter; - if(*iter=='(' && is_templated_function(iter, parenthesis_end_iter)) { - iter.forward_char(); - get_buffer()->select_range(iter, parenthesis_end_iter); + //Indenting after for instance if(...)\n...;\n + else if (*condition_iter == ';' && condition_iter.get_line() > 0 && is_code_iter(condition_iter)) { + auto previous_end_iter = start_iter; + while (previous_end_iter.backward_char() && !previous_end_iter.ends_line()) {} + auto condition_iter = get_condition_iter(previous_end_iter); + auto previous_start_iter = get_tabs_end_iter( + get_buffer()->get_iter_at_line(find_start_of_sentence(condition_iter).get_line())); + auto previous_tabs = get_line_before(previous_start_iter); + auto after_condition_iter = condition_iter; + after_condition_iter.forward_char(); + std::string previous_sentence = get_buffer()->get_text(previous_start_iter, after_condition_iter); + std::smatch sm2; + if (std::regex_match(previous_sentence, sm2, no_bracket_statement_regex)) { + get_buffer()->insert_at_cursor("\n" + previous_tabs); + scroll_to(get_buffer()->get_insert()); + return true; + } + } + //Indenting after ':' + else if (*condition_iter == ':' && is_code_iter(condition_iter)) { + bool perform_indent = true; + auto iter = condition_iter; + while (!iter.starts_line() && *iter == ' ' && iter.backward_char()) {} + if (*iter == ')') { + auto token = get_token(get_tabs_end_iter(get_buffer()->get_iter_at_line(iter.get_line()))); + if (token != "case") + perform_indent = false; + } + if (perform_indent) { + Gtk::TextIter found_curly_iter; + if (find_open_curly_bracket_backward(iter, found_curly_iter)) { + auto tabs_end_iter = get_tabs_end_iter(get_buffer()->get_iter_at_line(found_curly_iter.get_line())); + auto tabs_start_of_sentence = get_line_before(tabs_end_iter); + if (tabs.size() == (tabs_start_of_sentence.size() + tab_size)) { + auto start_line_iter = get_buffer()->get_iter_at_line(iter.get_line()); + auto start_line_plus_tab_size = start_line_iter; + for (size_t c = 0; c < tab_size; c++) + start_line_plus_tab_size.forward_char(); + get_buffer()->erase(start_line_iter, start_line_plus_tab_size); + } else { + get_buffer()->insert_at_cursor("\n" + tabs + tab); + scroll_to(get_buffer()->get_insert()); + return true; + } + } + } + } + get_buffer()->insert_at_cursor("\n" + tabs); scroll_to(get_buffer()->get_insert()); return true; - } } - } - - return false; + //Indent left when writing } on a new line + else if (key->keyval == GDK_KEY_braceright) { + std::string line = get_line_before(); + if (line.size() >= tab_size && iter.ends_line()) { + bool indent_left = true; + for (auto c: line) { + if (c != tab_char) { + indent_left = false; + break; + } + } + if (indent_left) { + Gtk::TextIter insert_it = get_buffer()->get_insert()->get_iter(); + Gtk::TextIter line_it = get_buffer()->get_iter_at_line(insert_it.get_line()); + Gtk::TextIter line_plus_it = line_it; + line_plus_it.forward_chars(tab_size); + get_buffer()->erase(line_it, line_plus_it); + get_buffer()->insert_at_cursor("}"); + return true; + } + } + } + //Indent left when writing { on a new line after for instance if(...)\n... + else if (key->keyval == GDK_KEY_braceleft) { + auto tabs_end_iter = get_tabs_end_iter(); + auto tabs = get_line_before(tabs_end_iter); + size_t line_nr = iter.get_line(); + if (line_nr > 0 && tabs.size() >= tab_size && iter == tabs_end_iter) { + auto previous_end_iter = iter; + while (previous_end_iter.backward_char() && !previous_end_iter.ends_line()) {} + auto condition_iter = get_condition_iter(previous_end_iter); + auto previous_start_iter = get_tabs_end_iter( + get_buffer()->get_iter_at_line(find_start_of_sentence(condition_iter).get_line())); + auto previous_tabs = get_line_before(previous_start_iter); + auto after_condition_iter = condition_iter; + after_condition_iter.forward_char(); + if ((tabs.size() - tab_size) == previous_tabs.size()) { + std::string previous_sentence = get_buffer()->get_text(previous_start_iter, after_condition_iter); + std::smatch sm; + if (std::regex_match(previous_sentence, sm, no_bracket_statement_regex)) { + auto start_iter = iter; + start_iter.backward_chars(tab_size); + get_buffer()->erase(start_iter, iter); + get_buffer()->insert_at_cursor("{"); + scroll_to(get_buffer()->get_insert()); + return true; + } + } + } + } + // Mark parameters of templated functions after pressing tab and after writing template argument + else if (key->keyval == GDK_KEY_Tab && (key->state & GDK_SHIFT_MASK) == 0) { + if (*iter == '>') { + iter.forward_char(); + Gtk::TextIter parenthesis_end_iter; + if (*iter == '(' && is_templated_function(iter, parenthesis_end_iter)) { + iter.forward_char(); + get_buffer()->select_range(iter, parenthesis_end_iter); + scroll_to(get_buffer()->get_insert()); + return true; + } + } + } + + return false; } bool Source::View::on_key_press_event_smart_brackets(GdkEventKey *key) { - if(get_buffer()->get_has_selection()) - return false; - - auto iter=get_buffer()->get_insert()->get_iter(); - auto previous_iter=iter; - previous_iter.backward_char(); - if(is_code_iter(iter)) { - //Move after ')' if closed expression - if(key->keyval==GDK_KEY_parenright) { - if(*iter==')' && symbol_count(iter, '(', ')')==0) { - iter.forward_char(); - get_buffer()->place_cursor(iter); - scroll_to(get_buffer()->get_insert()); - return true; - } - } - //Move after '>' if >( and closed expression - else if(key->keyval==GDK_KEY_greater) { - if(*iter=='>') { - iter.forward_char(); - Gtk::TextIter parenthesis_end_iter; - if(*iter=='(' && is_templated_function(iter, parenthesis_end_iter)) { - get_buffer()->place_cursor(iter); - scroll_to(get_buffer()->get_insert()); - return true; - } - } - } - //Move after '(' if >( and select text inside parentheses - else if(key->keyval==GDK_KEY_parenleft) { - auto previous_iter=iter; - previous_iter.backward_char(); - if(*previous_iter=='>') { - Gtk::TextIter parenthesis_end_iter; - if(*iter=='(' && is_templated_function(iter, parenthesis_end_iter)) { - iter.forward_char(); - get_buffer()->select_range(iter, parenthesis_end_iter); - scroll_to(iter); - return true; - } - } + if (get_buffer()->get_has_selection()) + return false; + + auto iter = get_buffer()->get_insert()->get_iter(); + auto previous_iter = iter; + previous_iter.backward_char(); + if (is_code_iter(iter)) { + //Move after ')' if closed expression + if (key->keyval == GDK_KEY_parenright) { + if (*iter == ')' && symbol_count(iter, '(', ')') == 0) { + iter.forward_char(); + get_buffer()->place_cursor(iter); + scroll_to(get_buffer()->get_insert()); + return true; + } + } + //Move after '>' if >( and closed expression + else if (key->keyval == GDK_KEY_greater) { + if (*iter == '>') { + iter.forward_char(); + Gtk::TextIter parenthesis_end_iter; + if (*iter == '(' && is_templated_function(iter, parenthesis_end_iter)) { + get_buffer()->place_cursor(iter); + scroll_to(get_buffer()->get_insert()); + return true; + } + } + } + //Move after '(' if >( and select text inside parentheses + else if (key->keyval == GDK_KEY_parenleft) { + auto previous_iter = iter; + previous_iter.backward_char(); + if (*previous_iter == '>') { + Gtk::TextIter parenthesis_end_iter; + if (*iter == '(' && is_templated_function(iter, parenthesis_end_iter)) { + iter.forward_char(); + get_buffer()->select_range(iter, parenthesis_end_iter); + scroll_to(iter); + return true; + } + } + } } - } - - return false; + + return false; } bool Source::View::on_key_press_event_smart_inserts(GdkEventKey *key) { - if(get_buffer()->get_has_selection()) { - bool perform_insertion=false; - char left_char, right_char; - // Insert () around selection - if(key->keyval==GDK_KEY_parenleft) { - perform_insertion=true; - left_char='('; - right_char=')'; - } - // Insert [] around selection - else if(key->keyval==GDK_KEY_bracketleft) { - perform_insertion=true; - left_char='['; - right_char=']'; - } - // Insert {} around selection - else if(key->keyval==GDK_KEY_braceleft) { - perform_insertion=true; - left_char='{'; - right_char='}'; - } - // Insert <> around selection - else if(key->keyval==GDK_KEY_less) { - perform_insertion=true; - left_char='<'; - right_char='>'; - } - // Insert '' around selection - else if(key->keyval==GDK_KEY_apostrophe) { - perform_insertion=true; - left_char='\''; - right_char='\''; - } - // Insert "" around selection - else if(key->keyval==GDK_KEY_quotedbl) { - perform_insertion=true; - left_char='"'; - right_char='"'; - } - else if(language && language->get_id()=="markdown") { - if(key->keyval==GDK_KEY_dead_grave) { - perform_insertion=true; - left_char='`'; - right_char='`'; - } - if(key->keyval==GDK_KEY_asterisk) { - perform_insertion=true; - left_char='*'; - right_char='*'; - } - if(key->keyval==GDK_KEY_underscore) { - perform_insertion=true; - left_char='_'; - right_char='_'; - } - if(key->keyval==GDK_KEY_dead_tilde) { - perform_insertion=true; - left_char='~'; - right_char='~'; - } - } - if(perform_insertion) { - Gtk::TextIter start, end; - get_buffer()->get_selection_bounds(start, end); - auto start_mark=get_buffer()->create_mark(start); - auto end_mark=get_buffer()->create_mark(end); - get_buffer()->insert(start, Glib::ustring()+left_char); - get_buffer()->insert(end_mark->get_iter(), Glib::ustring()+right_char); - auto start_mark_next_iter=start_mark->get_iter(); - start_mark_next_iter.forward_char(); - get_buffer()->select_range(start_mark_next_iter, end_mark->get_iter()); - get_buffer()->delete_mark(start_mark); - get_buffer()->delete_mark(end_mark); - return true; + if (get_buffer()->get_has_selection()) { + bool perform_insertion = false; + char left_char, right_char; + // Insert () around selection + if (key->keyval == GDK_KEY_parenleft) { + perform_insertion = true; + left_char = '('; + right_char = ')'; + } + // Insert [] around selection + else if (key->keyval == GDK_KEY_bracketleft) { + perform_insertion = true; + left_char = '['; + right_char = ']'; + } + // Insert {} around selection + else if (key->keyval == GDK_KEY_braceleft) { + perform_insertion = true; + left_char = '{'; + right_char = '}'; + } + // Insert <> around selection + else if (key->keyval == GDK_KEY_less) { + perform_insertion = true; + left_char = '<'; + right_char = '>'; + } + // Insert '' around selection + else if (key->keyval == GDK_KEY_apostrophe) { + perform_insertion = true; + left_char = '\''; + right_char = '\''; + } + // Insert "" around selection + else if (key->keyval == GDK_KEY_quotedbl) { + perform_insertion = true; + left_char = '"'; + right_char = '"'; + } else if (language && language->get_id() == "markdown") { + if (key->keyval == GDK_KEY_dead_grave) { + perform_insertion = true; + left_char = '`'; + right_char = '`'; + } + if (key->keyval == GDK_KEY_asterisk) { + perform_insertion = true; + left_char = '*'; + right_char = '*'; + } + if (key->keyval == GDK_KEY_underscore) { + perform_insertion = true; + left_char = '_'; + right_char = '_'; + } + if (key->keyval == GDK_KEY_dead_tilde) { + perform_insertion = true; + left_char = '~'; + right_char = '~'; + } + } + if (perform_insertion) { + Gtk::TextIter start, end; + get_buffer()->get_selection_bounds(start, end); + auto start_mark = get_buffer()->create_mark(start); + auto end_mark = get_buffer()->create_mark(end); + get_buffer()->insert(start, Glib::ustring() + left_char); + get_buffer()->insert(end_mark->get_iter(), Glib::ustring() + right_char); + auto start_mark_next_iter = start_mark->get_iter(); + start_mark_next_iter.forward_char(); + get_buffer()->select_range(start_mark_next_iter, end_mark->get_iter()); + get_buffer()->delete_mark(start_mark); + get_buffer()->delete_mark(end_mark); + return true; + } + return false; } - return false; - } - - auto iter=get_buffer()->get_insert()->get_iter(); - auto previous_iter=iter; - previous_iter.backward_char(); - auto next_iter=iter; - next_iter.forward_char(); - - auto allow_insertion=[](const Gtk::TextIter &iter) { - if(iter.ends_line() || *iter==' ' || *iter=='\t' || *iter==';' || *iter==')' || *iter==']' || *iter=='[' || *iter=='{' || *iter=='}') - return true; - return false; - }; - - // Move right when clicking ' before a ' or when clicking " before a " - if(((key->keyval==GDK_KEY_apostrophe && *iter=='\'') || - (key->keyval==GDK_KEY_quotedbl && *iter=='\"')) && is_code_iter(next_iter)) { - bool perform_move=false; - if(*previous_iter!='\\') - perform_move=true; - else { - auto it=previous_iter; - long backslash_count=1; - while(it.backward_char() && *it=='\\') { - ++backslash_count; - } - if(backslash_count%2==0) - perform_move=true; - } - if(perform_move) { - get_buffer()->place_cursor(next_iter); - scroll_to(get_buffer()->get_insert()); - return true; + + auto iter = get_buffer()->get_insert()->get_iter(); + auto previous_iter = iter; + previous_iter.backward_char(); + auto next_iter = iter; + next_iter.forward_char(); + + auto allow_insertion = [](const Gtk::TextIter &iter) { + if (iter.ends_line() || *iter == ' ' || *iter == '\t' || *iter == ';' || *iter == ')' || *iter == ']' || + *iter == '[' || *iter == '{' || *iter == '}') + return true; + return false; + }; + + // Move right when clicking ' before a ' or when clicking " before a " + if (((key->keyval == GDK_KEY_apostrophe && *iter == '\'') || + (key->keyval == GDK_KEY_quotedbl && *iter == '\"')) && is_code_iter(next_iter)) { + bool perform_move = false; + if (*previous_iter != '\\') + perform_move = true; + else { + auto it = previous_iter; + long backslash_count = 1; + while (it.backward_char() && *it == '\\') { + ++backslash_count; + } + if (backslash_count % 2 == 0) + perform_move = true; + } + if (perform_move) { + get_buffer()->place_cursor(next_iter); + scroll_to(get_buffer()->get_insert()); + return true; + } } - } - // When to delete '' or "" - else if(key->keyval==GDK_KEY_BackSpace) { - if(((*previous_iter=='\'' && *iter=='\'') || - (*previous_iter=='"' && *iter=='"')) && is_code_iter(previous_iter)) { - get_buffer()->erase(previous_iter, next_iter); - scroll_to(get_buffer()->get_insert()); - return true; + // When to delete '' or "" + else if (key->keyval == GDK_KEY_BackSpace) { + if (((*previous_iter == '\'' && *iter == '\'') || + (*previous_iter == '"' && *iter == '"')) && is_code_iter(previous_iter)) { + get_buffer()->erase(previous_iter, next_iter); + scroll_to(get_buffer()->get_insert()); + return true; + } } - } - - if(is_code_iter(iter)) { - // Insert () - if(key->keyval==GDK_KEY_parenleft && allow_insertion(iter)) { - if(symbol_count(iter, '(', ')')==0) { - get_buffer()->insert_at_cursor(")"); - iter=get_buffer()->get_insert()->get_iter(); - iter.backward_char(); - get_buffer()->place_cursor(iter); - get_buffer()->insert_at_cursor("("); - scroll_to(get_buffer()->get_insert()); + + if (is_code_iter(iter)) { + // Insert () + if (key->keyval == GDK_KEY_parenleft && allow_insertion(iter)) { + if (symbol_count(iter, '(', ')') == 0) { + get_buffer()->insert_at_cursor(")"); + iter = get_buffer()->get_insert()->get_iter(); + iter.backward_char(); + get_buffer()->place_cursor(iter); + get_buffer()->insert_at_cursor("("); + scroll_to(get_buffer()->get_insert()); + return true; + } + } + // Insert [] + else if (key->keyval == GDK_KEY_bracketleft && allow_insertion(iter)) { + if (symbol_count(iter, '[', ']') == 0) { + get_buffer()->insert_at_cursor("[]"); + auto iter = get_buffer()->get_insert()->get_iter(); + iter.backward_char(); + get_buffer()->place_cursor(iter); + scroll_to(get_buffer()->get_insert()); + return true; + } + } + // Move left on ] in [] + else if (key->keyval == GDK_KEY_bracketright) { + if (*iter == ']' && symbol_count(iter, '[', ']') == 0) { + iter.forward_char(); + get_buffer()->place_cursor(iter); + scroll_to(get_buffer()->get_insert()); + return true; + } + } + // Insert '' + else if (key->keyval == GDK_KEY_apostrophe && allow_insertion(iter) && symbol_count(iter, '\'', -1) % 2 == 0) { + get_buffer()->insert_at_cursor("''"); + auto iter = get_buffer()->get_insert()->get_iter(); + iter.backward_char(); + get_buffer()->place_cursor(iter); + scroll_to(get_buffer()->get_insert()); + return true; + } + // Insert "" + else if (key->keyval == GDK_KEY_quotedbl && allow_insertion(iter) && symbol_count(iter, '"', -1) % 2 == 0) { + get_buffer()->insert_at_cursor("\"\""); + auto iter = get_buffer()->get_insert()->get_iter(); + iter.backward_char(); + get_buffer()->place_cursor(iter); + scroll_to(get_buffer()->get_insert()); + return true; + } + // Insert ; at the end of line, if iter is at the last ) + else if (key->keyval == GDK_KEY_semicolon) { + if (*iter == ')' && symbol_count(iter, '(', ')') == 0) { + if (next_iter.ends_line()) { + Gtk::TextIter open_non_curly_bracket_iter; + if (find_open_non_curly_bracket_backward(previous_iter, open_non_curly_bracket_iter)) { + open_non_curly_bracket_iter.backward_char(); + if (*open_non_curly_bracket_iter == ' ') + open_non_curly_bracket_iter.backward_char(); + if (get_token(open_non_curly_bracket_iter) != "for") { + iter.forward_char(); + get_buffer()->place_cursor(iter); + get_buffer()->insert_at_cursor(";"); + scroll_to(get_buffer()->get_insert()); + return true; + } + } + } + } + } + // Delete () + else if (key->keyval == GDK_KEY_BackSpace) { + if (*previous_iter == '(' && *iter == ')' && symbol_count(iter, '(', ')') == 0) { + auto next_iter = iter; + next_iter.forward_char(); + get_buffer()->erase(previous_iter, next_iter); + scroll_to(get_buffer()->get_insert()); + return true; + } + // Delete [] + else if (*previous_iter == '[' && *iter == ']' && symbol_count(iter, '[', ']') == 0) { + auto next_iter = iter; + next_iter.forward_char(); + get_buffer()->erase(previous_iter, next_iter); + scroll_to(get_buffer()->get_insert()); + return true; + } + } + } + + return false; +} + +bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) { + if (!multiple_cursors_signals_set) { + multiple_cursors_signals_set = true; + multiple_cursors_last_insert = get_buffer()->create_mark(get_buffer()->get_insert()->get_iter(), false); + get_buffer()->signal_mark_set().connect( + [this](const Gtk::TextBuffer::iterator &iter, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { + for (auto &extra_cursor: multiple_cursors_extra_cursors) { + if (extra_cursor.first == mark && + (!iter.ends_line() || iter.get_line_offset() > extra_cursor.second)) { + extra_cursor.second = iter.get_line_offset(); + break; + } + } + + if (mark->get_name() == "insert") { + if (multiple_cursors_use) { + multiple_cursors_use = false; + auto offset_diff = mark->get_iter().get_offset() - + multiple_cursors_last_insert->get_iter().get_offset(); + if (offset_diff != 0) { + for (auto &extra_cursor: multiple_cursors_extra_cursors) { + auto iter = extra_cursor.first->get_iter(); + iter.forward_chars(offset_diff); + get_buffer()->move_mark(extra_cursor.first, iter); + } + } + multiple_cursors_use = true; + } + get_buffer()->delete_mark(multiple_cursors_last_insert); + multiple_cursors_last_insert = get_buffer()->create_mark(mark->get_iter(), false); + } + }); + + // TODO: this handler should run after signal_insert + get_buffer()->signal_insert().connect( + [this](const Gtk::TextBuffer::iterator &iter, const Glib::ustring &text, int bytes) { + if (multiple_cursors_use) { + multiple_cursors_use = false; + auto offset = iter.get_offset() - get_buffer()->get_insert()->get_iter().get_offset(); + for (auto &extra_cursor: multiple_cursors_extra_cursors) { + auto iter = extra_cursor.first->get_iter(); + iter.forward_chars(offset); + get_buffer()->insert(iter, text); + } + multiple_cursors_use = true; + } + }); + + get_buffer()->signal_erase().connect( + [this](const Gtk::TextBuffer::iterator &iter_start, const Gtk::TextBuffer::iterator &iter_end) { + if (multiple_cursors_use) { + auto insert_offset = get_buffer()->get_insert()->get_iter().get_offset(); + multiple_cursors_erase_backward_length = insert_offset - iter_start.get_offset(); + multiple_cursors_erase_forward_length = iter_end.get_offset() - insert_offset; + } + }, false); + + get_buffer()->signal_erase().connect( + [this](const Gtk::TextBuffer::iterator &iter_start, const Gtk::TextBuffer::iterator &iter_end) { + if (multiple_cursors_use) { + multiple_cursors_use = false; + for (auto &extra_cursor: multiple_cursors_extra_cursors) { + auto start_iter = extra_cursor.first->get_iter(); + auto end_iter = start_iter; + start_iter.backward_chars(multiple_cursors_erase_backward_length); + end_iter.forward_chars(multiple_cursors_erase_forward_length); + get_buffer()->erase(start_iter, end_iter); + } + multiple_cursors_use = true; + } + }); + } + + + if (key->keyval == GDK_KEY_Escape && !multiple_cursors_extra_cursors.empty()) { + for (auto &extra_cursor: multiple_cursors_extra_cursors) { + extra_cursor.first->set_visible(false); + get_buffer()->delete_mark(extra_cursor.first); + } + multiple_cursors_extra_cursors.clear(); return true; - } } - // Insert [] - else if(key->keyval==GDK_KEY_bracketleft && allow_insertion(iter)) { - if(symbol_count(iter, '[', ']')==0) { - get_buffer()->insert_at_cursor("[]"); - auto iter=get_buffer()->get_insert()->get_iter(); + + unsigned create_cursor_mask = GDK_MOD1_MASK; + unsigned move_last_created_cursor_mask = GDK_SHIFT_MASK | GDK_MOD1_MASK; + + // Move last created cursor + if ((key->keyval == GDK_KEY_Left || key->keyval == GDK_KEY_KP_Left) && + (key->state & move_last_created_cursor_mask) == move_last_created_cursor_mask) { + if (multiple_cursors_extra_cursors.empty()) + return false; + auto &cursor = multiple_cursors_extra_cursors.back().first; + auto iter = cursor->get_iter(); iter.backward_char(); - get_buffer()->place_cursor(iter); - scroll_to(get_buffer()->get_insert()); + get_buffer()->move_mark(cursor, iter); return true; - } } - // Move left on ] in [] - else if(key->keyval==GDK_KEY_bracketright) { - if(*iter==']' && symbol_count(iter, '[', ']')==0) { + if ((key->keyval == GDK_KEY_Right || key->keyval == GDK_KEY_KP_Right) && + (key->state & move_last_created_cursor_mask) == move_last_created_cursor_mask) { + if (multiple_cursors_extra_cursors.empty()) + return false; + auto &cursor = multiple_cursors_extra_cursors.back().first; + auto iter = cursor->get_iter(); iter.forward_char(); - get_buffer()->place_cursor(iter); - scroll_to(get_buffer()->get_insert()); + get_buffer()->move_mark(cursor, iter); return true; - } - } - // Insert '' - else if(key->keyval==GDK_KEY_apostrophe && allow_insertion(iter) && symbol_count(iter, '\'', -1)%2==0) { - get_buffer()->insert_at_cursor("''"); - auto iter=get_buffer()->get_insert()->get_iter(); - iter.backward_char(); - get_buffer()->place_cursor(iter); - scroll_to(get_buffer()->get_insert()); - return true; - } - // Insert "" - else if(key->keyval==GDK_KEY_quotedbl && allow_insertion(iter) && symbol_count(iter, '"', -1)%2==0) { - get_buffer()->insert_at_cursor("\"\""); - auto iter=get_buffer()->get_insert()->get_iter(); - iter.backward_char(); - get_buffer()->place_cursor(iter); - scroll_to(get_buffer()->get_insert()); - return true; - } - // Insert ; at the end of line, if iter is at the last ) - else if(key->keyval==GDK_KEY_semicolon) { - if(*iter==')' && symbol_count(iter, '(', ')')==0) { - if(next_iter.ends_line()) { - Gtk::TextIter open_non_curly_bracket_iter; - if(find_open_non_curly_bracket_backward(previous_iter, open_non_curly_bracket_iter)) { - open_non_curly_bracket_iter.backward_char(); - if(*open_non_curly_bracket_iter==' ') - open_non_curly_bracket_iter.backward_char(); - if(get_token(open_non_curly_bracket_iter)!="for") { - iter.forward_char(); - get_buffer()->place_cursor(iter); - get_buffer()->insert_at_cursor(";"); - scroll_to(get_buffer()->get_insert()); - return true; - } - } - } - } - } - // Delete () - else if(key->keyval==GDK_KEY_BackSpace) { - if(*previous_iter=='(' && *iter==')' && symbol_count(iter, '(', ')')==0) { - auto next_iter=iter; - next_iter.forward_char(); - get_buffer()->erase(previous_iter, next_iter); - scroll_to(get_buffer()->get_insert()); + } + if ((key->keyval == GDK_KEY_Up || key->keyval == GDK_KEY_KP_Up) && + (key->state & move_last_created_cursor_mask) == move_last_created_cursor_mask) { + if (multiple_cursors_extra_cursors.empty()) + return false; + auto &extra_cursor = multiple_cursors_extra_cursors.back(); + auto iter = extra_cursor.first->get_iter(); + auto line_offset = extra_cursor.second; + if (iter.backward_line()) { + auto end_line_iter = iter; + if (!end_line_iter.ends_line()) + end_line_iter.forward_to_line_end(); + iter.forward_chars(std::min(line_offset, end_line_iter.get_line_offset())); + get_buffer()->move_mark(extra_cursor.first, iter); + } return true; - } - // Delete [] - else if(*previous_iter=='[' && *iter==']' && symbol_count(iter, '[', ']')==0) { - auto next_iter=iter; - next_iter.forward_char(); - get_buffer()->erase(previous_iter, next_iter); - scroll_to(get_buffer()->get_insert()); + } + if ((key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down) && + (key->state & move_last_created_cursor_mask) == move_last_created_cursor_mask) { + if (multiple_cursors_extra_cursors.empty()) + return false; + auto &extra_cursor = multiple_cursors_extra_cursors.back(); + auto iter = extra_cursor.first->get_iter(); + auto line_offset = extra_cursor.second; + if (iter.forward_line()) { + auto end_line_iter = iter; + if (!end_line_iter.ends_line()) + end_line_iter.forward_to_line_end(); + iter.forward_chars(std::min(line_offset, end_line_iter.get_line_offset())); + get_buffer()->move_mark(extra_cursor.first, iter); + } return true; - } } - } - - return false; -} -bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) { - if(!multiple_cursors_signals_set) { - multiple_cursors_signals_set=true; - multiple_cursors_last_insert=get_buffer()->create_mark(get_buffer()->get_insert()->get_iter(), false); - get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator &iter, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { - for(auto &extra_cursor: multiple_cursors_extra_cursors) { - if(extra_cursor.first==mark && (!iter.ends_line() || iter.get_line_offset()>extra_cursor.second)) { - extra_cursor.second=iter.get_line_offset(); - break; - } - } - - if(mark->get_name()=="insert") { - if(multiple_cursors_use) { - multiple_cursors_use=false; - auto offset_diff=mark->get_iter().get_offset()-multiple_cursors_last_insert->get_iter().get_offset(); - if(offset_diff!=0) { - for(auto &extra_cursor: multiple_cursors_extra_cursors) { - auto iter=extra_cursor.first->get_iter(); - iter.forward_chars(offset_diff); - get_buffer()->move_mark(extra_cursor.first, iter); - } - } - multiple_cursors_use=true; - } - get_buffer()->delete_mark(multiple_cursors_last_insert); - multiple_cursors_last_insert=get_buffer()->create_mark(mark->get_iter(), false); - } - }); - - // TODO: this handler should run after signal_insert - get_buffer()->signal_insert().connect([this](const Gtk::TextBuffer::iterator &iter, const Glib::ustring &text, int bytes) { - if(multiple_cursors_use) { - multiple_cursors_use=false; - auto offset=iter.get_offset()-get_buffer()->get_insert()->get_iter().get_offset(); - for(auto &extra_cursor: multiple_cursors_extra_cursors) { - auto iter=extra_cursor.first->get_iter(); - iter.forward_chars(offset); - get_buffer()->insert(iter, text); - } - multiple_cursors_use=true; - } - }); - - get_buffer()->signal_erase().connect([this](const Gtk::TextBuffer::iterator &iter_start, const Gtk::TextBuffer::iterator &iter_end) { - if(multiple_cursors_use) { - auto insert_offset=get_buffer()->get_insert()->get_iter().get_offset(); - multiple_cursors_erase_backward_length=insert_offset-iter_start.get_offset(); - multiple_cursors_erase_forward_length=iter_end.get_offset()-insert_offset; - } - }, false); - - get_buffer()->signal_erase().connect([this](const Gtk::TextBuffer::iterator &iter_start, const Gtk::TextBuffer::iterator &iter_end) { - if(multiple_cursors_use) { - multiple_cursors_use=false; - for(auto &extra_cursor: multiple_cursors_extra_cursors) { - auto start_iter=extra_cursor.first->get_iter(); - auto end_iter=start_iter; - start_iter.backward_chars(multiple_cursors_erase_backward_length); - end_iter.forward_chars(multiple_cursors_erase_forward_length); - get_buffer()->erase(start_iter, end_iter); - } - multiple_cursors_use=true; - } - }); - } - - - if(key->keyval==GDK_KEY_Escape && !multiple_cursors_extra_cursors.empty()) { - for(auto &extra_cursor: multiple_cursors_extra_cursors) { - extra_cursor.first->set_visible(false); - get_buffer()->delete_mark(extra_cursor.first); - } - multiple_cursors_extra_cursors.clear(); - return true; - } - - unsigned create_cursor_mask=GDK_MOD1_MASK; - unsigned move_last_created_cursor_mask=GDK_SHIFT_MASK|GDK_MOD1_MASK; - - // Move last created cursor - if((key->keyval==GDK_KEY_Left || key->keyval==GDK_KEY_KP_Left) && (key->state&move_last_created_cursor_mask)==move_last_created_cursor_mask) { - if(multiple_cursors_extra_cursors.empty()) - return false; - auto &cursor=multiple_cursors_extra_cursors.back().first; - auto iter=cursor->get_iter(); - iter.backward_char(); - get_buffer()->move_mark(cursor, iter); - return true; - } - if((key->keyval==GDK_KEY_Right || key->keyval==GDK_KEY_KP_Right) && (key->state&move_last_created_cursor_mask)==move_last_created_cursor_mask) { - if(multiple_cursors_extra_cursors.empty()) - return false; - auto &cursor=multiple_cursors_extra_cursors.back().first; - auto iter=cursor->get_iter(); - iter.forward_char(); - get_buffer()->move_mark(cursor, iter); - return true; - } - if((key->keyval==GDK_KEY_Up || key->keyval==GDK_KEY_KP_Up) && (key->state&move_last_created_cursor_mask)==move_last_created_cursor_mask) { - if(multiple_cursors_extra_cursors.empty()) - return false; - auto &extra_cursor=multiple_cursors_extra_cursors.back(); - auto iter=extra_cursor.first->get_iter(); - auto line_offset=extra_cursor.second; - if(iter.backward_line()) { - auto end_line_iter=iter; - if(!end_line_iter.ends_line()) - end_line_iter.forward_to_line_end(); - iter.forward_chars(std::min(line_offset, end_line_iter.get_line_offset())); - get_buffer()->move_mark(extra_cursor.first, iter); + // Create extra cursor + if ((key->keyval == GDK_KEY_Up || key->keyval == GDK_KEY_KP_Up) && + (key->state & create_cursor_mask) == create_cursor_mask) { + auto insert_iter = get_buffer()->get_insert()->get_iter(); + auto insert_line_offset = insert_iter.get_line_offset(); + auto offset = insert_iter.get_offset(); + for (auto &extra_cursor: multiple_cursors_extra_cursors) + offset = std::min(offset, extra_cursor.first->get_iter().get_offset()); + auto iter = get_buffer()->get_iter_at_offset(offset); + if (iter.backward_line()) { + auto end_line_iter = iter; + if (!end_line_iter.ends_line()) + end_line_iter.forward_to_line_end(); + iter.forward_chars(std::min(insert_line_offset, end_line_iter.get_line_offset())); + multiple_cursors_extra_cursors.emplace_back(get_buffer()->create_mark(iter, false), insert_line_offset); + multiple_cursors_extra_cursors.back().first->set_visible(true); + } + return true; } - return true; - } - if((key->keyval==GDK_KEY_Down || key->keyval==GDK_KEY_KP_Down) && (key->state&move_last_created_cursor_mask)==move_last_created_cursor_mask) { - if(multiple_cursors_extra_cursors.empty()) - return false; - auto &extra_cursor=multiple_cursors_extra_cursors.back(); - auto iter=extra_cursor.first->get_iter(); - auto line_offset=extra_cursor.second; - if(iter.forward_line()) { - auto end_line_iter=iter; - if(!end_line_iter.ends_line()) - end_line_iter.forward_to_line_end(); - iter.forward_chars(std::min(line_offset, end_line_iter.get_line_offset())); - get_buffer()->move_mark(extra_cursor.first, iter); + if ((key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down) && + (key->state & create_cursor_mask) == create_cursor_mask) { + auto insert_iter = get_buffer()->get_insert()->get_iter(); + auto insert_line_offset = insert_iter.get_line_offset(); + auto offset = insert_iter.get_offset(); + for (auto &extra_cursor: multiple_cursors_extra_cursors) + offset = std::max(offset, extra_cursor.first->get_iter().get_offset()); + auto iter = get_buffer()->get_iter_at_offset(offset); + if (iter.forward_line()) { + auto end_line_iter = iter; + if (!end_line_iter.ends_line()) + end_line_iter.forward_to_line_end(); + iter.forward_chars(std::min(insert_line_offset, end_line_iter.get_line_offset())); + multiple_cursors_extra_cursors.emplace_back(get_buffer()->create_mark(iter, false), insert_line_offset); + multiple_cursors_extra_cursors.back().first->set_visible(true); + } + return true; } - return true; - } - - // Create extra cursor - if((key->keyval==GDK_KEY_Up || key->keyval==GDK_KEY_KP_Up) && (key->state&create_cursor_mask)==create_cursor_mask) { - auto insert_iter=get_buffer()->get_insert()->get_iter(); - auto insert_line_offset=insert_iter.get_line_offset(); - auto offset=insert_iter.get_offset(); - for(auto &extra_cursor: multiple_cursors_extra_cursors) - offset=std::min(offset, extra_cursor.first->get_iter().get_offset()); - auto iter=get_buffer()->get_iter_at_offset(offset); - if(iter.backward_line()) { - auto end_line_iter=iter; - if(!end_line_iter.ends_line()) - end_line_iter.forward_to_line_end(); - iter.forward_chars(std::min(insert_line_offset, end_line_iter.get_line_offset())); - multiple_cursors_extra_cursors.emplace_back(get_buffer()->create_mark(iter, false), insert_line_offset); - multiple_cursors_extra_cursors.back().first->set_visible(true); + + // Move cursors left/right + if ((key->keyval == GDK_KEY_Left || key->keyval == GDK_KEY_KP_Left) && (key->state & GDK_CONTROL_MASK) > 0) { + multiple_cursors_use = false; + for (auto &extra_cursor: multiple_cursors_extra_cursors) { + auto iter = extra_cursor.first->get_iter(); + iter.backward_word_start(); + extra_cursor.second = iter.get_line_offset(); + get_buffer()->move_mark(extra_cursor.first, iter); + } + auto insert = get_buffer()->get_insert(); + auto iter = insert->get_iter(); + iter.backward_word_start(); + get_buffer()->move_mark(insert, iter); + if ((key->state & GDK_SHIFT_MASK) == 0) + get_buffer()->move_mark_by_name("selection_bound", iter); + return true; } - return true; - } - if((key->keyval==GDK_KEY_Down || key->keyval==GDK_KEY_KP_Down) && (key->state&create_cursor_mask)==create_cursor_mask) { - auto insert_iter=get_buffer()->get_insert()->get_iter(); - auto insert_line_offset=insert_iter.get_line_offset(); - auto offset=insert_iter.get_offset(); - for(auto &extra_cursor: multiple_cursors_extra_cursors) - offset=std::max(offset, extra_cursor.first->get_iter().get_offset()); - auto iter=get_buffer()->get_iter_at_offset(offset); - if(iter.forward_line()) { - auto end_line_iter=iter; - if(!end_line_iter.ends_line()) - end_line_iter.forward_to_line_end(); - iter.forward_chars(std::min(insert_line_offset, end_line_iter.get_line_offset())); - multiple_cursors_extra_cursors.emplace_back(get_buffer()->create_mark(iter, false), insert_line_offset); - multiple_cursors_extra_cursors.back().first->set_visible(true); + if ((key->keyval == GDK_KEY_Right || key->keyval == GDK_KEY_KP_Right) && (key->state & GDK_CONTROL_MASK) > 0) { + multiple_cursors_use = false; + for (auto &extra_cursor: multiple_cursors_extra_cursors) { + auto iter = extra_cursor.first->get_iter(); + iter.forward_visible_word_end(); + extra_cursor.second = iter.get_line_offset(); + get_buffer()->move_mark(extra_cursor.first, iter); + } + auto insert = get_buffer()->get_insert(); + auto iter = insert->get_iter(); + iter.forward_visible_word_end(); + get_buffer()->move_mark(insert, iter); + if ((key->state & GDK_SHIFT_MASK) == 0) + get_buffer()->move_mark_by_name("selection_bound", iter); + return true; } - return true; - } - - // Move cursors left/right - if((key->keyval==GDK_KEY_Left || key->keyval==GDK_KEY_KP_Left) && (key->state&GDK_CONTROL_MASK)>0) { - multiple_cursors_use=false; - for(auto &extra_cursor: multiple_cursors_extra_cursors) { - auto iter=extra_cursor.first->get_iter(); - iter.backward_word_start(); - extra_cursor.second=iter.get_line_offset(); - get_buffer()->move_mark(extra_cursor.first, iter); - } - auto insert=get_buffer()->get_insert(); - auto iter=insert->get_iter(); - iter.backward_word_start(); - get_buffer()->move_mark(insert, iter); - if((key->state&GDK_SHIFT_MASK)==0) - get_buffer()->move_mark_by_name("selection_bound", iter); - return true; - } - if((key->keyval==GDK_KEY_Right || key->keyval==GDK_KEY_KP_Right) && (key->state&GDK_CONTROL_MASK)>0) { - multiple_cursors_use=false; - for(auto &extra_cursor: multiple_cursors_extra_cursors) { - auto iter=extra_cursor.first->get_iter(); - iter.forward_visible_word_end(); - extra_cursor.second=iter.get_line_offset(); - get_buffer()->move_mark(extra_cursor.first, iter); - } - auto insert=get_buffer()->get_insert(); - auto iter=insert->get_iter(); - iter.forward_visible_word_end(); - get_buffer()->move_mark(insert, iter); - if((key->state&GDK_SHIFT_MASK)==0) - get_buffer()->move_mark_by_name("selection_bound", iter); - return true; - } - - // Move cursors up/down - if((key->keyval==GDK_KEY_Up || key->keyval==GDK_KEY_KP_Up)) { - multiple_cursors_use=false; - for(auto &extra_cursor: multiple_cursors_extra_cursors) { - auto iter=extra_cursor.first->get_iter(); - auto line_offset=extra_cursor.second; - if(iter.backward_line()) { - auto end_line_iter=iter; - if(!end_line_iter.ends_line()) - end_line_iter.forward_to_line_end(); - iter.forward_chars(std::min(line_offset, end_line_iter.get_line_offset())); - get_buffer()->move_mark(extra_cursor.first, iter); - } + + // Move cursors up/down + if ((key->keyval == GDK_KEY_Up || key->keyval == GDK_KEY_KP_Up)) { + multiple_cursors_use = false; + for (auto &extra_cursor: multiple_cursors_extra_cursors) { + auto iter = extra_cursor.first->get_iter(); + auto line_offset = extra_cursor.second; + if (iter.backward_line()) { + auto end_line_iter = iter; + if (!end_line_iter.ends_line()) + end_line_iter.forward_to_line_end(); + iter.forward_chars(std::min(line_offset, end_line_iter.get_line_offset())); + get_buffer()->move_mark(extra_cursor.first, iter); + } + } + return false; } - return false; - } - if((key->keyval==GDK_KEY_Down || key->keyval==GDK_KEY_KP_Down)) { - multiple_cursors_use=false; - for(auto &extra_cursor: multiple_cursors_extra_cursors) { - auto iter=extra_cursor.first->get_iter(); - auto line_offset=extra_cursor.second; - if(iter.forward_line()) { - auto end_line_iter=iter; - if(!end_line_iter.ends_line()) - end_line_iter.forward_to_line_end(); - iter.forward_chars(std::min(line_offset, end_line_iter.get_line_offset())); - get_buffer()->move_mark(extra_cursor.first, iter); - } + if ((key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down)) { + multiple_cursors_use = false; + for (auto &extra_cursor: multiple_cursors_extra_cursors) { + auto iter = extra_cursor.first->get_iter(); + auto line_offset = extra_cursor.second; + if (iter.forward_line()) { + auto end_line_iter = iter; + if (!end_line_iter.ends_line()) + end_line_iter.forward_to_line_end(); + iter.forward_chars(std::min(line_offset, end_line_iter.get_line_offset())); + get_buffer()->move_mark(extra_cursor.first, iter); + } + } + return false; } + + // Smart Home-key, start of line + if ((key->keyval == GDK_KEY_Home || key->keyval == GDK_KEY_KP_Home) && (key->state & GDK_CONTROL_MASK) == 0) { + for (auto &extra_cursor: multiple_cursors_extra_cursors) + get_buffer()->move_mark(extra_cursor.first, get_smart_home_iter(extra_cursor.first->get_iter())); + multiple_cursors_use = false; + return false; + } + // Smart End-key, end of line + if ((key->keyval == GDK_KEY_End || key->keyval == GDK_KEY_KP_End) && (key->state & GDK_CONTROL_MASK) == 0) { + for (auto &extra_cursor: multiple_cursors_extra_cursors) + get_buffer()->move_mark(extra_cursor.first, get_smart_end_iter(extra_cursor.first->get_iter())); + multiple_cursors_use = false; + return false; + } + return false; - } - - // Smart Home-key, start of line - if((key->keyval==GDK_KEY_Home || key->keyval==GDK_KEY_KP_Home) && (key->state&GDK_CONTROL_MASK)==0) { - for(auto &extra_cursor: multiple_cursors_extra_cursors) - get_buffer()->move_mark(extra_cursor.first, get_smart_home_iter(extra_cursor.first->get_iter())); - multiple_cursors_use=false; - return false; - } - // Smart End-key, end of line - if((key->keyval==GDK_KEY_End || key->keyval==GDK_KEY_KP_End) && (key->state&GDK_CONTROL_MASK)==0) { - for(auto &extra_cursor: multiple_cursors_extra_cursors) - get_buffer()->move_mark(extra_cursor.first, get_smart_end_iter(extra_cursor.first->get_iter())); - multiple_cursors_use=false; - return false; - } - - return false; } bool Source::View::on_button_press_event(GdkEventButton *event) { - // Select range when double clicking - if(event->type==GDK_2BUTTON_PRESS) { - Gtk::TextIter start, end; - get_buffer()->get_selection_bounds(start, end); - auto iter=start; - while((*iter>=48 && *iter<=57) || (*iter>=65 && *iter<=90) || (*iter>=97 && *iter<=122) || *iter==95) { - start=iter; - if(!iter.backward_char()) - break; - } - while((*end>=48 && *end<=57) || (*end>=65 && *end<=90) || (*end>=97 && *end<=122) || *end==95) { - if(!end.forward_char()) - break; - } - get_buffer()->select_range(start, end); - return true; - } + // Select range when double clicking + if (event->type == GDK_2BUTTON_PRESS) { + Gtk::TextIter start, end; + get_buffer()->get_selection_bounds(start, end); + auto iter = start; + while ((*iter >= 48 && *iter <= 57) || (*iter >= 65 && *iter <= 90) || (*iter >= 97 && *iter <= 122) || + *iter == 95) { + start = iter; + if (!iter.backward_char()) + break; + } + while ((*end >= 48 && *end <= 57) || (*end >= 65 && *end <= 90) || (*end >= 97 && *end <= 122) || *end == 95) { + if (!end.forward_char()) + break; + } + get_buffer()->select_range(start, end); + return true; + } - // Go to implementation or declaration - if((event->type == GDK_BUTTON_PRESS) && (event->button == 1)){ + // Go to implementation or declaration + if ((event->type == GDK_BUTTON_PRESS) && (event->button == 1)) { #ifdef __APPLE__ - GdkModifierType mask=GDK_MOD2_MASK; + GdkModifierType mask=GDK_MOD2_MASK; #else - GdkModifierType mask=GDK_CONTROL_MASK; + GdkModifierType mask = GDK_CONTROL_MASK; #endif - if(event->state & mask) { - int x, y; - window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, event->x, event->y, x, y); - Gtk::TextIter iter; - get_iter_at_location(iter, x, y); - if(iter) - get_buffer()->place_cursor(iter); - - Menu::get().actions["source_goto_declaration_or_implementation"]->activate(); - return true; + if (event->state & mask) { + int x, y; + window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, event->x, event->y, x, y); + Gtk::TextIter iter; + get_iter_at_location(iter, x, y); + if (iter) + get_buffer()->place_cursor(iter); + + Menu::get().actions["source_goto_declaration_or_implementation"]->activate(); + return true; + } } - } - // Open right click menu - if((event->type == GDK_BUTTON_PRESS) && (event->button == 3)){ - hide_tooltips(); - if(!get_buffer()->get_has_selection()){ - int x,y; - window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT,event->x,event->y,x,y); - Gtk::TextIter iter; - get_iter_at_location(iter,x,y); - if(iter) - get_buffer()->place_cursor(iter); - Menu::get().right_click_line_menu->popup(event->button,event->time); - } else { - Menu::get().right_click_selected_menu->popup(event->button,event->time); + // Open right click menu + if ((event->type == GDK_BUTTON_PRESS) && (event->button == 3)) { + hide_tooltips(); + if (!get_buffer()->get_has_selection()) { + int x, y; + window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, event->x, event->y, x, y); + Gtk::TextIter iter; + get_iter_at_location(iter, x, y); + if (iter) + get_buffer()->place_cursor(iter); + Menu::get().right_click_line_menu->popup(event->button, event->time); + } else { + Menu::get().right_click_selected_menu->popup(event->button, event->time); + } + return true; } - return true; - } - return Gsv::View::on_button_press_event(event); + return Gsv::View::on_button_press_event(event); } bool Source::View::on_motion_notify_event(GdkEventMotion *event) { - // Workaround for drag-and-drop crash on MacOS - // TODO 2018: check if this bug has been fixed + // Workaround for drag-and-drop crash on MacOS + // TODO 2018: check if this bug has been fixed #ifdef __APPLE__ - if((event->state & GDK_BUTTON1_MASK) == 0) + if((event->state & GDK_BUTTON1_MASK) == 0) return Gsv::View::on_motion_notify_event(event); else { int x, y; @@ -2727,241 +2745,241 @@ bool Source::View::on_motion_notify_event(GdkEventMotion *event) { return true; } #else - return Gsv::View::on_motion_notify_event(event); + return Gsv::View::on_motion_notify_event(event); #endif } std::pair<char, unsigned> Source::View::find_tab_char_and_size() { - std::unordered_map<char, size_t> tab_chars; - std::unordered_map<unsigned, size_t> tab_sizes; - auto iter=get_buffer()->begin(); - long tab_count=-1; - long last_tab_count=0; - bool single_quoted=false; - bool double_quoted=false; - //For bracket languages, TODO: add more language ids - if(is_bracket_language && !(language && language->get_id()=="html")) { - bool line_comment=false; - bool comment=false; - bool bracket_last_line=false; - char last_char=0; - long last_tab_diff=-1; - while(iter) { - if(iter.starts_line()) { - line_comment=false; - single_quoted=false; - double_quoted=false; - tab_count=0; - if(last_char=='{') - bracket_last_line=true; - else - bracket_last_line=false; - } - if(bracket_last_line && tab_count!=-1) { - if(*iter==' ') { - tab_chars[' ']++; - tab_count++; + std::unordered_map<char, size_t> tab_chars; + std::unordered_map<unsigned, size_t> tab_sizes; + auto iter = get_buffer()->begin(); + long tab_count = -1; + long last_tab_count = 0; + bool single_quoted = false; + bool double_quoted = false; + //For bracket languages, TODO: add more language ids + if (is_bracket_language && !(language && language->get_id() == "html")) { + bool line_comment = false; + bool comment = false; + bool bracket_last_line = false; + char last_char = 0; + long last_tab_diff = -1; + while (iter) { + if (iter.starts_line()) { + line_comment = false; + single_quoted = false; + double_quoted = false; + tab_count = 0; + if (last_char == '{') + bracket_last_line = true; + else + bracket_last_line = false; + } + if (bracket_last_line && tab_count != -1) { + if (*iter == ' ') { + tab_chars[' ']++; + tab_count++; + } else if (*iter == '\t') { + tab_chars['\t']++; + tab_count++; + } else { + auto line_iter = iter; + char last_line_char = 0; + while (line_iter && !line_iter.ends_line()) { + if (*line_iter != ' ' && *line_iter != '\t') + last_line_char = *line_iter; + if (*line_iter == '(') + break; + line_iter.forward_char(); + } + if (last_line_char == ':' || *iter == '#') { + tab_count = 0; + if ((iter.get_line() + 1) < get_buffer()->get_line_count()) { + iter = get_buffer()->get_iter_at_line(iter.get_line() + 1); + continue; + } + } else if (!iter.ends_line()) { + if (tab_count != last_tab_count) + tab_sizes[std::abs(tab_count - last_tab_count)]++; + last_tab_diff = std::abs(tab_count - last_tab_count); + last_tab_count = tab_count; + last_char = 0; + } + } + } + + auto prev_iter = iter; + prev_iter.backward_char(); + auto prev_prev_iter = prev_iter; + prev_prev_iter.backward_char(); + if (!double_quoted && *iter == '\'' && !(*prev_iter == '\\' && *prev_prev_iter != '\\')) + single_quoted = !single_quoted; + else if (!single_quoted && *iter == '\"' && !(*prev_iter == '\\' && *prev_prev_iter != '\\')) + double_quoted = !double_quoted; + else if (!single_quoted && !double_quoted) { + auto next_iter = iter; + next_iter.forward_char(); + if (*iter == '/' && *next_iter == '/') + line_comment = true; + else if (*iter == '/' && *next_iter == '*') + comment = true; + else if (*iter == '*' && *next_iter == '/') { + iter.forward_char(); + iter.forward_char(); + comment = false; + } + } + if (!single_quoted && !double_quoted && !comment && !line_comment && *iter != ' ' && *iter != '\t' && + !iter.ends_line()) + last_char = *iter; + if (!single_quoted && !double_quoted && !comment && !line_comment && *iter == '}' && tab_count != -1 && + last_tab_diff != -1) + last_tab_count -= last_tab_diff; + if (*iter != ' ' && *iter != '\t') + tab_count = -1; + + iter.forward_char(); } - else if(*iter=='\t') { - tab_chars['\t']++; - tab_count++; + } else { + long para_count = 0; + while (iter) { + if (iter.starts_line()) + tab_count = 0; + if (tab_count != -1 && para_count == 0 && single_quoted == false && double_quoted == false) { + if (*iter == ' ') { + tab_chars[' ']++; + tab_count++; + } else if (*iter == '\t') { + tab_chars['\t']++; + tab_count++; + } else if (!iter.ends_line()) { + if (tab_count != last_tab_count) + tab_sizes[std::abs(tab_count - last_tab_count)]++; + last_tab_count = tab_count; + } + } + auto prev_iter = iter; + prev_iter.backward_char(); + auto prev_prev_iter = prev_iter; + prev_prev_iter.backward_char(); + if (!double_quoted && *iter == '\'' && !(*prev_iter == '\\' && *prev_prev_iter != '\\')) + single_quoted = !single_quoted; + else if (!single_quoted && *iter == '\"' && !(*prev_iter == '\\' && *prev_prev_iter != '\\')) + double_quoted = !double_quoted; + else if (!single_quoted && !double_quoted) { + if (*iter == '(') + para_count++; + else if (*iter == ')') + para_count--; + } + if (*iter != ' ' && *iter != '\t') + tab_count = -1; + + iter.forward_char(); } - else { - auto line_iter=iter; - char last_line_char=0; - while(line_iter && !line_iter.ends_line()) { - if(*line_iter!=' ' && *line_iter!='\t') - last_line_char=*line_iter; - if(*line_iter=='(') - break; - line_iter.forward_char(); - } - if(last_line_char==':' || *iter=='#') { - tab_count=0; - if((iter.get_line()+1) < get_buffer()->get_line_count()) { - iter=get_buffer()->get_iter_at_line(iter.get_line()+1); - continue; - } - } - else if(!iter.ends_line()) { - if(tab_count!=last_tab_count) - tab_sizes[std::abs(tab_count-last_tab_count)]++; - last_tab_diff=std::abs(tab_count-last_tab_count); - last_tab_count=tab_count; - last_char=0; - } - } - } - - auto prev_iter=iter; - prev_iter.backward_char(); - auto prev_prev_iter=prev_iter; - prev_prev_iter.backward_char(); - if(!double_quoted && *iter=='\'' && !(*prev_iter=='\\' && *prev_prev_iter!='\\')) - single_quoted=!single_quoted; - else if(!single_quoted && *iter=='\"' && !(*prev_iter=='\\' && *prev_prev_iter!='\\')) - double_quoted=!double_quoted; - else if(!single_quoted && !double_quoted) { - auto next_iter=iter; - next_iter.forward_char(); - if(*iter=='/' && *next_iter=='/') - line_comment=true; - else if(*iter=='/' && *next_iter=='*') - comment=true; - else if(*iter=='*' && *next_iter=='/') { - iter.forward_char(); - iter.forward_char(); - comment=false; - } - } - if(!single_quoted && !double_quoted && !comment && !line_comment && *iter!=' ' && *iter!='\t' && !iter.ends_line()) - last_char=*iter; - if(!single_quoted && !double_quoted && !comment && !line_comment && *iter=='}' && tab_count!=-1 && last_tab_diff!=-1) - last_tab_count-=last_tab_diff; - if(*iter!=' ' && *iter!='\t') - tab_count=-1; - - iter.forward_char(); - } - } - else { - long para_count=0; - while(iter) { - if(iter.starts_line()) - tab_count=0; - if(tab_count!=-1 && para_count==0 && single_quoted==false && double_quoted==false) { - if(*iter==' ') { - tab_chars[' ']++; - tab_count++; - } - else if(*iter=='\t') { - tab_chars['\t']++; - tab_count++; - } - else if(!iter.ends_line()) { - if(tab_count!=last_tab_count) - tab_sizes[std::abs(tab_count-last_tab_count)]++; - last_tab_count=tab_count; - } - } - auto prev_iter=iter; - prev_iter.backward_char(); - auto prev_prev_iter=prev_iter; - prev_prev_iter.backward_char(); - if(!double_quoted && *iter=='\'' && !(*prev_iter=='\\' && *prev_prev_iter!='\\')) - single_quoted=!single_quoted; - else if(!single_quoted && *iter=='\"' && !(*prev_iter=='\\' && *prev_prev_iter!='\\')) - double_quoted=!double_quoted; - else if(!single_quoted && !double_quoted) { - if(*iter=='(') - para_count++; - else if(*iter==')') - para_count--; - } - if(*iter!=' ' && *iter!='\t') - tab_count=-1; - - iter.forward_char(); } - } - char found_tab_char=0; - size_t occurences=0; - for(auto &tab_char: tab_chars) { - if(tab_char.second>occurences) { - found_tab_char=tab_char.first; - occurences=tab_char.second; + char found_tab_char = 0; + size_t occurences = 0; + for (auto &tab_char: tab_chars) { + if (tab_char.second > occurences) { + found_tab_char = tab_char.first; + occurences = tab_char.second; + } } - } - unsigned found_tab_size=0; - occurences=0; - for(auto &tab_size: tab_sizes) { - if(tab_size.second>occurences) { - found_tab_size=tab_size.first; - occurences=tab_size.second; + unsigned found_tab_size = 0; + occurences = 0; + for (auto &tab_size: tab_sizes) { + if (tab_size.second > occurences) { + found_tab_size = tab_size.first; + occurences = tab_size.second; + } } - } - return {found_tab_char, found_tab_size}; + return {found_tab_char, found_tab_size}; } ///////////////////// //// GenericView //// ///////////////////// -Source::GenericView::GenericView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language) : BaseView(file_path, language), View(file_path, language, true) { - configure(); - spellcheck_all=true; - - if(language) - get_source_buffer()->set_language(language); - - auto completion=get_completion(); - completion->property_show_headers()=false; - completion->property_show_icons()=false; - completion->property_accelerators()=0; - - auto completion_words=Gsv::CompletionWords::create("", Glib::RefPtr<Gdk::Pixbuf>()); - completion_words->register_provider(get_buffer()); - completion->add_provider(completion_words); - - if(language) { - auto language_manager=LanguageManager::get_default(); - auto search_paths=language_manager->get_search_path(); - bool found_language_file=false; - boost::filesystem::path language_file; - for(auto &search_path: search_paths) { - boost::filesystem::path p(static_cast<std::string>(search_path)+'/'+static_cast<std::string>(language->get_id())+".lang"); - if(boost::filesystem::exists(p) && boost::filesystem::is_regular_file(p)) { - language_file=p; - found_language_file=true; - break; - } - } - if(found_language_file) { - auto completion_buffer_keywords=CompletionBuffer::create(); - boost::property_tree::ptree pt; - try { - boost::property_tree::xml_parser::read_xml(language_file.string(), pt); - } - catch(const std::exception &e) { - Terminal::get().print("Error: error parsing language file "+language_file.string()+": "+e.what()+'\n', true); - } - bool has_context_class=false; - parse_language_file(completion_buffer_keywords, has_context_class, pt); - if(!has_context_class) - spellcheck_all=false; - completion_words->register_provider(completion_buffer_keywords); +Source::GenericView::GenericView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language) + : BaseView(file_path, language), View(file_path, language, true) { + configure(); + spellcheck_all = true; + + if (language) + get_source_buffer()->set_language(language); + + auto completion = get_completion(); + completion->property_show_headers() = false; + completion->property_show_icons() = false; + completion->property_accelerators() = 0; + + auto completion_words = Gsv::CompletionWords::create("", Glib::RefPtr<Gdk::Pixbuf>()); + completion_words->register_provider(get_buffer()); + completion->add_provider(completion_words); + + if (language) { + auto language_manager = LanguageManager::get_default(); + auto search_paths = language_manager->get_search_path(); + bool found_language_file = false; + boost::filesystem::path language_file; + for (auto &search_path: search_paths) { + boost::filesystem::path p( + static_cast<std::string>(search_path) + '/' + static_cast<std::string>(language->get_id()) + + ".lang"); + if (boost::filesystem::exists(p) && boost::filesystem::is_regular_file(p)) { + language_file = p; + found_language_file = true; + break; + } + } + if (found_language_file) { + auto completion_buffer_keywords = CompletionBuffer::create(); + boost::property_tree::ptree pt; + try { + boost::property_tree::xml_parser::read_xml(language_file.string(), pt); + } + catch (const std::exception &e) { + Terminal::get().print( + "Error: error parsing language file " + language_file.string() + ": " + e.what() + '\n', true); + } + bool has_context_class = false; + parse_language_file(completion_buffer_keywords, has_context_class, pt); + if (!has_context_class) + spellcheck_all = false; + completion_words->register_provider(completion_buffer_keywords); + } } - } } -void Source::GenericView::parse_language_file(Glib::RefPtr<CompletionBuffer> &completion_buffer, bool &has_context_class, const boost::property_tree::ptree &pt) { - bool case_insensitive=false; - for(auto &node: pt) { - if(node.first=="<xmlcomment>") { - auto data=static_cast<std::string>(node.second.data()); - std::transform(data.begin(), data.end(), data.begin(), ::tolower); - if(data.find("case insensitive")!=std::string::npos) - case_insensitive=true; - } - else if(node.first=="keyword") { - auto data=static_cast<std::string>(node.second.data()); - completion_buffer->insert_at_cursor(data+'\n'); - if(case_insensitive) { - std::transform(data.begin(), data.end(), data.begin(), ::tolower); - completion_buffer->insert_at_cursor(data+'\n'); - } - } - else if(!has_context_class && node.first=="context") { - auto class_attribute=node.second.get<std::string>("<xmlattr>.class", ""); - auto class_disabled_attribute=node.second.get<std::string>("<xmlattr>.class-disabled", ""); - if(class_attribute.size()>0 || class_disabled_attribute.size()>0) - has_context_class=true; - } - try { - parse_language_file(completion_buffer, has_context_class, node.second); - } - catch(const std::exception &e) { +void +Source::GenericView::parse_language_file(Glib::RefPtr<CompletionBuffer> &completion_buffer, bool &has_context_class, + const boost::property_tree::ptree &pt) { + bool case_insensitive = false; + for (auto &node: pt) { + if (node.first == "<xmlcomment>") { + auto data = static_cast<std::string>(node.second.data()); + std::transform(data.begin(), data.end(), data.begin(), ::tolower); + if (data.find("case insensitive") != std::string::npos) + case_insensitive = true; + } else if (node.first == "keyword") { + auto data = static_cast<std::string>(node.second.data()); + completion_buffer->insert_at_cursor(data + '\n'); + if (case_insensitive) { + std::transform(data.begin(), data.end(), data.begin(), ::tolower); + completion_buffer->insert_at_cursor(data + '\n'); + } + } else if (!has_context_class && node.first == "context") { + auto class_attribute = node.second.get<std::string>("<xmlattr>.class", ""); + auto class_disabled_attribute = node.second.get<std::string>("<xmlattr>.class-disabled", ""); + if (class_attribute.size() > 0 || class_disabled_attribute.size() > 0) + has_context_class = true; + } + try { + parse_language_file(completion_buffer, has_context_class, node.second); + } + catch (const std::exception &e) { + } } - } } diff --git a/src/source.h b/src/source.h index 10b5153e..48d52d2e 100644 --- a/src/source.h +++ b/src/source.h @@ -1,4 +1,5 @@ #pragma once + #include "source_spellcheck.h" #include "source_diff.h" #include "tooltips.h" @@ -10,167 +11,213 @@ #include <tuple> namespace Source { - /// Workaround for buggy Gsv::LanguageManager::get_default() - class LanguageManager { - public: - static Glib::RefPtr<Gsv::LanguageManager> get_default(); - }; - /// Workaround for buggy Gsv::StyleSchemeManager::get_default() - class StyleSchemeManager { - public: - static Glib::RefPtr<Gsv::StyleSchemeManager> get_default(); - }; - - Glib::RefPtr<Gsv::Language> guess_language(const boost::filesystem::path &file_path); - - class Offset { - public: - Offset() {} - Offset(unsigned line, unsigned index, const boost::filesystem::path &file_path=""): line(line), index(index), file_path(file_path) {} - operator bool() { return !file_path.empty(); } - bool operator==(const Offset &o) {return (line==o.line && index==o.index);} - - unsigned line; - unsigned index; - boost::filesystem::path file_path; - }; - - class FixIt { - public: - enum class Type {INSERT, REPLACE, ERASE}; - - FixIt(const std::string &source, const std::pair<Offset, Offset> &offsets); - - std::string string(Glib::RefPtr<Gtk::TextBuffer> buffer); - - Type type; - std::string source; - std::pair<Offset, Offset> offsets; - }; - - class View : public SpellCheckView, public DiffView { - public: - static std::unordered_set<View*> non_deleted_views; - static std::unordered_set<View*> views; - - View(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language, bool is_generic_view=false); - ~View(); - - bool save() override; - - void configure() override; - - void search_highlight(const std::string &text, bool case_sensitive, bool regex); - std::function<void(int number)> update_search_occurrences; - void search_forward(); - void search_backward(); - void replace_forward(const std::string &replacement); - void replace_backward(const std::string &replacement); - void replace_all(const std::string &replacement); - - void paste(); - - std::function<void()> non_interactive_completion; - std::function<void(bool)> format_style; - std::function<Offset()> get_declaration_location; - std::function<Offset()> get_type_declaration_location; - std::function<std::vector<Offset>()> get_implementation_locations; - std::function<std::vector<Offset>()> get_declaration_or_implementation_locations; - std::function<std::vector<std::pair<Offset, std::string> >()> get_usages; - std::function<std::string()> get_method; - std::function<std::vector<std::pair<Offset, std::string> >()> get_methods; - std::function<std::vector<std::string>()> get_token_data; - std::function<std::string()> get_token_spelling; - std::function<void(const std::string &text)> rename_similar_tokens; - std::function<void()> goto_next_diagnostic; - std::function<std::vector<FixIt>()> get_fix_its; - std::function<void()> toggle_comments; - std::function<std::tuple<Source::Offset, std::string, size_t>()> get_documentation_template; - std::function<void(int)> toggle_breakpoint; - - void hide_tooltips() override; - void hide_dialogs() override; - - void set_tab_char_and_size(char tab_char, unsigned tab_size); - std::pair<char, unsigned> get_tab_char_and_size() {return {tab_char, tab_size};} - - bool soft_reparse_needed=false; - bool full_reparse_needed=false; - virtual void soft_reparse(bool delayed=false) {soft_reparse_needed=false;} - virtual void full_reparse() {full_reparse_needed=false;} - protected: - bool parsed=true; - Tooltips diagnostic_tooltips; - Tooltips type_tooltips; - sigc::connection delayed_tooltips_connection; - sigc::connection delayed_tag_similar_symbols_connection; - virtual void show_diagnostic_tooltips(const Gdk::Rectangle &rectangle) { diagnostic_tooltips.show(rectangle); } - void add_diagnostic_tooltip(const Gtk::TextIter &start, const Gtk::TextIter &end, std::string spelling, bool error); - void clear_diagnostic_tooltips(); - virtual void show_type_tooltips(const Gdk::Rectangle &rectangle) {} - gdouble on_motion_last_x=0.0; - gdouble on_motion_last_y=0.0; - - /// Usually returns at start of line, but not always - Gtk::TextIter find_start_of_sentence(Gtk::TextIter iter); - bool find_open_non_curly_bracket_backward(Gtk::TextIter iter, Gtk::TextIter &found_iter); - bool find_open_curly_bracket_backward(Gtk::TextIter iter, Gtk::TextIter &found_iter); - bool find_close_curly_bracket_forward(Gtk::TextIter iter, Gtk::TextIter &found_iter); - long symbol_count(Gtk::TextIter iter, unsigned int positive_char, unsigned int negative_char); - bool is_templated_function(Gtk::TextIter iter, Gtk::TextIter &parenthesis_end_iter); - - std::string get_token(Gtk::TextIter iter); - - void cleanup_whitespace_characters_on_return(const Gtk::TextIter &iter); - - bool is_bracket_language=false; - bool on_key_press_event(GdkEventKey* key) override; - bool on_key_press_event_basic(GdkEventKey* key); - bool on_key_press_event_bracket_language(GdkEventKey* key); - bool on_key_press_event_smart_brackets(GdkEventKey* key); - bool on_key_press_event_smart_inserts(GdkEventKey* key); - bool on_button_press_event(GdkEventButton *event) override; - bool on_motion_notify_event(GdkEventMotion *motion_event) override; - - std::pair<char, unsigned> find_tab_char_and_size(); - unsigned tab_size; - char tab_char; - std::string tab; - - bool interactive_completion=true; - - guint previous_non_modifier_keyval=0; - private: - void setup_tooltip_and_dialog_events(); - void setup_format_style(bool is_generic_view); - - void cleanup_whitespace_characters(); - Gsv::DrawSpacesFlags parse_show_whitespace_characters(const std::string &text); - - GtkSourceSearchContext *search_context; - GtkSourceSearchSettings *search_settings; - static void search_occurrences_updated(GtkWidget* widget, GParamSpec* property, gpointer data); - - sigc::connection renderer_activate_connection; - - bool multiple_cursors_signals_set=false; - bool multiple_cursors_use=false; - std::vector<std::pair<Glib::RefPtr<Gtk::TextBuffer::Mark>, int>> multiple_cursors_extra_cursors; - Glib::RefPtr<Gtk::TextBuffer::Mark> multiple_cursors_last_insert; - int multiple_cursors_erase_backward_length; - int multiple_cursors_erase_forward_length; - bool on_key_press_event_multiple_cursors(GdkEventKey* key); - }; - - class GenericView : public View { - private: - class CompletionBuffer : public Gtk::TextBuffer { + /// Workaround for buggy Gsv::LanguageManager::get_default() + class LanguageManager { + public: + static Glib::RefPtr<Gsv::LanguageManager> get_default(); + }; + + /// Workaround for buggy Gsv::StyleSchemeManager::get_default() + class StyleSchemeManager { + public: + static Glib::RefPtr<Gsv::StyleSchemeManager> get_default(); + }; + + Glib::RefPtr<Gsv::Language> guess_language(const boost::filesystem::path &file_path); + + class Offset { + public: + Offset() {} + + Offset(unsigned line, unsigned index, const boost::filesystem::path &file_path = "") : line(line), index(index), + file_path(file_path) {} + + operator bool() { return !file_path.empty(); } + + bool operator==(const Offset &o) { return (line == o.line && index == o.index); } + + unsigned line; + unsigned index; + boost::filesystem::path file_path; + }; + + class FixIt { + public: + enum class Type { + INSERT, REPLACE, ERASE + }; + + FixIt(const std::string &source, const std::pair<Offset, Offset> &offsets); + + std::string string(Glib::RefPtr<Gtk::TextBuffer> buffer); + + Type type; + std::string source; + std::pair<Offset, Offset> offsets; + }; + + class View : public SpellCheckView, public DiffView { + public: + static std::unordered_set<View *> non_deleted_views; + static std::unordered_set<View *> views; + + View(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language, + bool is_generic_view = false); + + ~View(); + + bool save() override; + + void configure() override; + + void search_highlight(const std::string &text, bool case_sensitive, bool regex); + + std::function<void(int number)> update_search_occurrences; + + void search_forward(); + + void search_backward(); + + void replace_forward(const std::string &replacement); + + void replace_backward(const std::string &replacement); + + void replace_all(const std::string &replacement); + + void paste(); + + std::function<void()> non_interactive_completion; + std::function<void(bool)> format_style; + std::function<Offset()> get_declaration_location; + std::function<Offset()> get_type_declaration_location; + std::function<std::vector<Offset>()> get_implementation_locations; + std::function<std::vector<Offset>()> get_declaration_or_implementation_locations; + std::function<std::vector<std::pair<Offset, std::string> >()> get_usages; + std::function<std::string()> get_method; + std::function<std::vector<std::pair<Offset, std::string> >()> get_methods; + std::function<std::vector<std::string>()> get_token_data; + std::function<std::string()> get_token_spelling; + std::function<void(const std::string &text)> rename_similar_tokens; + std::function<void()> goto_next_diagnostic; + std::function<std::vector<FixIt>()> get_fix_its; + std::function<void()> toggle_comments; + std::function<std::tuple<Source::Offset, std::string, size_t>()> get_documentation_template; + std::function<void(int)> toggle_breakpoint; + + void hide_tooltips() override; + + void hide_dialogs() override; + + void set_tab_char_and_size(char tab_char, unsigned tab_size); + + std::pair<char, unsigned> get_tab_char_and_size() { return {tab_char, tab_size}; } + + bool soft_reparse_needed = false; + bool full_reparse_needed = false; + + virtual void soft_reparse(bool delayed = false) { soft_reparse_needed = false; } + + virtual void full_reparse() { full_reparse_needed = false; } + + protected: + bool parsed = true; + Tooltips diagnostic_tooltips; + Tooltips type_tooltips; + sigc::connection delayed_tooltips_connection; + + virtual void show_diagnostic_tooltips(const Gdk::Rectangle &rectangle) { diagnostic_tooltips.show(rectangle); } + + void + add_diagnostic_tooltip(const Gtk::TextIter &start, const Gtk::TextIter &end, std::string spelling, bool error); + + void clear_diagnostic_tooltips(); + + virtual void show_type_tooltips(const Gdk::Rectangle &rectangle) {} + + gdouble on_motion_last_x = 0.0; + gdouble on_motion_last_y = 0.0; + + /// Usually returns at start of line, but not always + Gtk::TextIter find_start_of_sentence(Gtk::TextIter iter); + + bool find_open_non_curly_bracket_backward(Gtk::TextIter iter, Gtk::TextIter &found_iter); + + bool find_open_curly_bracket_backward(Gtk::TextIter iter, Gtk::TextIter &found_iter); + + bool find_close_curly_bracket_forward(Gtk::TextIter iter, Gtk::TextIter &found_iter); + + long symbol_count(Gtk::TextIter iter, unsigned int positive_char, unsigned int negative_char); + + bool is_templated_function(Gtk::TextIter iter, Gtk::TextIter &parenthesis_end_iter); + + std::string get_token(Gtk::TextIter iter); + + void cleanup_whitespace_characters_on_return(const Gtk::TextIter &iter); + + bool is_bracket_language = false; + + bool on_key_press_event(GdkEventKey *key) override; + + bool on_key_press_event_basic(GdkEventKey *key); + + bool on_key_press_event_bracket_language(GdkEventKey *key); + + bool on_key_press_event_smart_brackets(GdkEventKey *key); + + bool on_key_press_event_smart_inserts(GdkEventKey *key); + + bool on_button_press_event(GdkEventButton *event) override; + + bool on_motion_notify_event(GdkEventMotion *motion_event) override; + + std::pair<char, unsigned> find_tab_char_and_size(); + + unsigned tab_size; + char tab_char; + std::string tab; + + bool interactive_completion = true; + + guint previous_non_modifier_keyval = 0; + private: + void setup_tooltip_and_dialog_events(); + + void setup_format_style(bool is_generic_view); + + void cleanup_whitespace_characters(); + + Gsv::DrawSpacesFlags parse_show_whitespace_characters(const std::string &text); + + GtkSourceSearchContext *search_context; + GtkSourceSearchSettings *search_settings; + + static void search_occurrences_updated(GtkWidget *widget, GParamSpec *property, gpointer data); + + sigc::connection renderer_activate_connection; + + bool multiple_cursors_signals_set = false; + bool multiple_cursors_use = false; + std::vector<std::pair<Glib::RefPtr<Gtk::TextBuffer::Mark>, int>> multiple_cursors_extra_cursors; + Glib::RefPtr<Gtk::TextBuffer::Mark> multiple_cursors_last_insert; + int multiple_cursors_erase_backward_length; + int multiple_cursors_erase_forward_length; + + bool on_key_press_event_multiple_cursors(GdkEventKey *key); + }; + + class GenericView : public View { + private: + class CompletionBuffer : public Gtk::TextBuffer { + public: + static Glib::RefPtr<CompletionBuffer> create() { + return Glib::RefPtr<CompletionBuffer>(new CompletionBuffer()); + } + }; + public: - static Glib::RefPtr<CompletionBuffer> create() {return Glib::RefPtr<CompletionBuffer>(new CompletionBuffer());} + GenericView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); + + void parse_language_file(Glib::RefPtr<CompletionBuffer> &completion_buffer, bool &has_context_class, + const boost::property_tree::ptree &pt); }; - public: - GenericView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); - - void parse_language_file(Glib::RefPtr<CompletionBuffer> &completion_buffer, bool &has_context_class, const boost::property_tree::ptree &pt); - }; } diff --git a/src/source_base.cc b/src/source_base.cc index 8a6813bd..20778a5b 100644 --- a/src/source_base.cc +++ b/src/source_base.cc @@ -5,389 +5,401 @@ #include "config.h" #include <fstream> -Source::BaseView::BaseView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language): Gsv::View(), file_path(file_path), language(language), status_diagnostics(0, 0, 0) { - load(true); - get_buffer()->place_cursor(get_buffer()->get_iter_at_offset(0)); - - signal_focus_in_event().connect([this](GdkEventFocus *event) { - if(this->last_write_time!=static_cast<std::time_t>(-1)) - check_last_write_time(); - return false; - }); - - monitor_file(); +Source::BaseView::BaseView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language) + : Gsv::View(), file_path(file_path), language(language), status_diagnostics(0, 0, 0) { + load(true); + get_buffer()->place_cursor(get_buffer()->get_iter_at_offset(0)); + + signal_focus_in_event().connect([this](GdkEventFocus *event) { + if (this->last_write_time != static_cast<std::time_t>(-1)) + check_last_write_time(); + return false; + }); + + monitor_file(); } Source::BaseView::~BaseView() { - monitor_changed_connection.disconnect(); - delayed_monitor_changed_connection.disconnect(); + monitor_changed_connection.disconnect(); + delayed_monitor_changed_connection.disconnect(); } bool Source::BaseView::load(bool not_undoable_action) { - boost::system::error_code ec; - last_write_time=boost::filesystem::last_write_time(file_path, ec); - if(ec) - last_write_time=static_cast<std::time_t>(-1); - - disable_spellcheck=true; - if(not_undoable_action) - get_source_buffer()->begin_not_undoable_action(); - - class Guard { - public: - Source::BaseView *view; - bool not_undoable_action; - ~Guard() { - if(not_undoable_action) - view->get_source_buffer()->end_not_undoable_action(); - view->disable_spellcheck=false; - } - }; - Guard guard{this, not_undoable_action}; - - if(language) { - std::ifstream input(file_path.string(), std::ofstream::binary); - if(input) { - std::stringstream ss; - ss << input.rdbuf(); - Glib::ustring ustr=ss.str(); - - bool valid=true; - Glib::ustring::iterator iter; - while(!ustr.validate(iter)) { - auto next_char_iter=iter; - next_char_iter++; - ustr.replace(iter, next_char_iter, "?"); - valid=false; - } - - if(!valid) - Terminal::get().print("Warning: "+file_path.string()+" is not a valid UTF-8 file. Saving might corrupt the file.\n"); - - if(get_buffer()->size()==0) - get_buffer()->insert_at_cursor(ustr); - else - replace_text(ustr.raw()); - } - else - return false; - } - else { - std::ifstream input(file_path.string(), std::ofstream::binary); - if(input) { - std::stringstream ss; - ss << input.rdbuf(); - Glib::ustring ustr=ss.str(); - - if(ustr.validate()) { - if(get_buffer()->size()==0) - get_buffer()->insert_at_cursor(ustr); - else - replace_text(ustr.raw()); - } - else { - Terminal::get().print("Error: "+file_path.string()+" is not a valid UTF-8 file.\n", true); - return false; - } + boost::system::error_code ec; + last_write_time = boost::filesystem::last_write_time(file_path, ec); + if (ec) + last_write_time = static_cast<std::time_t>(-1); + + disable_spellcheck = true; + if (not_undoable_action) + get_source_buffer()->begin_not_undoable_action(); + + class Guard { + public: + Source::BaseView *view; + bool not_undoable_action; + + ~Guard() { + if (not_undoable_action) + view->get_source_buffer()->end_not_undoable_action(); + view->disable_spellcheck = false; + } + }; + Guard guard{this, not_undoable_action}; + + if (language) { + std::ifstream input(file_path.string(), std::ofstream::binary); + if (input) { + std::stringstream ss; + ss << input.rdbuf(); + Glib::ustring ustr = ss.str(); + + bool valid = true; + Glib::ustring::iterator iter; + while (!ustr.validate(iter)) { + auto next_char_iter = iter; + next_char_iter++; + ustr.replace(iter, next_char_iter, "?"); + valid = false; + } + + if (!valid) + Terminal::get().print("Warning: " + file_path.string() + + " is not a valid UTF-8 file. Saving might corrupt the file.\n"); + + if (get_buffer()->size() == 0) + get_buffer()->insert_at_cursor(ustr); + else + replace_text(ustr.raw()); + } else + return false; + } else { + std::ifstream input(file_path.string(), std::ofstream::binary); + if (input) { + std::stringstream ss; + ss << input.rdbuf(); + Glib::ustring ustr = ss.str(); + + if (ustr.validate()) { + if (get_buffer()->size() == 0) + get_buffer()->insert_at_cursor(ustr); + else + replace_text(ustr.raw()); + } else { + Terminal::get().print("Error: " + file_path.string() + " is not a valid UTF-8 file.\n", true); + return false; + } + } else + return false; } - else - return false; - } - - get_buffer()->set_modified(false); - return true; + + get_buffer()->set_modified(false); + return true; } void Source::BaseView::replace_text(const std::string &new_text) { - get_buffer()->begin_user_action(); - - if(get_buffer()->size()==0) { - get_buffer()->insert_at_cursor(new_text); - get_buffer()->end_user_action(); - return; - } - else if(new_text.empty()) { - get_buffer()->set_text(new_text); - get_buffer()->end_user_action(); - return; - } - - auto iter=get_buffer()->get_insert()->get_iter(); - int cursor_line_nr=iter.get_line(); - int cursor_line_offset=iter.ends_line() ? std::numeric_limits<int>::max() : iter.get_line_offset(); - - std::vector<std::pair<const char*, const char*>> new_lines; - - const char* line_start=new_text.c_str(); - for(size_t i=0;i<new_text.size();++i) { - if(new_text[i]=='\n') { - new_lines.emplace_back(line_start, &new_text[i]+1); - line_start = &new_text[i]+1; + get_buffer()->begin_user_action(); + + if (get_buffer()->size() == 0) { + get_buffer()->insert_at_cursor(new_text); + get_buffer()->end_user_action(); + return; + } else if (new_text.empty()) { + get_buffer()->set_text(new_text); + get_buffer()->end_user_action(); + return; } - } - if(new_text.empty() || new_text.back()!='\n') - new_lines.emplace_back(line_start, &new_text[new_text.size()]); - - try { - auto hunks = Git::Repository::Diff::get_hunks(get_buffer()->get_text().raw(), new_text); - - for(auto it=hunks.rbegin();it!=hunks.rend();++it) { - bool place_cursor=false; - Gtk::TextIter start; - if(it->old_lines.second!=0) { - start=get_buffer()->get_iter_at_line(it->old_lines.first-1); - auto end=get_buffer()->get_iter_at_line(it->old_lines.first-1+it->old_lines.second); - - if(cursor_line_nr>=start.get_line() && cursor_line_nr<end.get_line()) { - if(it->new_lines.second!=0) { - place_cursor = true; - int line_diff=cursor_line_nr-start.get_line(); - cursor_line_nr+=static_cast<int>(0.5+(static_cast<float>(line_diff)/it->old_lines.second)*it->new_lines.second)-line_diff; - } + + auto iter = get_buffer()->get_insert()->get_iter(); + int cursor_line_nr = iter.get_line(); + int cursor_line_offset = iter.ends_line() ? std::numeric_limits<int>::max() : iter.get_line_offset(); + + std::vector<std::pair<const char *, const char *>> new_lines; + + const char *line_start = new_text.c_str(); + for (size_t i = 0; i < new_text.size(); ++i) { + if (new_text[i] == '\n') { + new_lines.emplace_back(line_start, &new_text[i] + 1); + line_start = &new_text[i] + 1; } - - get_buffer()->erase(start, end); - start=get_buffer()->get_iter_at_line(it->old_lines.first-1); - } - else - start=get_buffer()->get_iter_at_line(it->old_lines.first); - if(it->new_lines.second!=0) { - get_buffer()->insert(start, new_lines[it->new_lines.first-1].first, new_lines[it->new_lines.first-1+it->new_lines.second-1].second); - if(place_cursor) - place_cursor_at_line_offset(cursor_line_nr, cursor_line_offset); - } } - } - catch(...) { - Terminal::get().print("Error: Could not replace text in buffer\n", true); - } - - get_buffer()->end_user_action(); + if (new_text.empty() || new_text.back() != '\n') + new_lines.emplace_back(line_start, &new_text[new_text.size()]); + + try { + auto hunks = Git::Repository::Diff::get_hunks(get_buffer()->get_text().raw(), new_text); + + for (auto it = hunks.rbegin(); it != hunks.rend(); ++it) { + bool place_cursor = false; + Gtk::TextIter start; + if (it->old_lines.second != 0) { + start = get_buffer()->get_iter_at_line(it->old_lines.first - 1); + auto end = get_buffer()->get_iter_at_line(it->old_lines.first - 1 + it->old_lines.second); + + if (cursor_line_nr >= start.get_line() && cursor_line_nr < end.get_line()) { + if (it->new_lines.second != 0) { + place_cursor = true; + int line_diff = cursor_line_nr - start.get_line(); + cursor_line_nr += static_cast<int>(0.5 + + (static_cast<float>(line_diff) / it->old_lines.second) * + it->new_lines.second) - line_diff; + } + } + + get_buffer()->erase(start, end); + start = get_buffer()->get_iter_at_line(it->old_lines.first - 1); + } else + start = get_buffer()->get_iter_at_line(it->old_lines.first); + if (it->new_lines.second != 0) { + get_buffer()->insert(start, new_lines[it->new_lines.first - 1].first, + new_lines[it->new_lines.first - 1 + it->new_lines.second - 1].second); + if (place_cursor) + place_cursor_at_line_offset(cursor_line_nr, cursor_line_offset); + } + } + } + catch (...) { + Terminal::get().print("Error: Could not replace text in buffer\n", true); + } + + get_buffer()->end_user_action(); } void Source::BaseView::rename(const boost::filesystem::path &path) { - file_path=path; - - if(update_status_file_path) - update_status_file_path(this); - if(update_tab_label) - update_tab_label(this); + file_path = path; + + if (update_status_file_path) + update_status_file_path(this); + if (update_tab_label) + update_tab_label(this); } void Source::BaseView::monitor_file() { #ifdef __APPLE__ // TODO: Gio file monitor is bugged on MacOS - class Recursive { - public: - static void f(BaseView *view, std::time_t last_write_time_) { - view->delayed_monitor_changed_connection.disconnect(); - view->delayed_monitor_changed_connection=Glib::signal_timeout().connect([view, last_write_time_]() { - boost::system::error_code ec; - auto last_write_time=boost::filesystem::last_write_time(view->file_path, ec); - if(last_write_time!=last_write_time_) - view->check_last_write_time(last_write_time); - Recursive::f(view, last_write_time); - return false; - }, 1000); - } - }; - delayed_monitor_changed_connection.disconnect(); - if(this->last_write_time!=static_cast<std::time_t>(-1)) - Recursive::f(this, last_write_time); -#else - if(this->last_write_time!=static_cast<std::time_t>(-1)) { - monitor=Gio::File::create_for_path(file_path.string())->monitor_file(Gio::FileMonitorFlags::FILE_MONITOR_NONE); - monitor_changed_connection.disconnect(); - monitor_changed_connection=monitor->signal_changed().connect([this](const Glib::RefPtr<Gio::File> &file, - const Glib::RefPtr<Gio::File>&, - Gio::FileMonitorEvent monitor_event) { - if(monitor_event!=Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { - delayed_monitor_changed_connection.disconnect(); - delayed_monitor_changed_connection=Glib::signal_timeout().connect([this]() { - check_last_write_time(); + class Recursive { + public: + static void f(BaseView *view, std::time_t last_write_time_) { + view->delayed_monitor_changed_connection.disconnect(); + view->delayed_monitor_changed_connection=Glib::signal_timeout().connect([view, last_write_time_]() { + boost::system::error_code ec; + auto last_write_time=boost::filesystem::last_write_time(view->file_path, ec); + if(last_write_time!=last_write_time_) + view->check_last_write_time(last_write_time); + Recursive::f(view, last_write_time); return false; - }, 500); + }, 1000); } - }); - } + }; + delayed_monitor_changed_connection.disconnect(); + if(this->last_write_time!=static_cast<std::time_t>(-1)) + Recursive::f(this, last_write_time); +#else + if (this->last_write_time != static_cast<std::time_t>(-1)) { + monitor = Gio::File::create_for_path(file_path.string())->monitor_file( + Gio::FileMonitorFlags::FILE_MONITOR_NONE); + monitor_changed_connection.disconnect(); + monitor_changed_connection = monitor->signal_changed().connect([this](const Glib::RefPtr<Gio::File> &file, + const Glib::RefPtr<Gio::File> &, + Gio::FileMonitorEvent monitor_event) { + if (monitor_event != Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { + delayed_monitor_changed_connection.disconnect(); + delayed_monitor_changed_connection = Glib::signal_timeout().connect([this]() { + check_last_write_time(); + return false; + }, 500); + } + }); + } #endif } void Source::BaseView::check_last_write_time(std::time_t last_write_time_) { - if(this->last_write_time==static_cast<std::time_t>(-1)) - return; - - if(Config::get().source.auto_reload_changed_files && !get_buffer()->get_modified()) { - boost::system::error_code ec; - auto last_write_time=last_write_time_!=static_cast<std::time_t>(-1) ? last_write_time_ : boost::filesystem::last_write_time(file_path, ec); - if(!ec && last_write_time!=this->last_write_time) { - if(load()) + if (this->last_write_time == static_cast<std::time_t>(-1)) return; + + if (Config::get().source.auto_reload_changed_files && !get_buffer()->get_modified()) { + boost::system::error_code ec; + auto last_write_time = last_write_time_ != static_cast<std::time_t>(-1) ? last_write_time_ + : boost::filesystem::last_write_time( + file_path, ec); + if (!ec && last_write_time != this->last_write_time) { + if (load()) + return; + } + } else if (has_focus()) { + boost::system::error_code ec; + auto last_write_time = last_write_time_ != static_cast<std::time_t>(-1) ? last_write_time_ + : boost::filesystem::last_write_time( + file_path, ec); + if (!ec && last_write_time != this->last_write_time) + Info::get().print("Caution: " + file_path.filename().string() + " was changed outside of juCi++"); } - } - else if(has_focus()) { - boost::system::error_code ec; - auto last_write_time=last_write_time_!=static_cast<std::time_t>(-1) ? last_write_time_ : boost::filesystem::last_write_time(file_path, ec); - if(!ec && last_write_time!=this->last_write_time) - Info::get().print("Caution: " + file_path.filename().string() + " was changed outside of juCi++"); - } } Gtk::TextIter Source::BaseView::get_iter_at_line_pos(int line, int pos) { - return get_iter_at_line_index(line, pos); + return get_iter_at_line_index(line, pos); } Gtk::TextIter Source::BaseView::get_iter_at_line_offset(int line, int offset) { - line=std::min(line, get_buffer()->get_line_count()-1); - if(line<0) - line=0; - auto iter=get_iter_at_line_end(line); - offset=std::min(offset, iter.get_line_offset()); - if(offset<0) - offset=0; - return get_buffer()->get_iter_at_line_offset(line, offset); + line = std::min(line, get_buffer()->get_line_count() - 1); + if (line < 0) + line = 0; + auto iter = get_iter_at_line_end(line); + offset = std::min(offset, iter.get_line_offset()); + if (offset < 0) + offset = 0; + return get_buffer()->get_iter_at_line_offset(line, offset); } Gtk::TextIter Source::BaseView::get_iter_at_line_index(int line, int index) { - line=std::min(line, get_buffer()->get_line_count()-1); - if(line<0) - line=0; - auto iter=get_iter_at_line_end(line); - index=std::min(index, iter.get_line_index()); - if(index<0) - index=0; - return get_buffer()->get_iter_at_line_index(line, index); + line = std::min(line, get_buffer()->get_line_count() - 1); + if (line < 0) + line = 0; + auto iter = get_iter_at_line_end(line); + index = std::min(index, iter.get_line_index()); + if (index < 0) + index = 0; + return get_buffer()->get_iter_at_line_index(line, index); } Gtk::TextIter Source::BaseView::get_iter_at_line_end(int line_nr) { - if(line_nr>=get_buffer()->get_line_count()) - return get_buffer()->end(); - else if(line_nr+1<get_buffer()->get_line_count()) { - auto iter=get_buffer()->get_iter_at_line(line_nr+1); - iter.backward_char(); - if(!iter.ends_line()) // for CR+LF - iter.backward_char(); - return iter; - } - else { - auto iter=get_buffer()->get_iter_at_line(line_nr); - while(!iter.ends_line() && iter.forward_char()) {} - return iter; - } + if (line_nr >= get_buffer()->get_line_count()) + return get_buffer()->end(); + else if (line_nr + 1 < get_buffer()->get_line_count()) { + auto iter = get_buffer()->get_iter_at_line(line_nr + 1); + iter.backward_char(); + if (!iter.ends_line()) // for CR+LF + iter.backward_char(); + return iter; + } else { + auto iter = get_buffer()->get_iter_at_line(line_nr); + while (!iter.ends_line() && iter.forward_char()) {} + return iter; + } } Gtk::TextIter Source::BaseView::get_iter_for_dialog() { - auto iter=get_buffer()->get_insert()->get_iter(); - Gdk::Rectangle visible_rect; - get_visible_rect(visible_rect); - Gdk::Rectangle iter_rect; - get_iter_location(iter, iter_rect); - iter_rect.set_width(1); - if(iter.get_line_offset()>=80) { - get_iter_at_location(iter, visible_rect.get_x(), iter_rect.get_y()); + auto iter = get_buffer()->get_insert()->get_iter(); + Gdk::Rectangle visible_rect; + get_visible_rect(visible_rect); + Gdk::Rectangle iter_rect; get_iter_location(iter, iter_rect); - } - if(!visible_rect.intersects(iter_rect)) - get_iter_at_location(iter, visible_rect.get_x(), visible_rect.get_y()+visible_rect.get_height()/3); - return iter; + iter_rect.set_width(1); + if (iter.get_line_offset() >= 80) { + get_iter_at_location(iter, visible_rect.get_x(), iter_rect.get_y()); + get_iter_location(iter, iter_rect); + } + if (!visible_rect.intersects(iter_rect)) + get_iter_at_location(iter, visible_rect.get_x(), visible_rect.get_y() + visible_rect.get_height() / 3); + return iter; } void Source::BaseView::place_cursor_at_line_pos(int line, int pos) { - get_buffer()->place_cursor(get_iter_at_line_pos(line, pos)); + get_buffer()->place_cursor(get_iter_at_line_pos(line, pos)); } void Source::BaseView::place_cursor_at_line_offset(int line, int offset) { - get_buffer()->place_cursor(get_iter_at_line_offset(line, offset)); + get_buffer()->place_cursor(get_iter_at_line_offset(line, offset)); } void Source::BaseView::place_cursor_at_line_index(int line, int index) { - get_buffer()->place_cursor(get_iter_at_line_index(line, index)); + get_buffer()->place_cursor(get_iter_at_line_index(line, index)); } Gtk::TextIter Source::BaseView::get_smart_home_iter(const Gtk::TextIter &iter) { - auto start_line_iter=get_buffer()->get_iter_at_line(iter.get_line()); - auto start_sentence_iter=start_line_iter; - while(!start_sentence_iter.ends_line() && - (*start_sentence_iter==' ' || *start_sentence_iter=='\t') && - start_sentence_iter.forward_char()) {} - - if(iter>start_sentence_iter || iter==start_line_iter) - return start_sentence_iter; - else - return start_line_iter; + auto start_line_iter = get_buffer()->get_iter_at_line(iter.get_line()); + auto start_sentence_iter = start_line_iter; + while (!start_sentence_iter.ends_line() && + (*start_sentence_iter == ' ' || *start_sentence_iter == '\t') && + start_sentence_iter.forward_char()) {} + + if (iter > start_sentence_iter || iter == start_line_iter) + return start_sentence_iter; + else + return start_line_iter; } Gtk::TextIter Source::BaseView::get_smart_end_iter(const Gtk::TextIter &iter) { - auto end_line_iter=get_iter_at_line_end(iter.get_line()); - auto end_sentence_iter=end_line_iter; - while(!end_sentence_iter.starts_line() && - (*end_sentence_iter==' ' || *end_sentence_iter=='\t' || end_sentence_iter.ends_line()) && - end_sentence_iter.backward_char()) {} - if(!end_sentence_iter.ends_line() && *end_sentence_iter!=' ' && *end_sentence_iter!='\t') - end_sentence_iter.forward_char(); - - if(iter==end_line_iter) - return end_sentence_iter; - else - return end_line_iter; + auto end_line_iter = get_iter_at_line_end(iter.get_line()); + auto end_sentence_iter = end_line_iter; + while (!end_sentence_iter.starts_line() && + (*end_sentence_iter == ' ' || *end_sentence_iter == '\t' || end_sentence_iter.ends_line()) && + end_sentence_iter.backward_char()) {} + if (!end_sentence_iter.ends_line() && *end_sentence_iter != ' ' && *end_sentence_iter != '\t') + end_sentence_iter.forward_char(); + + if (iter == end_line_iter) + return end_sentence_iter; + else + return end_line_iter; } std::string Source::BaseView::get_line(const Gtk::TextIter &iter) { - auto line_start_it = get_buffer()->get_iter_at_line(iter.get_line()); - auto line_end_it = get_iter_at_line_end(iter.get_line()); - std::string line(get_buffer()->get_text(line_start_it, line_end_it)); - return line; + auto line_start_it = get_buffer()->get_iter_at_line(iter.get_line()); + auto line_end_it = get_iter_at_line_end(iter.get_line()); + std::string line(get_buffer()->get_text(line_start_it, line_end_it)); + return line; } + std::string Source::BaseView::get_line(Glib::RefPtr<Gtk::TextBuffer::Mark> mark) { - return get_line(mark->get_iter()); + return get_line(mark->get_iter()); } + std::string Source::BaseView::get_line(int line_nr) { - return get_line(get_buffer()->get_iter_at_line(line_nr)); + return get_line(get_buffer()->get_iter_at_line(line_nr)); } + std::string Source::BaseView::get_line() { - return get_line(get_buffer()->get_insert()); + return get_line(get_buffer()->get_insert()); } std::string Source::BaseView::get_line_before(const Gtk::TextIter &iter) { - auto line_it = get_buffer()->get_iter_at_line(iter.get_line()); - std::string line(get_buffer()->get_text(line_it, iter)); - return line; + auto line_it = get_buffer()->get_iter_at_line(iter.get_line()); + std::string line(get_buffer()->get_text(line_it, iter)); + return line; } + std::string Source::BaseView::get_line_before(Glib::RefPtr<Gtk::TextBuffer::Mark> mark) { - return get_line_before(mark->get_iter()); + return get_line_before(mark->get_iter()); } + std::string Source::BaseView::get_line_before() { - return get_line_before(get_buffer()->get_insert()); + return get_line_before(get_buffer()->get_insert()); } Gtk::TextIter Source::BaseView::get_tabs_end_iter(const Gtk::TextIter &iter) { - return get_tabs_end_iter(iter.get_line()); + return get_tabs_end_iter(iter.get_line()); } + Gtk::TextIter Source::BaseView::get_tabs_end_iter(Glib::RefPtr<Gtk::TextBuffer::Mark> mark) { - return get_tabs_end_iter(mark->get_iter()); + return get_tabs_end_iter(mark->get_iter()); } + Gtk::TextIter Source::BaseView::get_tabs_end_iter(int line_nr) { - auto sentence_iter = get_buffer()->get_iter_at_line(line_nr); - while((*sentence_iter==' ' || *sentence_iter=='\t') && !sentence_iter.ends_line() && sentence_iter.forward_char()) {} - return sentence_iter; + auto sentence_iter = get_buffer()->get_iter_at_line(line_nr); + while ((*sentence_iter == ' ' || *sentence_iter == '\t') && !sentence_iter.ends_line() && + sentence_iter.forward_char()) {} + return sentence_iter; } + Gtk::TextIter Source::BaseView::get_tabs_end_iter() { - return get_tabs_end_iter(get_buffer()->get_insert()); + return get_tabs_end_iter(get_buffer()->get_insert()); } void Source::BaseView::place_cursor_at_next_diagnostic() { - auto insert_offset=get_buffer()->get_insert()->get_iter().get_offset(); - for(auto offset: diagnostic_offsets) { - if(offset>insert_offset) { - get_buffer()->place_cursor(get_buffer()->get_iter_at_offset(offset)); - scroll_to(get_buffer()->get_insert(), 0.0, 1.0, 0.5); - return; + auto insert_offset = get_buffer()->get_insert()->get_iter().get_offset(); + for (auto offset: diagnostic_offsets) { + if (offset > insert_offset) { + get_buffer()->place_cursor(get_buffer()->get_iter_at_offset(offset)); + scroll_to(get_buffer()->get_insert(), 0.0, 1.0, 0.5); + return; + } + } + if (diagnostic_offsets.size() == 0) + Info::get().print("No diagnostics found in current buffer"); + else { + auto iter = get_buffer()->get_iter_at_offset(*diagnostic_offsets.begin()); + get_buffer()->place_cursor(iter); + scroll_to(get_buffer()->get_insert(), 0.0, 1.0, 0.5); } - } - if(diagnostic_offsets.size()==0) - Info::get().print("No diagnostics found in current buffer"); - else { - auto iter=get_buffer()->get_iter_at_offset(*diagnostic_offsets.begin()); - get_buffer()->place_cursor(iter); - scroll_to(get_buffer()->get_insert(), 0.0, 1.0, 0.5); - } } diff --git a/src/source_base.h b/src/source_base.h index 0ded18af..34d4ca90 100644 --- a/src/source_base.h +++ b/src/source_base.h @@ -6,85 +6,113 @@ #include <boost/filesystem.hpp> namespace Source { - class BaseView : public Gsv::View { - public: - BaseView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); - ~BaseView(); - boost::filesystem::path file_path; - - Glib::RefPtr<Gsv::Language> language; - - bool load(bool not_undoable_action=false); - /// Set new text more optimally and without unnecessary scrolling - void replace_text(const std::string &new_text); - virtual void rename(const boost::filesystem::path &path); - virtual bool save() = 0; - - Glib::RefPtr<Gio::FileMonitor> monitor; - sigc::connection monitor_changed_connection; - sigc::connection delayed_monitor_changed_connection; - - virtual void configure() = 0; - virtual void hide_tooltips() = 0; - virtual void hide_dialogs() = 0; - - std::function<void(BaseView* view, bool center, bool show_tooltips)> scroll_to_cursor_delayed=[](BaseView* view, bool center, bool show_tooltips) {}; - - /// Safely returns iter given line and an offset using either byte index or character offset. Defaults to using byte index. - virtual Gtk::TextIter get_iter_at_line_pos(int line, int pos); - /// Safely returns iter given line and character offset - Gtk::TextIter get_iter_at_line_offset(int line, int offset); - /// Safely returns iter given line and byte index - Gtk::TextIter get_iter_at_line_index(int line, int index); - - Gtk::TextIter get_iter_at_line_end(int line_nr); - Gtk::TextIter get_iter_for_dialog(); - - /// Safely places cursor at line using get_iter_at_line_pos. - void place_cursor_at_line_pos(int line, int pos); - /// Safely places cursor at line offset - void place_cursor_at_line_offset(int line, int offset); - /// Safely places cursor at line index - void place_cursor_at_line_index(int line, int index); - - protected: - std::time_t last_write_time; - void monitor_file(); - void check_last_write_time(std::time_t last_write_time_=static_cast<std::time_t>(-1)); - - /// Move iter to line start. Depending on iter position, before or after indentation. - /// Works with wrapped lines. - Gtk::TextIter get_smart_home_iter(const Gtk::TextIter &iter); - /// Move iter to line end. Depending on iter position, before or after indentation. - /// Works with wrapped lines. - /// Note that smart end goes FIRST to end of line to avoid hiding empty chars after expressions. - Gtk::TextIter get_smart_end_iter(const Gtk::TextIter &iter); - - std::string get_line(const Gtk::TextIter &iter); - std::string get_line(Glib::RefPtr<Gtk::TextBuffer::Mark> mark); - std::string get_line(int line_nr); - std::string get_line(); - std::string get_line_before(const Gtk::TextIter &iter); - std::string get_line_before(Glib::RefPtr<Gtk::TextBuffer::Mark> mark); - std::string get_line_before(); - Gtk::TextIter get_tabs_end_iter(const Gtk::TextIter &iter); - Gtk::TextIter get_tabs_end_iter(Glib::RefPtr<Gtk::TextBuffer::Mark> mark); - Gtk::TextIter get_tabs_end_iter(int line_nr); - Gtk::TextIter get_tabs_end_iter(); - - std::set<int> diagnostic_offsets; - void place_cursor_at_next_diagnostic(); - public: - std::function<void(BaseView *view)> update_tab_label; - std::function<void(BaseView *view)> update_status_location; - std::function<void(BaseView *view)> update_status_file_path; - std::function<void(BaseView *view)> update_status_diagnostics; - std::function<void(BaseView *view)> update_status_state; - std::tuple<size_t, size_t, size_t> status_diagnostics; - std::string status_state; - std::function<void(BaseView *view)> update_status_branch; - std::string status_branch; - - bool disable_spellcheck=false; - }; + class BaseView : public Gsv::View { + public: + BaseView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); + + ~BaseView(); + + boost::filesystem::path file_path; + + Glib::RefPtr<Gsv::Language> language; + + bool load(bool not_undoable_action = false); + + /// Set new text more optimally and without unnecessary scrolling + void replace_text(const std::string &new_text); + + virtual void rename(const boost::filesystem::path &path); + + virtual bool save() = 0; + + Glib::RefPtr<Gio::FileMonitor> monitor; + sigc::connection monitor_changed_connection; + sigc::connection delayed_monitor_changed_connection; + + virtual void configure() = 0; + + virtual void hide_tooltips() = 0; + + virtual void hide_dialogs() = 0; + + std::function<void(BaseView *view, bool center, bool show_tooltips)> scroll_to_cursor_delayed = []( + BaseView *view, bool center, bool show_tooltips) {}; + + /// Safely returns iter given line and an offset using either byte index or character offset. Defaults to using byte index. + virtual Gtk::TextIter get_iter_at_line_pos(int line, int pos); + + /// Safely returns iter given line and character offset + Gtk::TextIter get_iter_at_line_offset(int line, int offset); + + /// Safely returns iter given line and byte index + Gtk::TextIter get_iter_at_line_index(int line, int index); + + Gtk::TextIter get_iter_at_line_end(int line_nr); + + Gtk::TextIter get_iter_for_dialog(); + + /// Safely places cursor at line using get_iter_at_line_pos. + void place_cursor_at_line_pos(int line, int pos); + + /// Safely places cursor at line offset + void place_cursor_at_line_offset(int line, int offset); + + /// Safely places cursor at line index + void place_cursor_at_line_index(int line, int index); + + protected: + std::time_t last_write_time; + + void monitor_file(); + + void check_last_write_time(std::time_t last_write_time_ = static_cast<std::time_t>(-1)); + + /// Move iter to line start. Depending on iter position, before or after indentation. + /// Works with wrapped lines. + Gtk::TextIter get_smart_home_iter(const Gtk::TextIter &iter); + + /// Move iter to line end. Depending on iter position, before or after indentation. + /// Works with wrapped lines. + /// Note that smart end goes FIRST to end of line to avoid hiding empty chars after expressions. + Gtk::TextIter get_smart_end_iter(const Gtk::TextIter &iter); + + std::string get_line(const Gtk::TextIter &iter); + + std::string get_line(Glib::RefPtr<Gtk::TextBuffer::Mark> mark); + + std::string get_line(int line_nr); + + std::string get_line(); + + std::string get_line_before(const Gtk::TextIter &iter); + + std::string get_line_before(Glib::RefPtr<Gtk::TextBuffer::Mark> mark); + + std::string get_line_before(); + + Gtk::TextIter get_tabs_end_iter(const Gtk::TextIter &iter); + + Gtk::TextIter get_tabs_end_iter(Glib::RefPtr<Gtk::TextBuffer::Mark> mark); + + Gtk::TextIter get_tabs_end_iter(int line_nr); + + Gtk::TextIter get_tabs_end_iter(); + + std::set<int> diagnostic_offsets; + + void place_cursor_at_next_diagnostic(); + + public: + std::function<void(BaseView *view)> update_tab_label; + std::function<void(BaseView *view)> update_status_location; + std::function<void(BaseView *view)> update_status_file_path; + std::function<void(BaseView *view)> update_status_diagnostics; + std::function<void(BaseView *view)> update_status_state; + std::tuple<size_t, size_t, size_t> status_diagnostics; + std::string status_state; + std::function<void(BaseView *view)> update_status_branch; + std::string status_branch; + + bool disable_spellcheck = false; + }; } diff --git a/src/source_clang.cc b/src/source_clang.cc index affe5812..68c39bbe 100644 --- a/src/source_clang.cc +++ b/src/source_clang.cc @@ -2,9 +2,11 @@ #include "config.h" #include "terminal.h" #include "project_build.h" + #ifdef JUCI_ENABLE_DEBUG #include "debug_lldb.h" #endif + #include "info.h" #include "dialogs.h" #include "ctags.h" @@ -16,375 +18,377 @@ clangmm::Index Source::ClangViewParse::clang_index(0, 0); -Source::ClangViewParse::ClangViewParse(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language): - BaseView(file_path, language), Source::View(file_path, language) { - Usages::Clang::erase_cache(file_path); - - auto tag_table=get_buffer()->get_tag_table(); - for (auto &item : clang_types()) { - if(!tag_table->lookup(item.second)) { - get_buffer()->create_tag(item.second); +Source::ClangViewParse::ClangViewParse(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language) : + BaseView(file_path, language), Source::View(file_path, language) { + Usages::Clang::erase_cache(file_path); + + auto tag_table = get_buffer()->get_tag_table(); + for (auto &item : clang_types()) { + if (!tag_table->lookup(item.second)) { + get_buffer()->create_tag(item.second); + } + } + configure(); + + if (get_buffer()->size() == 0 && (language->get_id() == "chdr" || language->get_id() == "cpphdr")) { + disable_spellcheck = true; + get_buffer()->insert_at_cursor("#pragma once\n"); + disable_spellcheck = false; + Info::get().print("Added \"#pragma once\" to empty C/C++ header file"); } - } - configure(); - - if(get_buffer()->size()==0 && (language->get_id()=="chdr" || language->get_id()=="cpphdr")) { - disable_spellcheck=true; - get_buffer()->insert_at_cursor("#pragma once\n"); - disable_spellcheck=false; - Info::get().print("Added \"#pragma once\" to empty C/C++ header file"); - } - - parse_initialize(); - - get_buffer()->signal_changed().connect([this]() { - soft_reparse(true); - }); + + parse_initialize(); + + get_buffer()->signal_changed().connect([this]() { + soft_reparse(true); + }); } bool Source::ClangViewParse::save() { - if(!Source::View::save()) - return false; - - if(language->get_id()=="chdr" || language->get_id()=="cpphdr") { - for(auto &view: views) { - if(auto clang_view=dynamic_cast<Source::ClangView*>(view)) { - if(this!=clang_view) - clang_view->soft_reparse_needed=true; - } + if (!Source::View::save()) + return false; + + if (language->get_id() == "chdr" || language->get_id() == "cpphdr") { + for (auto &view: views) { + if (auto clang_view = dynamic_cast<Source::ClangView *>(view)) { + if (this != clang_view) + clang_view->soft_reparse_needed = true; + } + } } - } - return true; + return true; } void Source::ClangViewParse::configure() { - Source::View::configure(); - - auto scheme = get_source_buffer()->get_style_scheme(); - auto tag_table=get_buffer()->get_tag_table(); - for (auto &item : clang_types()) { - auto tag = get_buffer()->get_tag_table()->lookup(item.second); - if(tag) { - auto style = scheme->get_style(item.second); - if (style) { - if (style->property_foreground_set()) - tag->property_foreground() = style->property_foreground(); - if (style->property_background_set()) - tag->property_background() = style->property_background(); - if (style->property_strikethrough_set()) - tag->property_strikethrough() = style->property_strikethrough(); - // // if (style->property_bold_set()) tag->property_weight() = style->property_bold(); - // // if (style->property_italic_set()) tag->property_italic() = style->property_italic(); - // // if (style->property_line_background_set()) tag->property_line_background() = style->property_line_background(); - // // if (style->property_underline_set()) tag->property_underline() = style->property_underline(); - } + Source::View::configure(); + + auto scheme = get_source_buffer()->get_style_scheme(); + auto tag_table = get_buffer()->get_tag_table(); + for (auto &item : clang_types()) { + auto tag = get_buffer()->get_tag_table()->lookup(item.second); + if (tag) { + auto style = scheme->get_style(item.second); + if (style) { + if (style->property_foreground_set()) + tag->property_foreground() = style->property_foreground(); + if (style->property_background_set()) + tag->property_background() = style->property_background(); + if (style->property_strikethrough_set()) + tag->property_strikethrough() = style->property_strikethrough(); + // // if (style->property_bold_set()) tag->property_weight() = style->property_bold(); + // // if (style->property_italic_set()) tag->property_italic() = style->property_italic(); + // // if (style->property_line_background_set()) tag->property_line_background() = style->property_line_background(); + // // if (style->property_underline_set()) tag->property_underline() = style->property_underline(); + } + } } - } } void Source::ClangViewParse::parse_initialize() { - hide_tooltips(); - parsed=false; - if(parse_thread.joinable()) - parse_thread.join(); - parse_state=ParseState::PROCESSING; - parse_process_state=ParseProcessState::STARTING; - - auto buffer=get_buffer()->get_text(); - //Remove includes for first parse for initial syntax highlighting - std::size_t pos=0; - while((pos=buffer.find("#include", pos))!=std::string::npos) { - auto start_pos=pos; - pos=buffer.find('\n', pos+8); - if(pos==std::string::npos) - break; - if(start_pos==0 || buffer[start_pos-1]=='\n') { - buffer.replace(start_pos, pos-start_pos, pos-start_pos, ' '); + hide_tooltips(); + parsed = false; + if (parse_thread.joinable()) + parse_thread.join(); + parse_state = ParseState::PROCESSING; + parse_process_state = ParseProcessState::STARTING; + + auto buffer = get_buffer()->get_text(); + //Remove includes for first parse for initial syntax highlighting + std::size_t pos = 0; + while ((pos = buffer.find("#include", pos)) != std::string::npos) { + auto start_pos = pos; + pos = buffer.find('\n', pos + 8); + if (pos == std::string::npos) + break; + if (start_pos == 0 || buffer[start_pos - 1] == '\n') { + buffer.replace(start_pos, pos - start_pos, pos - start_pos, ' '); + } + pos++; } - pos++; - } - auto &buffer_raw=const_cast<std::string&>(buffer.raw()); - if(language && (language->get_id()=="chdr" || language->get_id()=="cpphdr")) - clangmm::remove_include_guard(buffer_raw); - - auto build=Project::Build::create(file_path); - if(build->project_path.empty()) - Info::get().print(file_path.filename().string()+": could not find a supported build system"); - build->update_default(); - auto arguments=CompileCommands::get_arguments(build->get_default_path(), file_path); - clang_tu = std::make_unique<clangmm::TranslationUnit>(clang_index, file_path.string(), arguments, buffer_raw); - clang_tokens=clang_tu->get_tokens(); - clang_tokens_offsets.clear(); - clang_tokens_offsets.reserve(clang_tokens->size()); - for(auto &token: *clang_tokens) - clang_tokens_offsets.emplace_back(token.get_source_range().get_offsets()); - update_syntax(); - - status_state="parsing..."; - if(update_status_state) - update_status_state(this); - parse_thread=std::thread([this]() { - while(true) { - while(parse_state==ParseState::PROCESSING && parse_process_state!=ParseProcessState::STARTING && parse_process_state!=ParseProcessState::PROCESSING) - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - if(parse_state!=ParseState::PROCESSING) - break; - auto expected=ParseProcessState::STARTING; - std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock); - if(parse_process_state.compare_exchange_strong(expected, ParseProcessState::PREPROCESSING)) { - dispatcher.post([this] { - auto expected=ParseProcessState::PREPROCESSING; - std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock); - if(parse_lock.try_lock()) { - if(parse_process_state.compare_exchange_strong(expected, ParseProcessState::PROCESSING)) - parse_thread_buffer=get_buffer()->get_text(); - parse_lock.unlock(); - } - else - parse_process_state.compare_exchange_strong(expected, ParseProcessState::STARTING); - }); - } - else if (parse_process_state==ParseProcessState::PROCESSING && parse_lock.try_lock()) { - auto &parse_thread_buffer_raw=const_cast<std::string&>(parse_thread_buffer.raw()); - if(this->language && (this->language->get_id()=="chdr" || this->language->get_id()=="cpphdr")) - clangmm::remove_include_guard(parse_thread_buffer_raw); - auto status=clang_tu->reparse(parse_thread_buffer_raw); - if(status==0) { - auto expected=ParseProcessState::PROCESSING; - if(parse_process_state.compare_exchange_strong(expected, ParseProcessState::POSTPROCESSING)) { - clang_tokens=clang_tu->get_tokens(); - clang_tokens_offsets.clear(); - clang_tokens_offsets.reserve(clang_tokens->size()); - for(auto &token: *clang_tokens) - clang_tokens_offsets.emplace_back(token.get_source_range().get_offsets()); - clang_diagnostics=clang_tu->get_diagnostics(); - parse_lock.unlock(); - dispatcher.post([this] { - std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock); - if(parse_lock.try_lock()) { - auto expected=ParseProcessState::POSTPROCESSING; - if(parse_process_state.compare_exchange_strong(expected, ParseProcessState::IDLE)) { - update_syntax(); - update_diagnostics(); - parsed=true; - status_state=""; - if(update_status_state) - update_status_state(this); + auto &buffer_raw = const_cast<std::string &>(buffer.raw()); + if (language && (language->get_id() == "chdr" || language->get_id() == "cpphdr")) + clangmm::remove_include_guard(buffer_raw); + + auto build = Project::Build::create(file_path); + if (build->project_path.empty()) + Info::get().print(file_path.filename().string() + ": could not find a supported build system"); + build->update_default(); + auto arguments = CompileCommands::get_arguments(build->get_default_path(), file_path); + clang_tu = std::make_unique<clangmm::TranslationUnit>(clang_index, file_path.string(), arguments, buffer_raw); + clang_tokens = clang_tu->get_tokens(); + clang_tokens_offsets.clear(); + clang_tokens_offsets.reserve(clang_tokens->size()); + for (auto &token: *clang_tokens) + clang_tokens_offsets.emplace_back(token.get_source_range().get_offsets()); + update_syntax(); + + status_state = "parsing..."; + if (update_status_state) + update_status_state(this); + parse_thread = std::thread([this]() { + while (true) { + while (parse_state == ParseState::PROCESSING && parse_process_state != ParseProcessState::STARTING && + parse_process_state != ParseProcessState::PROCESSING) + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + if (parse_state != ParseState::PROCESSING) + break; + auto expected = ParseProcessState::STARTING; + std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock); + if (parse_process_state.compare_exchange_strong(expected, ParseProcessState::PREPROCESSING)) { + dispatcher.post([this] { + auto expected = ParseProcessState::PREPROCESSING; + std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock); + if (parse_lock.try_lock()) { + if (parse_process_state.compare_exchange_strong(expected, ParseProcessState::PROCESSING)) + parse_thread_buffer = get_buffer()->get_text(); + parse_lock.unlock(); + } else + parse_process_state.compare_exchange_strong(expected, ParseProcessState::STARTING); + }); + } else if (parse_process_state == ParseProcessState::PROCESSING && parse_lock.try_lock()) { + auto &parse_thread_buffer_raw = const_cast<std::string &>(parse_thread_buffer.raw()); + if (this->language && (this->language->get_id() == "chdr" || this->language->get_id() == "cpphdr")) + clangmm::remove_include_guard(parse_thread_buffer_raw); + auto status = clang_tu->reparse(parse_thread_buffer_raw); + if (status == 0) { + auto expected = ParseProcessState::PROCESSING; + if (parse_process_state.compare_exchange_strong(expected, ParseProcessState::POSTPROCESSING)) { + clang_tokens = clang_tu->get_tokens(); + clang_tokens_offsets.clear(); + clang_tokens_offsets.reserve(clang_tokens->size()); + for (auto &token: *clang_tokens) + clang_tokens_offsets.emplace_back(token.get_source_range().get_offsets()); + clang_diagnostics = clang_tu->get_diagnostics(); + parse_lock.unlock(); + dispatcher.post([this] { + std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock); + if (parse_lock.try_lock()) { + auto expected = ParseProcessState::POSTPROCESSING; + if (parse_process_state.compare_exchange_strong(expected, ParseProcessState::IDLE)) { + update_syntax(); + update_diagnostics(); + parsed = true; + status_state = ""; + if (update_status_state) + update_status_state(this); + } + parse_lock.unlock(); + } + }); + } else + parse_lock.unlock(); + } else { + parse_state = ParseState::STOP; + parse_lock.unlock(); + dispatcher.post([this] { + Terminal::get().print("Error: failed to reparse " + this->file_path.string() + ".\n", true); + status_state = ""; + if (update_status_state) + update_status_state(this); + status_diagnostics = std::make_tuple(0, 0, 0); + if (update_status_diagnostics) + update_status_diagnostics(this); + }); } - parse_lock.unlock(); - } - }); - } - else - parse_lock.unlock(); - } - else { - parse_state=ParseState::STOP; - parse_lock.unlock(); - dispatcher.post([this] { - Terminal::get().print("Error: failed to reparse "+this->file_path.string()+".\n", true); - status_state=""; - if(update_status_state) - update_status_state(this); - status_diagnostics=std::make_tuple(0, 0, 0); - if(update_status_diagnostics) - update_status_diagnostics(this); - }); + } } - } - } - }); + }); } void Source::ClangViewParse::soft_reparse(bool delayed) { - soft_reparse_needed=false; - parsed=false; - if(parse_state!=ParseState::PROCESSING) - return; - parse_process_state=ParseProcessState::IDLE; - delayed_reparse_connection.disconnect(); - delayed_reparse_connection=Glib::signal_timeout().connect([this]() { - parsed=false; - auto expected=ParseProcessState::IDLE; - if(parse_process_state.compare_exchange_strong(expected, ParseProcessState::STARTING)) { - status_state="parsing..."; - if(update_status_state) - update_status_state(this); - } - return false; - }, delayed?1000:0); + soft_reparse_needed = false; + parsed = false; + if (parse_state != ParseState::PROCESSING) + return; + parse_process_state = ParseProcessState::IDLE; + delayed_reparse_connection.disconnect(); + delayed_reparse_connection = Glib::signal_timeout().connect([this]() { + parsed = false; + auto expected = ParseProcessState::IDLE; + if (parse_process_state.compare_exchange_strong(expected, ParseProcessState::STARTING)) { + status_state = "parsing..."; + if (update_status_state) + update_status_state(this); + } + return false; + }, delayed ? 1000 : 0); } const std::unordered_map<int, std::string> &Source::ClangViewParse::clang_types() { - static std::unordered_map<int, std::string> types{ - {8, "def:function"}, - {21, "def:function"}, - {22, "def:identifier"}, - {24, "def:function"}, - {25, "def:function"}, - {43, "def:type"}, - {44, "def:type"}, - {45, "def:type"}, - {46, "def:identifier"}, - {109, "def:string"}, - {702, "def:statement"}, - {705, "def:comment"} - }; - return types; + static std::unordered_map<int, std::string> types{ + {8, "def:function"}, + {21, "def:function"}, + {22, "def:identifier"}, + {24, "def:function"}, + {25, "def:function"}, + {43, "def:type"}, + {44, "def:type"}, + {45, "def:type"}, + {46, "def:identifier"}, + {109, "def:string"}, + {702, "def:statement"}, + {705, "def:comment"} + }; + return types; } void Source::ClangViewParse::update_syntax() { - auto buffer=get_buffer(); - const auto apply_tag=[this, buffer](const std::pair<clangmm::Offset, clangmm::Offset> &offsets, int type) { - auto type_it=clang_types().find(type); - if(type_it!=clang_types().end()) { - last_syntax_tags.emplace(type_it->second); - Gtk::TextIter begin_iter = buffer->get_iter_at_line_index(offsets.first.line-1, offsets.first.index-1); - Gtk::TextIter end_iter = buffer->get_iter_at_line_index(offsets.second.line-1, offsets.second.index-1); - buffer->apply_tag_by_name(type_it->second, begin_iter, end_iter); - } - }; - - for(auto &tag: last_syntax_tags) - buffer->remove_tag_by_name(tag, buffer->begin(), buffer->end()); - last_syntax_tags.clear(); - - for(size_t c=0;c<clang_tokens->size();++c) { - auto &token=(*clang_tokens)[c]; - auto &token_offsets=clang_tokens_offsets[c]; - //if(token.get_kind()==clangmm::Token::Kind::Token_Punctuation) - //ranges.emplace_back(token_offset, static_cast<int>(token.get_cursor().get_kind())); - auto token_kind=token.get_kind(); - if(token_kind==clangmm::Token::Kind::Keyword) - apply_tag(token_offsets, 702); - else if(token_kind==clangmm::Token::Kind::Identifier) { - auto cursor_kind=token.get_cursor().get_kind(); - if(cursor_kind==clangmm::Cursor::Kind::DeclRefExpr || cursor_kind==clangmm::Cursor::Kind::MemberRefExpr) - cursor_kind=token.get_cursor().get_referenced().get_kind(); - if(cursor_kind!=clangmm::Cursor::Kind::PreprocessingDirective) - apply_tag(token_offsets, static_cast<int>(cursor_kind)); + auto buffer = get_buffer(); + const auto apply_tag = [this, buffer](const std::pair<clangmm::Offset, clangmm::Offset> &offsets, int type) { + auto type_it = clang_types().find(type); + if (type_it != clang_types().end()) { + last_syntax_tags.emplace(type_it->second); + Gtk::TextIter begin_iter = buffer->get_iter_at_line_index(offsets.first.line - 1, offsets.first.index - 1); + Gtk::TextIter end_iter = buffer->get_iter_at_line_index(offsets.second.line - 1, offsets.second.index - 1); + buffer->apply_tag_by_name(type_it->second, begin_iter, end_iter); + } + }; + + for (auto &tag: last_syntax_tags) + buffer->remove_tag_by_name(tag, buffer->begin(), buffer->end()); + last_syntax_tags.clear(); + + for (size_t c = 0; c < clang_tokens->size(); ++c) { + auto &token = (*clang_tokens)[c]; + auto &token_offsets = clang_tokens_offsets[c]; + //if(token.get_kind()==clangmm::Token::Kind::Token_Punctuation) + //ranges.emplace_back(token_offset, static_cast<int>(token.get_cursor().get_kind())); + auto token_kind = token.get_kind(); + if (token_kind == clangmm::Token::Kind::Keyword) + apply_tag(token_offsets, 702); + else if (token_kind == clangmm::Token::Kind::Identifier) { + auto cursor_kind = token.get_cursor().get_kind(); + if (cursor_kind == clangmm::Cursor::Kind::DeclRefExpr || + cursor_kind == clangmm::Cursor::Kind::MemberRefExpr) + cursor_kind = token.get_cursor().get_referenced().get_kind(); + if (cursor_kind != clangmm::Cursor::Kind::PreprocessingDirective) + apply_tag(token_offsets, static_cast<int>(cursor_kind)); + } else if (token_kind == clangmm::Token::Kind::Literal) + apply_tag(token_offsets, static_cast<int>(clangmm::Cursor::Kind::StringLiteral)); + else if (token_kind == clangmm::Token::Kind::Comment) + apply_tag(token_offsets, 705); } - else if(token_kind==clangmm::Token::Kind::Literal) - apply_tag(token_offsets, static_cast<int>(clangmm::Cursor::Kind::StringLiteral)); - else if(token_kind==clangmm::Token::Kind::Comment) - apply_tag(token_offsets, 705); - } } void Source::ClangViewParse::update_diagnostics() { - clear_diagnostic_tooltips(); - fix_its.clear(); - size_t num_warnings=0; - size_t num_errors=0; - size_t num_fix_its=0; - for(auto &diagnostic: clang_diagnostics) { - if(diagnostic.path==file_path.string()) { - int line=diagnostic.offsets.first.line-1; - if(line<0 || line>=get_buffer()->get_line_count()) - line=get_buffer()->get_line_count()-1; - auto start=get_iter_at_line_end(line); - int index=diagnostic.offsets.first.index-1; - if(index>=0 && index<start.get_line_index()) - start=get_buffer()->get_iter_at_line_index(line, index); - if(start.ends_line()) { - while(!start.is_start() && start.ends_line()) - start.backward_char(); - } - diagnostic_offsets.emplace(start.get_offset()); - - line=diagnostic.offsets.second.line-1; - if(line<0 || line>=get_buffer()->get_line_count()) - line=get_buffer()->get_line_count()-1; - auto end=get_iter_at_line_end(line); - index=diagnostic.offsets.second.index-1; - if(index>=0 && index<end.get_line_index()) - end=get_buffer()->get_iter_at_line_index(line, index); - - bool error=false; - std::string severity_tag_name; - if(diagnostic.severity<=clangmm::Diagnostic::Severity::Warning) { - severity_tag_name="def:warning"; - num_warnings++; - } - else { - severity_tag_name="def:error"; - num_errors++; - error=true; - } - - std::string fix_its_string; - unsigned fix_its_count=0; - for(auto &fix_it: diagnostic.fix_its) { - auto clang_offsets=fix_it.offsets; - std::pair<Offset, Offset> offsets; - offsets.first.line=clang_offsets.first.line-1; - offsets.first.index=clang_offsets.first.index-1; - offsets.second.line=clang_offsets.second.line-1; - offsets.second.index=clang_offsets.second.index-1; - - fix_its.emplace_back(fix_it.source, offsets); - - if(fix_its_string.size()>0) - fix_its_string+='\n'; - fix_its_string+=fix_its.back().string(get_buffer()); - fix_its_count++; - num_fix_its++; - } - - if(fix_its_count==1) - fix_its_string.insert(0, "Fix-it:\n"); - else if(fix_its_count>1) - fix_its_string.insert(0, "Fix-its:\n"); - - if(!fix_its_string.empty()) - diagnostic.spelling+="\n\n"+fix_its_string; - - add_diagnostic_tooltip(start, end, diagnostic.spelling, error); + clear_diagnostic_tooltips(); + fix_its.clear(); + size_t num_warnings = 0; + size_t num_errors = 0; + size_t num_fix_its = 0; + for (auto &diagnostic: clang_diagnostics) { + if (diagnostic.path == file_path.string()) { + int line = diagnostic.offsets.first.line - 1; + if (line < 0 || line >= get_buffer()->get_line_count()) + line = get_buffer()->get_line_count() - 1; + auto start = get_iter_at_line_end(line); + int index = diagnostic.offsets.first.index - 1; + if (index >= 0 && index < start.get_line_index()) + start = get_buffer()->get_iter_at_line_index(line, index); + if (start.ends_line()) { + while (!start.is_start() && start.ends_line()) + start.backward_char(); + } + diagnostic_offsets.emplace(start.get_offset()); + + line = diagnostic.offsets.second.line - 1; + if (line < 0 || line >= get_buffer()->get_line_count()) + line = get_buffer()->get_line_count() - 1; + auto end = get_iter_at_line_end(line); + index = diagnostic.offsets.second.index - 1; + if (index >= 0 && index < end.get_line_index()) + end = get_buffer()->get_iter_at_line_index(line, index); + + bool error = false; + std::string severity_tag_name; + if (diagnostic.severity <= clangmm::Diagnostic::Severity::Warning) { + severity_tag_name = "def:warning"; + num_warnings++; + } else { + severity_tag_name = "def:error"; + num_errors++; + error = true; + } + + std::string fix_its_string; + unsigned fix_its_count = 0; + for (auto &fix_it: diagnostic.fix_its) { + auto clang_offsets = fix_it.offsets; + std::pair<Offset, Offset> offsets; + offsets.first.line = clang_offsets.first.line - 1; + offsets.first.index = clang_offsets.first.index - 1; + offsets.second.line = clang_offsets.second.line - 1; + offsets.second.index = clang_offsets.second.index - 1; + + fix_its.emplace_back(fix_it.source, offsets); + + if (fix_its_string.size() > 0) + fix_its_string += '\n'; + fix_its_string += fix_its.back().string(get_buffer()); + fix_its_count++; + num_fix_its++; + } + + if (fix_its_count == 1) + fix_its_string.insert(0, "Fix-it:\n"); + else if (fix_its_count > 1) + fix_its_string.insert(0, "Fix-its:\n"); + + if (!fix_its_string.empty()) + diagnostic.spelling += "\n\n" + fix_its_string; + + add_diagnostic_tooltip(start, end, diagnostic.spelling, error); + } } - } - status_diagnostics=std::make_tuple(num_warnings, num_errors, num_fix_its); - if(update_status_diagnostics) - update_status_diagnostics(this); + status_diagnostics = std::make_tuple(num_warnings, num_errors, num_fix_its); + if (update_status_diagnostics) + update_status_diagnostics(this); } void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle) { - if(parsed) { - Gtk::TextIter iter; - int location_x, location_y; - window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, rectangle.get_x(), rectangle.get_y(), location_x, location_y); - location_x+=(rectangle.get_width()-1)/2; - get_iter_at_location(iter, location_x, location_y); - Gdk::Rectangle iter_rectangle; - get_iter_location(iter, iter_rectangle); - if(iter.ends_line() && location_x>iter_rectangle.get_x()) - return; - - auto line=static_cast<unsigned>(iter.get_line()); - auto index=static_cast<unsigned>(iter.get_line_index()); - type_tooltips.clear(); - for(size_t c=clang_tokens->size()-1;c!=static_cast<size_t>(-1);--c) { - auto &token=(*clang_tokens)[c]; - auto &token_offsets=clang_tokens_offsets[c]; - if(token.is_identifier() || token.get_spelling() == "auto" ) { - if(line==token_offsets.first.line-1 && index>=token_offsets.first.index-1 && index <=token_offsets.second.index-1) { - auto cursor=token.get_cursor(); - auto referenced=cursor.get_referenced(); - if(referenced) { - auto start=get_buffer()->get_iter_at_line_index(token_offsets.first.line-1, token_offsets.first.index-1); - auto end=get_buffer()->get_iter_at_line_index(token_offsets.second.line-1, token_offsets.second.index-1); - auto create_tooltip_buffer=[this, &token, &start, &end]() { - auto tooltip_buffer=Gtk::TextBuffer::create(get_buffer()->get_tag_table()); - tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), "Type: "+token.get_cursor().get_type_description()); - auto brief_comment=token.get_cursor().get_brief_comments(); - if(brief_comment!="") - tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), "\n\n"+brief_comment); - + if (parsed) { + Gtk::TextIter iter; + int location_x, location_y; + window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, rectangle.get_x(), rectangle.get_y(), location_x, + location_y); + location_x += (rectangle.get_width() - 1) / 2; + get_iter_at_location(iter, location_x, location_y); + Gdk::Rectangle iter_rectangle; + get_iter_location(iter, iter_rectangle); + if (iter.ends_line() && location_x > iter_rectangle.get_x()) + return; + + auto line = static_cast<unsigned>(iter.get_line()); + auto index = static_cast<unsigned>(iter.get_line_index()); + type_tooltips.clear(); + for (size_t c = clang_tokens->size() - 1; c != static_cast<size_t>(-1); --c) { + auto &token = (*clang_tokens)[c]; + auto &token_offsets = clang_tokens_offsets[c]; + if (token.is_identifier() || token.get_spelling() == "auto") { + if (line == token_offsets.first.line - 1 && index >= token_offsets.first.index - 1 && + index <= token_offsets.second.index - 1) { + auto cursor = token.get_cursor(); + auto referenced = cursor.get_referenced(); + if (referenced) { + auto start = get_buffer()->get_iter_at_line_index(token_offsets.first.line - 1, + token_offsets.first.index - 1); + auto end = get_buffer()->get_iter_at_line_index(token_offsets.second.line - 1, + token_offsets.second.index - 1); + auto create_tooltip_buffer = [this, &token, &start, &end]() { + auto tooltip_buffer = Gtk::TextBuffer::create(get_buffer()->get_tag_table()); + tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), + "Type: " + token.get_cursor().get_type_description()); + auto brief_comment = token.get_cursor().get_brief_comments(); + if (brief_comment != "") + tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), + "\n\n" + brief_comment); + #ifdef JUCI_ENABLE_DEBUG - if(Debug::LLDB::get().is_stopped()) { + if(Debug::LLDB::get().is_stopped()) { auto referenced=token.get_cursor().get_referenced(); auto location=referenced.get_source_location(); Glib::ustring value_type="Value"; - + auto iter=start; while((*iter>='a' && *iter<='z') || (*iter>='A' && *iter<='Z') || (*iter>='0' && *iter<='9') || *iter=='_' || *iter=='.') { start=iter; @@ -400,7 +404,7 @@ void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle) } } auto spelling=get_buffer()->get_text(start, end).raw(); - + Glib::ustring debug_value; auto cursor_kind=referenced.get_kind(); if(cursor_kind!=clangmm::Cursor::Kind::FunctionDecl && cursor_kind!=clangmm::Cursor::Kind::CXXMethod && @@ -427,1430 +431,1487 @@ void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle) } } #endif - - return tooltip_buffer; - }; - - type_tooltips.emplace_back(create_tooltip_buffer, this, get_buffer()->create_mark(start), get_buffer()->create_mark(end)); - type_tooltips.show(); - return; - } + + return tooltip_buffer; + }; + + type_tooltips.emplace_back(create_tooltip_buffer, this, get_buffer()->create_mark(start), + get_buffer()->create_mark(end)); + type_tooltips.show(); + return; + } + } + } } - } } - } } -Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language): - BaseView(file_path, language), Source::ClangViewParse(file_path, language), autocomplete(this, interactive_completion, last_keyval, true) { - non_interactive_completion=[this] { - if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) - return; - autocomplete.run(); - }; - - autocomplete.is_processing=[this] { - return parse_state==ParseState::PROCESSING; - }; - - autocomplete.reparse=[this] { - selected_completion_string=nullptr; - code_complete_results=nullptr; - soft_reparse(true); - }; - - autocomplete.cancel_reparse=[this] { - delayed_reparse_connection.disconnect(); - }; - - autocomplete.get_parse_lock=[this]() { - return std::make_unique<std::lock_guard<std::mutex>>(parse_mutex); - }; - - autocomplete.stop_parse=[this]() { - parse_process_state=ParseProcessState::IDLE; - }; - - // Activate argument completions - get_buffer()->signal_changed().connect([this] { - if(!interactive_completion) - return; - if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) - return; - if(!has_focus()) - return; - if(show_arguments) - autocomplete.stop(); - show_arguments=false; - delayed_show_arguments_connection.disconnect(); - delayed_show_arguments_connection=Glib::signal_timeout().connect([this]() { - if(get_buffer()->get_has_selection()) +Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::path &file_path, + Glib::RefPtr<Gsv::Language> language) : + BaseView(file_path, language), Source::ClangViewParse(file_path, language), + autocomplete(this, interactive_completion, last_keyval, true) { + non_interactive_completion = [this] { + if (CompletionDialog::get() && CompletionDialog::get()->is_visible()) + return; + autocomplete.run(); + }; + + autocomplete.is_processing = [this] { + return parse_state == ParseState::PROCESSING; + }; + + autocomplete.reparse = [this] { + selected_completion_string = nullptr; + code_complete_results = nullptr; + soft_reparse(true); + }; + + autocomplete.cancel_reparse = [this] { + delayed_reparse_connection.disconnect(); + }; + + autocomplete.get_parse_lock = [this]() { + return std::make_unique<std::lock_guard<std::mutex>>(parse_mutex); + }; + + autocomplete.stop_parse = [this]() { + parse_process_state = ParseProcessState::IDLE; + }; + + // Activate argument completions + get_buffer()->signal_changed().connect([this] { + if (!interactive_completion) + return; + if (CompletionDialog::get() && CompletionDialog::get()->is_visible()) + return; + if (!has_focus()) + return; + if (show_arguments) + autocomplete.stop(); + show_arguments = false; + delayed_show_arguments_connection.disconnect(); + delayed_show_arguments_connection = Glib::signal_timeout().connect([this]() { + if (get_buffer()->get_has_selection()) + return false; + if (CompletionDialog::get() && CompletionDialog::get()->is_visible()) + return false; + if (!has_focus()) + return false; + if (is_possible_parameter()) { + autocomplete.stop(); + autocomplete.run(); + } + return false; + }, 500); + }, false); + + // Remove argument completions + signal_key_press_event().connect([this](GdkEventKey *key) { + if (show_arguments && CompletionDialog::get() && CompletionDialog::get()->is_visible() && + key->keyval != GDK_KEY_Down && key->keyval != GDK_KEY_Up && + key->keyval != GDK_KEY_Return && key->keyval != GDK_KEY_KP_Enter && + key->keyval != GDK_KEY_ISO_Left_Tab && key->keyval != GDK_KEY_Tab && + (key->keyval < GDK_KEY_Shift_L || key->keyval > GDK_KEY_Hyper_R)) { + get_buffer()->erase(CompletionDialog::get()->start_mark->get_iter(), + get_buffer()->get_insert()->get_iter()); + CompletionDialog::get()->hide(); + } return false; - if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) + }, false); + + autocomplete.is_continue_key = [](guint keyval) { + if ((keyval >= '0' && keyval <= '9') || (keyval >= 'a' && keyval <= 'z') || (keyval >= 'A' && keyval <= 'Z') || + keyval == '_') + return true; + return false; - if(!has_focus()) + }; + + autocomplete.is_restart_key = [this](guint keyval) { + auto iter = get_buffer()->get_insert()->get_iter(); + iter.backward_chars(2); + if (keyval == '.' || (keyval == ':' && *iter == ':') || (keyval == '>' && *iter == '-')) + return true; return false; - if(is_possible_parameter()) { - autocomplete.stop(); - autocomplete.run(); - } - return false; - }, 500); - }, false); - - // Remove argument completions - signal_key_press_event().connect([this](GdkEventKey *key) { - if(show_arguments && CompletionDialog::get() && CompletionDialog::get()->is_visible() && - key->keyval != GDK_KEY_Down && key->keyval != GDK_KEY_Up && - key->keyval != GDK_KEY_Return && key->keyval != GDK_KEY_KP_Enter && - key->keyval != GDK_KEY_ISO_Left_Tab && key->keyval != GDK_KEY_Tab && - (key->keyval < GDK_KEY_Shift_L || key->keyval > GDK_KEY_Hyper_R)) { - get_buffer()->erase(CompletionDialog::get()->start_mark->get_iter(), get_buffer()->get_insert()->get_iter()); - CompletionDialog::get()->hide(); - } - return false; - }, false); + }; - autocomplete.is_continue_key=[](guint keyval) { - if((keyval>='0' && keyval<='9') || (keyval>='a' && keyval<='z') || (keyval>='A' && keyval<='Z') || keyval=='_') - return true; - - return false; - }; - - autocomplete.is_restart_key=[this](guint keyval) { - auto iter=get_buffer()->get_insert()->get_iter(); - iter.backward_chars(2); - if(keyval=='.' || (keyval==':' && *iter==':') || (keyval=='>' && *iter=='-')) - return true; - return false; - }; - - autocomplete.run_check=[this]() { - auto iter=get_buffer()->get_insert()->get_iter(); - iter.backward_char(); - if(!is_code_iter(iter)) - return false; - - show_arguments=false; - - std::string line=" "+get_line_before(); - const static std::regex dot_or_arrow("^.*[a-zA-Z0-9_\\)\\]\\>](\\.|->)([a-zA-Z0-9_]*)$"); - const static std::regex colon_colon("^.*::([a-zA-Z0-9_]*)$"); - const static std::regex part_of_symbol("^.*[^a-zA-Z0-9_]+([a-zA-Z0-9_]{3,})$"); - std::smatch sm; - if(std::regex_match(line, sm, dot_or_arrow)) { - { - std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); - autocomplete.prefix=sm[2].str(); - } - if(autocomplete.prefix.size()==0 || autocomplete.prefix[0]<'0' || autocomplete.prefix[0]>'9') - return true; - } - else if(std::regex_match(line, sm, colon_colon)) { - { - std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); - autocomplete.prefix=sm[1].str(); - } - if(autocomplete.prefix.size()==0 || autocomplete.prefix[0]<'0' || autocomplete.prefix[0]>'9') - return true; - } - else if(std::regex_match(line, sm, part_of_symbol)) { - { - std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); - autocomplete.prefix=sm[1].str(); - } - if(autocomplete.prefix.size()==0 || autocomplete.prefix[0]<'0' || autocomplete.prefix[0]>'9') - return true; - } - else if(is_possible_parameter()) { - show_arguments=true; - std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); - autocomplete.prefix=""; - return true; - } - else if(!interactive_completion) { - auto end_iter=get_buffer()->get_insert()->get_iter(); - auto iter=end_iter; - while(iter.backward_char() && autocomplete.is_continue_key(*iter)) {} - if(iter!=end_iter) - iter.forward_char(); - std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); - autocomplete.prefix=get_buffer()->get_text(iter, end_iter); - return true; - } - - return false; - }; - - autocomplete.before_add_rows=[this] { - status_state="autocomplete..."; - if(update_status_state) - update_status_state(this); - }; - - autocomplete.after_add_rows=[this] { - status_state=""; - if(update_status_state) - update_status_state(this); - }; - - autocomplete.on_add_rows_error=[this] { - Terminal::get().print("Error: autocomplete failed, reparsing "+this->file_path.string()+"\n", true); - selected_completion_string=nullptr; - code_complete_results=nullptr; - full_reparse(); - }; - - autocomplete.add_rows=[this](std::string &buffer, int line_number, int column) { - if(this->language && (this->language->get_id()=="chdr" || this->language->get_id()=="cpphdr")) - clangmm::remove_include_guard(buffer); - code_complete_results=std::make_unique<clangmm::CodeCompleteResults>(clang_tu->get_code_completions(buffer, line_number, column)); - if(code_complete_results->cx_results==nullptr) { - auto expected=ParseState::PROCESSING; - parse_state.compare_exchange_strong(expected, ParseState::RESTARTING); - return; - } - - if(autocomplete.state==Autocomplete::State::STARTING) { - std::string prefix_copy; - { - std::lock_guard<std::mutex> lock(autocomplete.prefix_mutex); - prefix_copy=autocomplete.prefix; - } - - completion_strings.clear(); - for (unsigned i = 0; i < code_complete_results->size(); ++i) { - auto result=code_complete_results->get(i); - if(result.available()) { - std::string text; - if(show_arguments) { - class Recursive { - public: - static void f(const clangmm::CompletionString &completion_string, std::string &text) { - for(unsigned i = 0; i < completion_string.get_num_chunks(); ++i) { - auto kind=static_cast<clangmm::CompletionChunkKind>(clang_getCompletionChunkKind(completion_string.cx_completion_string, i)); - if(kind==clangmm::CompletionChunk_Optional) - f(clangmm::CompletionString(clang_getCompletionChunkCompletionString(completion_string.cx_completion_string, i)), text); - else if(kind==clangmm::CompletionChunk_CurrentParameter) { - auto chunk_cstr=clangmm::String(clang_getCompletionChunkText(completion_string.cx_completion_string, i)); - text+=chunk_cstr.c_str; - } - } - } - }; - Recursive::f(result, text); - if(!text.empty()) { - bool already_added=false; - for(auto &row: autocomplete.rows) { - if(row==text) { - already_added=true; - break; - } - } - if(!already_added) { - autocomplete.rows.emplace_back(std::move(text)); - completion_strings.emplace_back(result.cx_completion_string); - } + autocomplete.run_check = [this]() { + auto iter = get_buffer()->get_insert()->get_iter(); + iter.backward_char(); + if (!is_code_iter(iter)) + return false; + + show_arguments = false; + + std::string line = " " + get_line_before(); + const static std::regex dot_or_arrow("^.*[a-zA-Z0-9_\\)\\]\\>](\\.|->)([a-zA-Z0-9_]*)$"); + const static std::regex colon_colon("^.*::([a-zA-Z0-9_]*)$"); + const static std::regex part_of_symbol("^.*[^a-zA-Z0-9_]+([a-zA-Z0-9_]{3,})$"); + std::smatch sm; + if (std::regex_match(line, sm, dot_or_arrow)) { + { + std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); + autocomplete.prefix = sm[2].str(); } - } - else { - std::string return_text; - bool match=false; - for(unsigned i = 0; i < result.get_num_chunks(); ++i) { - auto kind=static_cast<clangmm::CompletionChunkKind>(clang_getCompletionChunkKind(result.cx_completion_string, i)); - if(kind!=clangmm::CompletionChunk_Informative) { - auto chunk_cstr=clangmm::String(clang_getCompletionChunkText(result.cx_completion_string, i)); - if(kind==clangmm::CompletionChunk_TypedText) { - if(strlen(chunk_cstr.c_str)>=prefix_copy.size() && prefix_copy.compare(0, prefix_copy.size(), chunk_cstr.c_str, prefix_copy.size())==0) - match = true; - else - break; - } - if(kind==clangmm::CompletionChunk_ResultType) - return_text=std::string(" → ")+chunk_cstr.c_str; - else - text+=chunk_cstr.c_str; - } + if (autocomplete.prefix.size() == 0 || autocomplete.prefix[0] < '0' || autocomplete.prefix[0] > '9') + return true; + } else if (std::regex_match(line, sm, colon_colon)) { + { + std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); + autocomplete.prefix = sm[1].str(); } - if(match && !text.empty()) { - if(!return_text.empty()) - text+=return_text; - autocomplete.rows.emplace_back(std::move(text)); - completion_strings.emplace_back(result.cx_completion_string); + if (autocomplete.prefix.size() == 0 || autocomplete.prefix[0] < '0' || autocomplete.prefix[0] > '9') + return true; + } else if (std::regex_match(line, sm, part_of_symbol)) { + { + std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); + autocomplete.prefix = sm[1].str(); } - } - } - } - } - }; - - autocomplete.on_show = [this] { - hide_tooltips(); - }; - - autocomplete.on_hide = [this] { - selected_completion_string=nullptr; - code_complete_results=nullptr; - }; - - autocomplete.on_changed = [this](unsigned int index, const std::string &text) { - selected_completion_string=completion_strings[index]; - }; - - autocomplete.on_select = [this](unsigned int index, const std::string &text, bool hide_window) { - std::string row; - auto pos=text.find(" → "); - if(pos!=std::string::npos) - row=text.substr(0, pos); - else - row=text; - //erase existing variable or function before insert iter - get_buffer()->erase(CompletionDialog::get()->start_mark->get_iter(), get_buffer()->get_insert()->get_iter()); - //do not insert template argument or function parameters if they already exist - auto iter=get_buffer()->get_insert()->get_iter(); - if(*iter=='<' || *iter=='(') { - auto bracket_pos=row.find(*iter); - if(bracket_pos!=std::string::npos) { - row=row.substr(0, bracket_pos); - } - } - //Fixes for the most commonly used stream manipulators - auto manipulators_map=autocomplete_manipulators_map(); - auto it=manipulators_map.find(row); - if(it!=manipulators_map.end()) - row=it->second; - //Do not insert template argument, function parameters or ':' unless hide_window is true - if(!hide_window) { - for(size_t c=0;c<row.size();++c) { - if(row[c]=='<' || row[c]=='(' || row[c]==':') { - row.erase(c); - break; + if (autocomplete.prefix.size() == 0 || autocomplete.prefix[0] < '0' || autocomplete.prefix[0] > '9') + return true; + } else if (is_possible_parameter()) { + show_arguments = true; + std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); + autocomplete.prefix = ""; + return true; + } else if (!interactive_completion) { + auto end_iter = get_buffer()->get_insert()->get_iter(); + auto iter = end_iter; + while (iter.backward_char() && autocomplete.is_continue_key(*iter)) {} + if (iter != end_iter) + iter.forward_char(); + std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); + autocomplete.prefix = get_buffer()->get_text(iter, end_iter); + return true; } - } - } - get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), row); - //if selection is finalized, select text inside template arguments or function parameters - if(hide_window) { - size_t start_pos=std::string::npos; - size_t end_pos=std::string::npos; - if(show_arguments) { - start_pos=0; - end_pos=row.size(); - } - else { - auto para_pos=row.find('('); - auto angle_pos=row.find('<'); - if(angle_pos<para_pos) { - start_pos=angle_pos+1; - end_pos=row.find('>'); + + return false; + }; + + autocomplete.before_add_rows = [this] { + status_state = "autocomplete..."; + if (update_status_state) + update_status_state(this); + }; + + autocomplete.after_add_rows = [this] { + status_state = ""; + if (update_status_state) + update_status_state(this); + }; + + autocomplete.on_add_rows_error = [this] { + Terminal::get().print("Error: autocomplete failed, reparsing " + this->file_path.string() + "\n", true); + selected_completion_string = nullptr; + code_complete_results = nullptr; + full_reparse(); + }; + + autocomplete.add_rows = [this](std::string &buffer, int line_number, int column) { + if (this->language && (this->language->get_id() == "chdr" || this->language->get_id() == "cpphdr")) + clangmm::remove_include_guard(buffer); + code_complete_results = std::make_unique<clangmm::CodeCompleteResults>( + clang_tu->get_code_completions(buffer, line_number, column)); + if (code_complete_results->cx_results == nullptr) { + auto expected = ParseState::PROCESSING; + parse_state.compare_exchange_strong(expected, ParseState::RESTARTING); + return; } - else if(para_pos!=std::string::npos) { - start_pos=para_pos+1; - end_pos=row.size()-1; + + if (autocomplete.state == Autocomplete::State::STARTING) { + std::string prefix_copy; + { + std::lock_guard<std::mutex> lock(autocomplete.prefix_mutex); + prefix_copy = autocomplete.prefix; + } + + completion_strings.clear(); + for (unsigned i = 0; i < code_complete_results->size(); ++i) { + auto result = code_complete_results->get(i); + if (result.available()) { + std::string text; + if (show_arguments) { + class Recursive { + public: + static void f(const clangmm::CompletionString &completion_string, std::string &text) { + for (unsigned i = 0; i < completion_string.get_num_chunks(); ++i) { + auto kind = static_cast<clangmm::CompletionChunkKind>(clang_getCompletionChunkKind( + completion_string.cx_completion_string, i)); + if (kind == clangmm::CompletionChunk_Optional) + f(clangmm::CompletionString(clang_getCompletionChunkCompletionString( + completion_string.cx_completion_string, i)), text); + else if (kind == clangmm::CompletionChunk_CurrentParameter) { + auto chunk_cstr = clangmm::String( + clang_getCompletionChunkText(completion_string.cx_completion_string, + i)); + text += chunk_cstr.c_str; + } + } + } + }; + Recursive::f(result, text); + if (!text.empty()) { + bool already_added = false; + for (auto &row: autocomplete.rows) { + if (row == text) { + already_added = true; + break; + } + } + if (!already_added) { + autocomplete.rows.emplace_back(std::move(text)); + completion_strings.emplace_back(result.cx_completion_string); + } + } + } else { + std::string return_text; + bool match = false; + for (unsigned i = 0; i < result.get_num_chunks(); ++i) { + auto kind = static_cast<clangmm::CompletionChunkKind>(clang_getCompletionChunkKind( + result.cx_completion_string, i)); + if (kind != clangmm::CompletionChunk_Informative) { + auto chunk_cstr = clangmm::String( + clang_getCompletionChunkText(result.cx_completion_string, i)); + if (kind == clangmm::CompletionChunk_TypedText) { + if (strlen(chunk_cstr.c_str) >= prefix_copy.size() && + prefix_copy.compare(0, prefix_copy.size(), chunk_cstr.c_str, + prefix_copy.size()) == 0) + match = true; + else + break; + } + if (kind == clangmm::CompletionChunk_ResultType) + return_text = std::string(" → ") + chunk_cstr.c_str; + else + text += chunk_cstr.c_str; + } + } + if (match && !text.empty()) { + if (!return_text.empty()) + text += return_text; + autocomplete.rows.emplace_back(std::move(text)); + completion_strings.emplace_back(result.cx_completion_string); + } + } + } + } } - if(start_pos==std::string::npos || end_pos==std::string::npos) { - if((start_pos=row.find('\"'))!=std::string::npos) { - end_pos=row.find('\"', start_pos+1); - ++start_pos; - } + }; + + autocomplete.on_show = [this] { + hide_tooltips(); + }; + + autocomplete.on_hide = [this] { + selected_completion_string = nullptr; + code_complete_results = nullptr; + }; + + autocomplete.on_changed = [this](unsigned int index, const std::string &text) { + selected_completion_string = completion_strings[index]; + }; + + autocomplete.on_select = [this](unsigned int index, const std::string &text, bool hide_window) { + std::string row; + auto pos = text.find(" → "); + if (pos != std::string::npos) + row = text.substr(0, pos); + else + row = text; + //erase existing variable or function before insert iter + get_buffer()->erase(CompletionDialog::get()->start_mark->get_iter(), get_buffer()->get_insert()->get_iter()); + //do not insert template argument or function parameters if they already exist + auto iter = get_buffer()->get_insert()->get_iter(); + if (*iter == '<' || *iter == '(') { + auto bracket_pos = row.find(*iter); + if (bracket_pos != std::string::npos) { + row = row.substr(0, bracket_pos); + } } - } - if(start_pos==std::string::npos || end_pos==std::string::npos) { - if((start_pos=row.find(' '))!=std::string::npos) { - std::vector<std::string> parameters={"expression", "arguments", "identifier", "type name", "qualifier::name", "macro", "condition"}; - for(auto ¶meter: parameters) { - if((start_pos=row.find(parameter, start_pos+1))!=std::string::npos) { - end_pos=start_pos+parameter.size(); - break; + //Fixes for the most commonly used stream manipulators + auto manipulators_map = autocomplete_manipulators_map(); + auto it = manipulators_map.find(row); + if (it != manipulators_map.end()) + row = it->second; + //Do not insert template argument, function parameters or ':' unless hide_window is true + if (!hide_window) { + for (size_t c = 0; c < row.size(); ++c) { + if (row[c] == '<' || row[c] == '(' || row[c] == ':') { + row.erase(c); + break; + } } - } } - } - - if(start_pos!=std::string::npos && end_pos!=std::string::npos) { - int start_offset=CompletionDialog::get()->start_mark->get_iter().get_offset()+start_pos; - int end_offset=CompletionDialog::get()->start_mark->get_iter().get_offset()+end_pos; - auto size=get_buffer()->size(); - if(start_offset!=end_offset && start_offset<size && end_offset<size) - get_buffer()->select_range(get_buffer()->get_iter_at_offset(start_offset), get_buffer()->get_iter_at_offset(end_offset)); - } - else { - //new autocomplete after for instance when selecting "std::" - auto iter=get_buffer()->get_insert()->get_iter(); - if(iter.backward_char() && *iter==':') { - autocomplete.run(); - return; + get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), row); + //if selection is finalized, select text inside template arguments or function parameters + if (hide_window) { + size_t start_pos = std::string::npos; + size_t end_pos = std::string::npos; + if (show_arguments) { + start_pos = 0; + end_pos = row.size(); + } else { + auto para_pos = row.find('('); + auto angle_pos = row.find('<'); + if (angle_pos < para_pos) { + start_pos = angle_pos + 1; + end_pos = row.find('>'); + } else if (para_pos != std::string::npos) { + start_pos = para_pos + 1; + end_pos = row.size() - 1; + } + if (start_pos == std::string::npos || end_pos == std::string::npos) { + if ((start_pos = row.find('\"')) != std::string::npos) { + end_pos = row.find('\"', start_pos + 1); + ++start_pos; + } + } + } + if (start_pos == std::string::npos || end_pos == std::string::npos) { + if ((start_pos = row.find(' ')) != std::string::npos) { + std::vector<std::string> parameters = {"expression", "arguments", "identifier", "type name", + "qualifier::name", "macro", "condition"}; + for (auto ¶meter: parameters) { + if ((start_pos = row.find(parameter, start_pos + 1)) != std::string::npos) { + end_pos = start_pos + parameter.size(); + break; + } + } + } + } + + if (start_pos != std::string::npos && end_pos != std::string::npos) { + int start_offset = CompletionDialog::get()->start_mark->get_iter().get_offset() + start_pos; + int end_offset = CompletionDialog::get()->start_mark->get_iter().get_offset() + end_pos; + auto size = get_buffer()->size(); + if (start_offset != end_offset && start_offset < size && end_offset < size) + get_buffer()->select_range(get_buffer()->get_iter_at_offset(start_offset), + get_buffer()->get_iter_at_offset(end_offset)); + } else { + //new autocomplete after for instance when selecting "std::" + auto iter = get_buffer()->get_insert()->get_iter(); + if (iter.backward_char() && *iter == ':') { + autocomplete.run(); + return; + } + } } - } - } - }; - - autocomplete.get_tooltip = [this](unsigned int index) { - return clangmm::to_string(clang_getCompletionBriefComment(completion_strings[index])); - }; + }; + + autocomplete.get_tooltip = [this](unsigned int index) { + return clangmm::to_string(clang_getCompletionBriefComment(completion_strings[index])); + }; } bool Source::ClangViewAutocomplete::is_possible_parameter() { - auto iter=get_buffer()->get_insert()->get_iter(); - if(iter.backward_char() && (!interactive_completion || last_keyval=='(' || last_keyval==',' || last_keyval==' ' || - last_keyval==GDK_KEY_Return || last_keyval==GDK_KEY_KP_Enter)) { - while((*iter==' ' || *iter=='\t' || *iter=='\n' || *iter=='\r') && iter.backward_char()) {} - if(*iter=='(' || *iter==',') - return true; - } - return false; + auto iter = get_buffer()->get_insert()->get_iter(); + if (iter.backward_char() && + (!interactive_completion || last_keyval == '(' || last_keyval == ',' || last_keyval == ' ' || + last_keyval == GDK_KEY_Return || last_keyval == GDK_KEY_KP_Enter)) { + while ((*iter == ' ' || *iter == '\t' || *iter == '\n' || *iter == '\r') && iter.backward_char()) {} + if (*iter == '(' || *iter == ',') + return true; + } + return false; } const std::unordered_map<std::string, std::string> &Source::ClangViewAutocomplete::autocomplete_manipulators_map() { - //TODO: feel free to add more - static std::unordered_map<std::string, std::string> map={ - {"endl(basic_ostream<_CharT, _Traits> &__os)", "endl"}, - {"flush(basic_ostream<_CharT, _Traits> &__os)", "flush"}, - {"hex(std::ios_base &__str)", "hex"}, //clang++ headers - {"hex(std::ios_base &__base)", "hex"}, //g++ headers - {"dec(std::ios_base &__str)", "dec"}, - {"dec(std::ios_base &__base)", "dec"} - }; - return map; + //TODO: feel free to add more + static std::unordered_map<std::string, std::string> map = { + {"endl(basic_ostream<_CharT, _Traits> &__os)", "endl"}, + {"flush(basic_ostream<_CharT, _Traits> &__os)", "flush"}, + {"hex(std::ios_base &__str)", "hex"}, //clang++ headers + {"hex(std::ios_base &__base)", "hex"}, //g++ headers + {"dec(std::ios_base &__str)", "dec"}, + {"dec(std::ios_base &__base)", "dec"} + }; + return map; } -Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language) : - BaseView(file_path, language), Source::ClangViewParse(file_path, language) { - similar_identifiers_tag=get_buffer()->create_tag(); - similar_identifiers_tag->property_weight()=Pango::WEIGHT_ULTRAHEAVY; - - get_buffer()->signal_changed().connect([this]() { - if(last_tagged_identifier) { - for(auto &mark: similar_identifiers_marks) { - get_buffer()->remove_tag(similar_identifiers_tag, mark.first->get_iter(), mark.second->get_iter()); - get_buffer()->delete_mark(mark.first); - get_buffer()->delete_mark(mark.second); - } - similar_identifiers_marks.clear(); - last_tagged_identifier=Identifier(); - } - }); - - get_token_spelling=[this]() { - if(!parsed) { - Info::get().print("Buffer is parsing"); - return std::string(); - } - auto identifier=get_identifier(); - if(identifier.spelling.empty() || - identifier.spelling=="::" || identifier.spelling=="," || identifier.spelling=="=" || - identifier.spelling=="(" || identifier.spelling==")" || - identifier.spelling=="[" || identifier.spelling=="]") { - Info::get().print("No valid symbol found at current cursor location"); - return std::string(); - } - return identifier.spelling; - }; - - rename_similar_tokens=[this](const std::string &text) { - if(!parsed) { - Info::get().print("Buffer is parsing"); - return; - } - auto identifier=get_identifier(); - if(identifier) { - wait_parsing(); - - std::vector<clangmm::TranslationUnit*> translation_units; - translation_units.emplace_back(clang_tu.get()); - for(auto &view: views) { - if(view!=this) { - if(auto clang_view=dynamic_cast<Source::ClangView*>(view)) - translation_units.emplace_back(clang_view->clang_tu.get()); +Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file_path, + Glib::RefPtr<Gsv::Language> language) : + BaseView(file_path, language), Source::ClangViewParse(file_path, language) { + similar_identifiers_tag = get_buffer()->create_tag(); + similar_identifiers_tag->property_weight() = Pango::WEIGHT_ULTRAHEAVY; + + get_buffer()->signal_changed().connect([this]() { + if (last_tagged_identifier) { + for (auto &mark: similar_identifiers_marks) { + get_buffer()->remove_tag(similar_identifiers_tag, mark.first->get_iter(), mark.second->get_iter()); + get_buffer()->delete_mark(mark.first); + get_buffer()->delete_mark(mark.second); + } + similar_identifiers_marks.clear(); + last_tagged_identifier = Identifier(); } - } - - auto build=Project::Build::create(this->file_path); - auto usages=Usages::Clang::get_usages(build->project_path, build->get_default_path(), build->get_debug_path(), identifier.spelling, identifier.cursor, translation_units); - - std::vector<Source::View*> renamed_views; - std::vector<Usages::Clang::Usages*> usages_renamed; - for(auto &usage: usages) { - size_t line_c=usage.lines.size()-1; - auto view_it=views.end(); - for(auto it=views.begin();it!=views.end();++it) { - if((*it)->file_path==usage.path) { - view_it=it; - break; - } + }); + + get_token_spelling = [this]() { + if (!parsed) { + Info::get().print("Buffer is parsing"); + return std::string(); + } + auto identifier = get_identifier(); + if (identifier.spelling.empty() || + identifier.spelling == "::" || identifier.spelling == "," || identifier.spelling == "=" || + identifier.spelling == "(" || identifier.spelling == ")" || + identifier.spelling == "[" || identifier.spelling == "]") { + Info::get().print("No valid symbol found at current cursor location"); + return std::string(); } - if(view_it!=views.end()) { - (*view_it)->get_buffer()->begin_user_action(); - for(auto offset_it=usage.offsets.rbegin();offset_it!=usage.offsets.rend();++offset_it) { - auto start_iter=(*view_it)->get_buffer()->get_iter_at_line_index(offset_it->first.line-1, offset_it->first.index-1); - auto end_iter=(*view_it)->get_buffer()->get_iter_at_line_index(offset_it->second.line-1, offset_it->second.index-1); - (*view_it)->get_buffer()->erase(start_iter, end_iter); - start_iter=(*view_it)->get_buffer()->get_iter_at_line_index(offset_it->first.line-1, offset_it->first.index-1); - (*view_it)->get_buffer()->insert(start_iter, text); - if(offset_it->first.index-1<usage.lines[line_c].size()) - usage.lines[line_c].replace(offset_it->first.index-1, offset_it->second.index-offset_it->first.index, text); - --line_c; - } - (*view_it)->get_buffer()->end_user_action(); - (*view_it)->save(); - renamed_views.emplace_back(*view_it); - usages_renamed.emplace_back(&usage); + return identifier.spelling; + }; + + rename_similar_tokens = [this](const std::string &text) { + if (!parsed) { + Info::get().print("Buffer is parsing"); + return; } - else { - std::string buffer; - { - std::ifstream stream(usage.path.string(), std::ifstream::binary); - if(stream) - buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>()); - } - std::ofstream stream(usage.path.string(), std::ifstream::binary); - if(!buffer.empty() && stream) { - std::vector<size_t> lines_start_pos={0}; - for(size_t c=0;c<buffer.size();++c) { - if(buffer[c]=='\n') - lines_start_pos.emplace_back(c+1); + auto identifier = get_identifier(); + if (identifier) { + wait_parsing(); + + std::vector<clangmm::TranslationUnit *> translation_units; + translation_units.emplace_back(clang_tu.get()); + for (auto &view: views) { + if (view != this) { + if (auto clang_view = dynamic_cast<Source::ClangView *>(view)) + translation_units.emplace_back(clang_view->clang_tu.get()); + } } - for(auto offset_it=usage.offsets.rbegin();offset_it!=usage.offsets.rend();++offset_it) { - auto start_line=offset_it->first.line-1; - auto end_line=offset_it->second.line-1; - if(start_line<lines_start_pos.size() && end_line<lines_start_pos.size()) { - auto start=lines_start_pos[start_line]+offset_it->first.index-1; - auto end=lines_start_pos[end_line]+offset_it->second.index-1; - if(start<buffer.size() && end<=buffer.size()) - buffer.replace(start, end-start, text); - } - if(offset_it->first.index-1<usage.lines[line_c].size()) - usage.lines[line_c].replace(offset_it->first.index-1, offset_it->second.index-offset_it->first.index, text); - --line_c; + + auto build = Project::Build::create(this->file_path); + auto usages = Usages::Clang::get_usages(build->project_path, build->get_default_path(), + build->get_debug_path(), identifier.spelling, identifier.cursor, + translation_units); + + std::vector<Source::View *> renamed_views; + std::vector<Usages::Clang::Usages *> usages_renamed; + for (auto &usage: usages) { + size_t line_c = usage.lines.size() - 1; + auto view_it = views.end(); + for (auto it = views.begin(); it != views.end(); ++it) { + if ((*it)->file_path == usage.path) { + view_it = it; + break; + } + } + if (view_it != views.end()) { + (*view_it)->get_buffer()->begin_user_action(); + for (auto offset_it = usage.offsets.rbegin(); offset_it != usage.offsets.rend(); ++offset_it) { + auto start_iter = (*view_it)->get_buffer()->get_iter_at_line_index(offset_it->first.line - 1, + offset_it->first.index - 1); + auto end_iter = (*view_it)->get_buffer()->get_iter_at_line_index(offset_it->second.line - 1, + offset_it->second.index - 1); + (*view_it)->get_buffer()->erase(start_iter, end_iter); + start_iter = (*view_it)->get_buffer()->get_iter_at_line_index(offset_it->first.line - 1, + offset_it->first.index - 1); + (*view_it)->get_buffer()->insert(start_iter, text); + if (offset_it->first.index - 1 < usage.lines[line_c].size()) + usage.lines[line_c].replace(offset_it->first.index - 1, + offset_it->second.index - offset_it->first.index, text); + --line_c; + } + (*view_it)->get_buffer()->end_user_action(); + (*view_it)->save(); + renamed_views.emplace_back(*view_it); + usages_renamed.emplace_back(&usage); + } else { + std::string buffer; + { + std::ifstream stream(usage.path.string(), std::ifstream::binary); + if (stream) + buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>()); + } + std::ofstream stream(usage.path.string(), std::ifstream::binary); + if (!buffer.empty() && stream) { + std::vector<size_t> lines_start_pos = {0}; + for (size_t c = 0; c < buffer.size(); ++c) { + if (buffer[c] == '\n') + lines_start_pos.emplace_back(c + 1); + } + for (auto offset_it = usage.offsets.rbegin(); offset_it != usage.offsets.rend(); ++offset_it) { + auto start_line = offset_it->first.line - 1; + auto end_line = offset_it->second.line - 1; + if (start_line < lines_start_pos.size() && end_line < lines_start_pos.size()) { + auto start = lines_start_pos[start_line] + offset_it->first.index - 1; + auto end = lines_start_pos[end_line] + offset_it->second.index - 1; + if (start < buffer.size() && end <= buffer.size()) + buffer.replace(start, end - start, text); + } + if (offset_it->first.index - 1 < usage.lines[line_c].size()) + usage.lines[line_c].replace(offset_it->first.index - 1, + offset_it->second.index - offset_it->first.index, text); + --line_c; + } + stream.write(buffer.data(), buffer.size()); + usages_renamed.emplace_back(&usage); + } else + Terminal::get().print("Error: could not write to file " + usage.path.string() + '\n', true); + } + } + + if (!usages_renamed.empty()) { + Terminal::get().print("Renamed "); + Terminal::get().print(identifier.spelling, true); + Terminal::get().print(" to "); + Terminal::get().print(text, true); + Terminal::get().print(" at:\n"); } - stream.write(buffer.data(), buffer.size()); - usages_renamed.emplace_back(&usage); - } - else - Terminal::get().print("Error: could not write to file "+usage.path.string()+'\n', true); + for (auto &usage: usages_renamed) { + size_t line_c = 0; + for (auto &offset: usage->offsets) { + Terminal::get().print( + filesystem::get_short_path(usage->path).string() + ':' + std::to_string(offset.first.line) + + ':' + std::to_string(offset.first.index) + ": "); + auto &line = usage->lines[line_c]; + auto index = offset.first.index - 1; + unsigned start = 0; + for (auto &chr: line) { + if (chr != ' ' && chr != '\t') + break; + ++start; + } + if (start < line.size() && index + text.size() < line.size()) { + Terminal::get().print(line.substr(start, index - start)); + Terminal::get().print(line.substr(index, text.size()), true); + Terminal::get().print(line.substr(index + text.size())); + } + Terminal::get().print("\n"); + ++line_c; + } + } + + for (auto &view: renamed_views) + view->soft_reparse_needed = false; } - } - - if(!usages_renamed.empty()) { - Terminal::get().print("Renamed "); - Terminal::get().print(identifier.spelling, true); - Terminal::get().print(" to "); - Terminal::get().print(text, true); - Terminal::get().print(" at:\n"); - } - for(auto &usage: usages_renamed) { - size_t line_c=0; - for(auto &offset: usage->offsets) { - Terminal::get().print(filesystem::get_short_path(usage->path).string()+':'+std::to_string(offset.first.line)+':'+std::to_string(offset.first.index)+": "); - auto &line=usage->lines[line_c]; - auto index=offset.first.index-1; - unsigned start=0; - for(auto &chr: line) { - if(chr!=' ' && chr!='\t') - break; - ++start; - } - if(start<line.size() && index+text.size()<line.size()) { - Terminal::get().print(line.substr(start, index-start)); - Terminal::get().print(line.substr(index, text.size()), true); - Terminal::get().print(line.substr(index+text.size())); - } - Terminal::get().print("\n"); - ++line_c; + }; + + get_buffer()->signal_mark_set().connect( + [this](const Gtk::TextBuffer::iterator &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { + if (mark->get_name() == "insert") { + delayed_tag_similar_identifiers_connection.disconnect(); + delayed_tag_similar_identifiers_connection = Glib::signal_timeout().connect([this]() { + auto identifier = get_identifier(); + tag_similar_identifiers(identifier); + return false; + }, 100); + } + }); + + auto declaration_location = [this]() { + auto identifier = get_identifier(); + if (identifier) { + auto source_location = identifier.cursor.get_canonical().get_source_location(); + auto offset = source_location.get_offset(); + return Offset(offset.line - 1, offset.index - 1, source_location.get_path()); + } else { + // If cursor is at an include line, return offset to included file + const static std::regex include_regex("^[ \t]*#[ \t]*include[ \t]*[<\"]([^<>\"]+)[>\"].*$"); + std::smatch sm; + auto line = get_line(); + if (std::regex_match(line, sm, include_regex)) { + struct ClientData { + boost::filesystem::path &file_path; + std::string found_include; + int line_nr; + std::string sm_str; + }; + ClientData client_data{this->file_path, std::string(), + get_buffer()->get_insert()->get_iter().get_line(), sm[1].str()}; + + // Attempt to find the 100% correct include file first + clang_getInclusions(clang_tu->cx_tu, + [](CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, + CXClientData client_data_) { + auto client_data = static_cast<ClientData *>(client_data_); + if (client_data->found_include.empty() && include_len > 0) { + auto source_location = clangmm::SourceLocation(inclusion_stack[0]); + if (static_cast<int>(source_location.get_offset().line) - 1 == + client_data->line_nr && + filesystem::get_normal_path(source_location.get_path()) == + client_data->file_path) + client_data->found_include = clangmm::to_string( + clang_getFileName(included_file)); + } + }, &client_data); + + if (!client_data.found_include.empty()) + return Offset(0, 0, client_data.found_include); + + // Find a matching include file if no include was found previously + clang_getInclusions(clang_tu->cx_tu, + [](CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, + CXClientData client_data_) { + auto client_data = static_cast<ClientData *>(client_data_); + if (client_data->found_include.empty()) { + for (unsigned c = 1; c < include_len; ++c) { + auto source_location = clangmm::SourceLocation(inclusion_stack[c]); + if (static_cast<int>(source_location.get_offset().line) - 1 <= + client_data->line_nr && + filesystem::get_normal_path(source_location.get_path()) == + client_data->file_path) { + auto included_file_str = clangmm::to_string( + clang_getFileName(included_file)); + if (included_file_str.size() >= client_data->sm_str.size() && + included_file_str.compare( + included_file_str.size() - client_data->sm_str.size(), + client_data->sm_str.size(), client_data->sm_str) == 0) { + client_data->found_include = included_file_str; + break; + } + } + } + } + }, &client_data); + + if (!client_data.found_include.empty()) + return Offset(0, 0, client_data.found_include); + } } - } - - for(auto &view: renamed_views) - view->soft_reparse_needed=false; - } - }; - - get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator& iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark>& mark){ - if(mark->get_name()=="insert") { - delayed_tag_similar_symbols_connection.disconnect(); - delayed_tag_similar_symbols_connection=Glib::signal_timeout().connect([this]() { - auto identifier=get_identifier(); - tag_similar_identifiers(identifier); - return false; - }, 100); - } - }); - - auto declaration_location=[this]() { - auto identifier=get_identifier(); - if(identifier) { - auto source_location=identifier.cursor.get_canonical().get_source_location(); - auto offset=source_location.get_offset(); - return Offset(offset.line-1, offset.index-1, source_location.get_path()); - } - else { - // If cursor is at an include line, return offset to included file - const static std::regex include_regex("^[ \t]*#[ \t]*include[ \t]*[<\"]([^<>\"]+)[>\"].*$"); - std::smatch sm; - auto line=get_line(); - if(std::regex_match(line, sm, include_regex)) { - struct ClientData { - boost::filesystem::path &file_path; - std::string found_include; - int line_nr; - std::string sm_str; - }; - ClientData client_data{this->file_path, std::string(), get_buffer()->get_insert()->get_iter().get_line(), sm[1].str()}; - - // Attempt to find the 100% correct include file first - clang_getInclusions(clang_tu->cx_tu, [](CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData client_data_) { - auto client_data=static_cast<ClientData*>(client_data_); - if(client_data->found_include.empty() && include_len>0) { - auto source_location=clangmm::SourceLocation(inclusion_stack[0]); - if(static_cast<int>(source_location.get_offset().line)-1==client_data->line_nr && - filesystem::get_normal_path(source_location.get_path())==client_data->file_path) - client_data->found_include=clangmm::to_string(clang_getFileName(included_file)); - } - }, &client_data); - - if(!client_data.found_include.empty()) - return Offset(0, 0, client_data.found_include); - - // Find a matching include file if no include was found previously - clang_getInclusions(clang_tu->cx_tu, [](CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData client_data_) { - auto client_data=static_cast<ClientData*>(client_data_); - if(client_data->found_include.empty()) { - for(unsigned c=1;c<include_len;++c) { - auto source_location=clangmm::SourceLocation(inclusion_stack[c]); - if(static_cast<int>(source_location.get_offset().line)-1<=client_data->line_nr && - filesystem::get_normal_path(source_location.get_path())==client_data->file_path) { - auto included_file_str=clangmm::to_string(clang_getFileName(included_file)); - if(included_file_str.size()>=client_data->sm_str.size() && - included_file_str.compare(included_file_str.size()-client_data->sm_str.size(), client_data->sm_str.size(), client_data->sm_str)==0) { - client_data->found_include=included_file_str; - break; + return Offset(); + }; + + get_declaration_location = [this, declaration_location]() { + if (!parsed) { + if (selected_completion_string) { + auto completion_cursor = clangmm::CompletionString(selected_completion_string).get_cursor( + clang_tu->cx_tu); + if (completion_cursor) { + auto source_location = completion_cursor.get_source_location(); + auto source_location_offset = source_location.get_offset(); + if (CompletionDialog::get()) + CompletionDialog::get()->hide(); + auto offset = Offset(source_location_offset.line - 1, source_location_offset.index - 1, + source_location.get_path()); + + // Workaround for bug in ArchLinux's clang_getFileName() + // TODO: remove the workaround when this is fixed + auto include_path = filesystem::get_normal_path(offset.file_path); + boost::system::error_code ec; + if (!boost::filesystem::exists(include_path, ec)) + offset.file_path = "/usr/include" / include_path; + + return offset; + } else { + Info::get().print("No declaration found"); + return Offset(); } - } } - } - }, &client_data); - - if(!client_data.found_include.empty()) - return Offset(0, 0, client_data.found_include); - } - } - return Offset(); - }; - - get_declaration_location=[this, declaration_location](){ - if(!parsed) { - if(selected_completion_string) { - auto completion_cursor=clangmm::CompletionString(selected_completion_string).get_cursor(clang_tu->cx_tu); - if(completion_cursor) { - auto source_location=completion_cursor.get_source_location(); - auto source_location_offset=source_location.get_offset(); - if(CompletionDialog::get()) - CompletionDialog::get()->hide(); - auto offset=Offset(source_location_offset.line-1, source_location_offset.index-1, source_location.get_path()); - - // Workaround for bug in ArchLinux's clang_getFileName() - // TODO: remove the workaround when this is fixed - auto include_path=filesystem::get_normal_path(offset.file_path); - boost::system::error_code ec; - if(!boost::filesystem::exists(include_path, ec)) - offset.file_path="/usr/include"/include_path; - - return offset; + + Info::get().print("Buffer is parsing"); + return Offset(); } - else { - Info::get().print("No declaration found"); - return Offset(); + auto offset = declaration_location(); + if (!offset) + Info::get().print("No declaration found"); + + // Workaround for bug in ArchLinux's clang_getFileName() + // TODO: remove the workaround when this is fixed + auto include_path = filesystem::get_normal_path(offset.file_path); + boost::system::error_code ec; + if (!boost::filesystem::exists(include_path, ec)) + offset.file_path = "/usr/include" / include_path; + + return offset; + }; + + get_type_declaration_location = [this]() { + if (!parsed) { + Info::get().print("Buffer is parsing"); + return Offset(); } - } - - Info::get().print("Buffer is parsing"); - return Offset(); - } - auto offset=declaration_location(); - if(!offset) - Info::get().print("No declaration found"); - - // Workaround for bug in ArchLinux's clang_getFileName() - // TODO: remove the workaround when this is fixed - auto include_path=filesystem::get_normal_path(offset.file_path); - boost::system::error_code ec; - if(!boost::filesystem::exists(include_path, ec)) - offset.file_path="/usr/include"/include_path; - - return offset; - }; - - get_type_declaration_location=[this](){ - if(!parsed) { - Info::get().print("Buffer is parsing"); - return Offset(); - } - auto identifier=get_identifier(); - if(identifier) { - auto type_cursor=identifier.cursor.get_type().get_cursor(); - if(type_cursor) { - auto source_location=type_cursor.get_source_location(); - auto path=source_location.get_path(); - if(!path.empty()) { - auto source_location_offset=source_location.get_offset(); - auto offset=Offset(source_location_offset.line-1, source_location_offset.index-1, path); - - // Workaround for bug in ArchLinux's clang_getFileName() - // TODO: remove the workaround when this is fixed - auto include_path=filesystem::get_normal_path(offset.file_path); - boost::system::error_code ec; - if(!boost::filesystem::exists(include_path, ec)) - offset.file_path="/usr/include"/include_path; - - return offset; + auto identifier = get_identifier(); + if (identifier) { + auto type_cursor = identifier.cursor.get_type().get_cursor(); + if (type_cursor) { + auto source_location = type_cursor.get_source_location(); + auto path = source_location.get_path(); + if (!path.empty()) { + auto source_location_offset = source_location.get_offset(); + auto offset = Offset(source_location_offset.line - 1, source_location_offset.index - 1, path); + + // Workaround for bug in ArchLinux's clang_getFileName() + // TODO: remove the workaround when this is fixed + auto include_path = filesystem::get_normal_path(offset.file_path); + boost::system::error_code ec; + if (!boost::filesystem::exists(include_path, ec)) + offset.file_path = "/usr/include" / include_path; + + return offset; + } + } } - } - } - Info::get().print("No type declaration found"); - return Offset(); - }; - - auto implementation_locations=[this](const Identifier &identifier) { - std::vector<Offset> offsets; - if(identifier) { - wait_parsing(); - - //First, look for a definition cursor that is equal - auto identifier_usr=identifier.cursor.get_usr(); - for(auto &view: views) { - if(auto clang_view=dynamic_cast<Source::ClangView*>(view)) { - for(auto &token: *clang_view->clang_tokens) { - auto cursor=token.get_cursor(); - auto cursor_kind=cursor.get_kind(); - if((cursor_kind==clangmm::Cursor::Kind::FunctionDecl || cursor_kind==clangmm::Cursor::Kind::CXXMethod || - cursor_kind==clangmm::Cursor::Kind::Constructor || cursor_kind==clangmm::Cursor::Kind::Destructor || - cursor_kind==clangmm::Cursor::Kind::FunctionTemplate || cursor_kind==clangmm::Cursor::Kind::ConversionFunction) && - token.is_identifier()) { - auto token_spelling=token.get_spelling(); - if(identifier.kind==cursor.get_kind() && identifier.spelling==token_spelling && identifier_usr==cursor.get_usr()) { - if(clang_isCursorDefinition(cursor.cx_cursor)) { - Offset offset; - auto location=cursor.get_source_location(); - auto clang_offset=location.get_offset(); - offset.file_path=location.get_path(); - offset.line=clang_offset.line-1; - offset.index=clang_offset.index-1; - offsets.emplace_back(offset); + Info::get().print("No type declaration found"); + return Offset(); + }; + + auto implementation_locations = [this](const Identifier &identifier) { + std::vector<Offset> offsets; + if (identifier) { + wait_parsing(); + + //First, look for a definition cursor that is equal + auto identifier_usr = identifier.cursor.get_usr(); + for (auto &view: views) { + if (auto clang_view = dynamic_cast<Source::ClangView *>(view)) { + for (auto &token: *clang_view->clang_tokens) { + auto cursor = token.get_cursor(); + auto cursor_kind = cursor.get_kind(); + if ((cursor_kind == clangmm::Cursor::Kind::FunctionDecl || + cursor_kind == clangmm::Cursor::Kind::CXXMethod || + cursor_kind == clangmm::Cursor::Kind::Constructor || + cursor_kind == clangmm::Cursor::Kind::Destructor || + cursor_kind == clangmm::Cursor::Kind::FunctionTemplate || + cursor_kind == clangmm::Cursor::Kind::ConversionFunction) && + token.is_identifier()) { + auto token_spelling = token.get_spelling(); + if (identifier.kind == cursor.get_kind() && identifier.spelling == token_spelling && + identifier_usr == cursor.get_usr()) { + if (clang_isCursorDefinition(cursor.cx_cursor)) { + Offset offset; + auto location = cursor.get_source_location(); + auto clang_offset = location.get_offset(); + offset.file_path = location.get_path(); + offset.line = clang_offset.line - 1; + offset.index = clang_offset.index - 1; + offsets.emplace_back(offset); + } + } + } + } } - } } - } + if (!offsets.empty()) + return offsets; + + //If no implementation was found, try using clang_getCursorDefinition + auto definition = identifier.cursor.get_definition(); + if (definition) { + auto location = definition.get_source_location(); + Offset offset; + offset.file_path = location.get_path(); + auto clang_offset = location.get_offset(); + offset.line = clang_offset.line - 1; + offset.index = clang_offset.index - 1; + offsets.emplace_back(offset); + return offsets; + } + + //If no implementation was found, use declaration if it is a function template + auto canonical = identifier.cursor.get_canonical(); + auto cursor = clang_tu->get_cursor(canonical.get_source_location()); + if (cursor && cursor.get_kind() == clangmm::Cursor::Kind::FunctionTemplate) { + auto location = cursor.get_source_location(); + Offset offset; + offset.file_path = location.get_path(); + auto clang_offset = location.get_offset(); + offset.line = clang_offset.line - 1; + offset.index = clang_offset.index - 1; + offsets.emplace_back(offset); + return offsets; + } + + //If no implementation was found, try using Ctags + auto name = identifier.cursor.get_spelling(); + auto parent = identifier.cursor.get_semantic_parent(); + while (parent && parent.get_kind() != clangmm::Cursor::Kind::TranslationUnit) { + auto spelling = parent.get_spelling() + "::"; + name.insert(0, spelling); + parent = parent.get_semantic_parent(); + } + auto ctags_locations = Ctags::get_locations(this->file_path, name, + identifier.cursor.get_type_description()); + if (!ctags_locations.empty()) { + for (auto &ctags_location: ctags_locations) { + Offset offset; + offset.file_path = ctags_location.file_path; + offset.line = ctags_location.line; + offset.index = ctags_location.index; + offsets.emplace_back(offset); + } + return offsets; + } } - } - if(!offsets.empty()) - return offsets; - - //If no implementation was found, try using clang_getCursorDefinition - auto definition=identifier.cursor.get_definition(); - if(definition) { - auto location=definition.get_source_location(); - Offset offset; - offset.file_path=location.get_path(); - auto clang_offset=location.get_offset(); - offset.line=clang_offset.line-1; - offset.index=clang_offset.index-1; - offsets.emplace_back(offset); - return offsets; - } - - //If no implementation was found, use declaration if it is a function template - auto canonical=identifier.cursor.get_canonical(); - auto cursor=clang_tu->get_cursor(canonical.get_source_location()); - if(cursor && cursor.get_kind()==clangmm::Cursor::Kind::FunctionTemplate) { - auto location=cursor.get_source_location(); - Offset offset; - offset.file_path=location.get_path(); - auto clang_offset=location.get_offset(); - offset.line=clang_offset.line-1; - offset.index=clang_offset.index-1; - offsets.emplace_back(offset); return offsets; - } - - //If no implementation was found, try using Ctags - auto name=identifier.cursor.get_spelling(); - auto parent=identifier.cursor.get_semantic_parent(); - while(parent && parent.get_kind()!=clangmm::Cursor::Kind::TranslationUnit) { - auto spelling=parent.get_spelling()+"::"; - name.insert(0, spelling); - parent=parent.get_semantic_parent(); - } - auto ctags_locations=Ctags::get_locations(this->file_path, name, identifier.cursor.get_type_description()); - if(!ctags_locations.empty()) { - for(auto &ctags_location: ctags_locations) { - Offset offset; - offset.file_path=ctags_location.file_path; - offset.line=ctags_location.line; - offset.index=ctags_location.index; - offsets.emplace_back(offset); + }; + + get_implementation_locations = [this, implementation_locations]() { + if (!parsed) { + if (selected_completion_string) { + auto completion_cursor = clangmm::CompletionString(selected_completion_string).get_cursor( + clang_tu->cx_tu); + if (completion_cursor) { + auto offsets = implementation_locations( + Identifier(completion_cursor.get_token_spelling(), completion_cursor)); + if (offsets.empty()) { + Info::get().print("No implementation found"); + return std::vector<Offset>(); + } + if (CompletionDialog::get()) + CompletionDialog::get()->hide(); + + // Workaround for bug in ArchLinux's clang_getFileName() + // TODO: remove the workaround when this is fixed + for (auto &offset: offsets) { + auto include_path = filesystem::get_normal_path(offset.file_path); + boost::system::error_code ec; + if (!boost::filesystem::exists(include_path, ec)) + offset.file_path = "/usr/include" / include_path; + } + + return offsets; + } else { + Info::get().print("No implementation found"); + return std::vector<Offset>(); + } + } + + Info::get().print("Buffer is parsing"); + return std::vector<Offset>(); } - return offsets; - } - } - return offsets; - }; - - get_implementation_locations=[this, implementation_locations](){ - if(!parsed) { - if(selected_completion_string) { - auto completion_cursor=clangmm::CompletionString(selected_completion_string).get_cursor(clang_tu->cx_tu); - if(completion_cursor) { - auto offsets=implementation_locations(Identifier(completion_cursor.get_token_spelling(), completion_cursor)); - if(offsets.empty()) { + auto offsets = implementation_locations(get_identifier()); + if (offsets.empty()) Info::get().print("No implementation found"); - return std::vector<Offset>(); - } - if(CompletionDialog::get()) - CompletionDialog::get()->hide(); - - // Workaround for bug in ArchLinux's clang_getFileName() - // TODO: remove the workaround when this is fixed - for(auto &offset: offsets) { - auto include_path=filesystem::get_normal_path(offset.file_path); + + // Workaround for bug in ArchLinux's clang_getFileName() + // TODO: remove the workaround when this is fixed + for (auto &offset: offsets) { + auto include_path = filesystem::get_normal_path(offset.file_path); boost::system::error_code ec; - if(!boost::filesystem::exists(include_path, ec)) - offset.file_path="/usr/include"/include_path; - } - - return offsets; + if (!boost::filesystem::exists(include_path, ec)) + offset.file_path = "/usr/include" / include_path; } - else { - Info::get().print("No implementation found"); - return std::vector<Offset>(); + + return offsets; + }; + + get_declaration_or_implementation_locations = [this, declaration_location, implementation_locations]() { + if (!parsed) { + Info::get().print("Buffer is parsing"); + return std::vector<Offset>(); } - } - - Info::get().print("Buffer is parsing"); - return std::vector<Offset>(); - } - auto offsets=implementation_locations(get_identifier()); - if(offsets.empty()) - Info::get().print("No implementation found"); - - // Workaround for bug in ArchLinux's clang_getFileName() - // TODO: remove the workaround when this is fixed - for(auto &offset: offsets) { - auto include_path=filesystem::get_normal_path(offset.file_path); - boost::system::error_code ec; - if(!boost::filesystem::exists(include_path, ec)) - offset.file_path="/usr/include"/include_path; - } - - return offsets; - }; - - get_declaration_or_implementation_locations=[this, declaration_location, implementation_locations]() { - if(!parsed) { - Info::get().print("Buffer is parsing"); - return std::vector<Offset>(); - } - - std::vector<Offset> offsets; - - bool is_implementation=false; - auto iter=get_buffer()->get_insert()->get_iter(); - auto line=static_cast<unsigned>(iter.get_line()); - auto index=static_cast<unsigned>(iter.get_line_index()); - for(size_t c=0;c<clang_tokens->size();++c) { - auto &token=(*clang_tokens)[c]; - if(token.is_identifier()) { - auto &token_offsets=clang_tokens_offsets[c]; - if(line==token_offsets.first.line-1 && index>=token_offsets.first.index-1 && index<=token_offsets.second.index-1) { - if(clang_isCursorDefinition(token.get_cursor().cx_cursor)>0) - is_implementation=true; - break; + + std::vector<Offset> offsets; + + bool is_implementation = false; + auto iter = get_buffer()->get_insert()->get_iter(); + auto line = static_cast<unsigned>(iter.get_line()); + auto index = static_cast<unsigned>(iter.get_line_index()); + for (size_t c = 0; c < clang_tokens->size(); ++c) { + auto &token = (*clang_tokens)[c]; + if (token.is_identifier()) { + auto &token_offsets = clang_tokens_offsets[c]; + if (line == token_offsets.first.line - 1 && index >= token_offsets.first.index - 1 && + index <= token_offsets.second.index - 1) { + if (clang_isCursorDefinition(token.get_cursor().cx_cursor) > 0) + is_implementation = true; + break; + } + } } - } - } - // If cursor is at implementation, return declaration_location - if(is_implementation) { - auto offset=declaration_location(); - if(offset) - offsets.emplace_back(offset); - } - else { - auto implementation_offsets=implementation_locations(get_identifier()); - if(!implementation_offsets.empty()) { - offsets=std::move(implementation_offsets); - } - else { - auto offset=declaration_location(); - if(offset) - offsets.emplace_back(offset); - } - } - - if(offsets.empty()) - Info::get().print("No declaration or implementation found"); - - // Workaround for bug in ArchLinux's clang_getFileName() - // TODO: remove the workaround when this is fixed - for(auto &offset: offsets) { - auto include_path=filesystem::get_normal_path(offset.file_path); - boost::system::error_code ec; - if(!boost::filesystem::exists(include_path, ec)) - offset.file_path="/usr/include"/include_path; - } - - return offsets; - }; - - get_usages=[this]() { - std::vector<std::pair<Offset, std::string> > usages; - if(!parsed) { - Info::get().print("Buffer is parsing"); - return usages; - } - auto identifier=get_identifier(); - if(identifier) { - wait_parsing(); - - auto embolden_token=[](std::string &line, unsigned token_start_pos, unsigned token_end_pos) { - //markup token as bold - size_t pos=0; - while((pos=line.find('&', pos))!=std::string::npos) { - size_t pos2=line.find(';', pos+2); - if(token_start_pos>pos) { - token_start_pos+=pos2-pos; - token_end_pos+=pos2-pos; - } - else if(token_end_pos>pos) - token_end_pos+=pos2-pos; - else - break; - pos=pos2+1; + // If cursor is at implementation, return declaration_location + if (is_implementation) { + auto offset = declaration_location(); + if (offset) + offsets.emplace_back(offset); + } else { + auto implementation_offsets = implementation_locations(get_identifier()); + if (!implementation_offsets.empty()) { + offsets = std::move(implementation_offsets); + } else { + auto offset = declaration_location(); + if (offset) + offsets.emplace_back(offset); + } } - line.insert(token_end_pos, "</b>"); - line.insert(token_start_pos, "<b>"); - - size_t start_pos=0; - while(start_pos<line.size() && (line[start_pos]==' ' || line[start_pos]=='\t')) - ++start_pos; - if(start_pos>0) - line.erase(0, start_pos); - }; - - std::vector<clangmm::TranslationUnit*> translation_units; - translation_units.emplace_back(clang_tu.get()); - for(auto &view: views) { - if(view!=this) { - if(auto clang_view=dynamic_cast<Source::ClangView*>(view)) - translation_units.emplace_back(clang_view->clang_tu.get()); + + if (offsets.empty()) + Info::get().print("No declaration or implementation found"); + + // Workaround for bug in ArchLinux's clang_getFileName() + // TODO: remove the workaround when this is fixed + for (auto &offset: offsets) { + auto include_path = filesystem::get_normal_path(offset.file_path); + boost::system::error_code ec; + if (!boost::filesystem::exists(include_path, ec)) + offset.file_path = "/usr/include" / include_path; } - } - - auto build=Project::Build::create(this->file_path); - auto usages_clang=Usages::Clang::get_usages(build->project_path, build->get_default_path(), build->get_debug_path(), {identifier.spelling}, {identifier.cursor}, translation_units); - for(auto &usage: usages_clang) { - for(size_t c=0;c<usage.offsets.size();++c) { - std::string line=Glib::Markup::escape_text(usage.lines[c]); - embolden_token(line, usage.offsets[c].first.index-1, usage.offsets[c].second.index-1); - usages.emplace_back(Offset(usage.offsets[c].first.line-1, usage.offsets[c].first.index-1, usage.path), line); + + return offsets; + }; + + get_usages = [this]() { + std::vector<std::pair<Offset, std::string> > usages; + if (!parsed) { + Info::get().print("Buffer is parsing"); + return usages; } - } - } - - if(usages.empty()) - Info::get().print("No symbol found at current cursor location"); - return usages; - }; - - get_method=[this] { - if(!parsed) { - Info::get().print("Buffer is parsing"); - return std::string(); - } - auto iter=get_buffer()->get_insert()->get_iter(); - auto line=static_cast<unsigned>(iter.get_line()); - auto index=static_cast<unsigned>(iter.get_line_index()); - for(size_t c=clang_tokens->size()-1;c!=static_cast<size_t>(-1);--c) { - auto &token=(*clang_tokens)[c]; - if(token.is_identifier()) { - auto &token_offsets=clang_tokens_offsets[c]; - if(line==token_offsets.first.line-1 && index>=token_offsets.first.index-1 && index<=token_offsets.second.index-1) { - auto token_spelling=token.get_spelling(); - if(!token_spelling.empty() && - (token_spelling.size()>1 || (token_spelling.back()>='a' && token_spelling.back()<='z') || - (token_spelling.back()>='A' && token_spelling.back()<='Z') || - token_spelling.back()=='_')) { - auto cursor=token.get_cursor(); - auto kind=cursor.get_kind(); - if(kind==clangmm::Cursor::Kind::FunctionDecl || kind==clangmm::Cursor::Kind::CXXMethod || - kind==clangmm::Cursor::Kind::Constructor || kind==clangmm::Cursor::Kind::Destructor || - kind==clangmm::Cursor::Kind::ConversionFunction) { - auto referenced=cursor.get_referenced(); - if(referenced && referenced==cursor) { - std::string result; - std::string specifier; - if(kind==clangmm::Cursor::Kind::FunctionDecl || kind==clangmm::Cursor::Kind::CXXMethod) { - auto start_offset=cursor.get_source_range().get_start().get_offset(); - auto end_offset=token_offsets.first; - - // To accurately get result type with needed namespace and class/struct names: - int angle_brackets=0; - for(size_t c=0;c<clang_tokens->size();++c) { - auto &token=(*clang_tokens)[c]; - auto &token_offsets=clang_tokens_offsets[c]; - if((token_offsets.first.line==start_offset.line && token_offsets.second.line!=end_offset.line && token_offsets.first.index>=start_offset.index) || - (token_offsets.first.line>start_offset.line && token_offsets.second.line<end_offset.line) || - (token_offsets.first.line!=start_offset.line && token_offsets.second.line==end_offset.line && token_offsets.second.index<=end_offset.index) || - (token_offsets.first.line==start_offset.line && token_offsets.second.line==end_offset.line && - token_offsets.first.index>=start_offset.index && token_offsets.second.index<=end_offset.index)) { - auto token_spelling=token.get_spelling(); - if(token.get_kind()==clangmm::Token::Kind::Identifier) { - if(c==0 || (*clang_tokens)[c-1].get_spelling()!="::") { - auto name=token_spelling; - auto parent=token.get_cursor().get_type().get_cursor().get_semantic_parent(); - while(parent && parent.get_kind()!=clangmm::Cursor::Kind::TranslationUnit) { - auto spelling=parent.get_token_spelling(); - name.insert(0, spelling+"::"); - parent=parent.get_semantic_parent(); - } - result+=name; + auto identifier = get_identifier(); + if (identifier) { + wait_parsing(); + + auto embolden_token = [](std::string &line, unsigned token_start_pos, unsigned token_end_pos) { + //markup token as bold + size_t pos = 0; + while ((pos = line.find('&', pos)) != std::string::npos) { + size_t pos2 = line.find(';', pos + 2); + if (token_start_pos > pos) { + token_start_pos += pos2 - pos; + token_end_pos += pos2 - pos; + } else if (token_end_pos > pos) + token_end_pos += pos2 - pos; + else + break; + pos = pos2 + 1; + } + line.insert(token_end_pos, "</b>"); + line.insert(token_start_pos, "<b>"); + + size_t start_pos = 0; + while (start_pos < line.size() && (line[start_pos] == ' ' || line[start_pos] == '\t')) + ++start_pos; + if (start_pos > 0) + line.erase(0, start_pos); + }; + + std::vector<clangmm::TranslationUnit *> translation_units; + translation_units.emplace_back(clang_tu.get()); + for (auto &view: views) { + if (view != this) { + if (auto clang_view = dynamic_cast<Source::ClangView *>(view)) + translation_units.emplace_back(clang_view->clang_tu.get()); + } + } + + auto build = Project::Build::create(this->file_path); + auto usages_clang = Usages::Clang::get_usages(build->project_path, build->get_default_path(), + build->get_debug_path(), {identifier.spelling}, + {identifier.cursor}, translation_units); + for (auto &usage: usages_clang) { + for (size_t c = 0; c < usage.offsets.size(); ++c) { + std::string line = Glib::Markup::escape_text(usage.lines[c]); + embolden_token(line, usage.offsets[c].first.index - 1, usage.offsets[c].second.index - 1); + usages.emplace_back( + Offset(usage.offsets[c].first.line - 1, usage.offsets[c].first.index - 1, usage.path), + line); + } + } + } + + if (usages.empty()) + Info::get().print("No symbol found at current cursor location"); + return usages; + }; + + get_method = [this] { + if (!parsed) { + Info::get().print("Buffer is parsing"); + return std::string(); + } + auto iter = get_buffer()->get_insert()->get_iter(); + auto line = static_cast<unsigned>(iter.get_line()); + auto index = static_cast<unsigned>(iter.get_line_index()); + for (size_t c = clang_tokens->size() - 1; c != static_cast<size_t>(-1); --c) { + auto &token = (*clang_tokens)[c]; + if (token.is_identifier()) { + auto &token_offsets = clang_tokens_offsets[c]; + if (line == token_offsets.first.line - 1 && index >= token_offsets.first.index - 1 && + index <= token_offsets.second.index - 1) { + auto token_spelling = token.get_spelling(); + if (!token_spelling.empty() && + (token_spelling.size() > 1 || (token_spelling.back() >= 'a' && token_spelling.back() <= 'z') || + (token_spelling.back() >= 'A' && token_spelling.back() <= 'Z') || + token_spelling.back() == '_')) { + auto cursor = token.get_cursor(); + auto kind = cursor.get_kind(); + if (kind == clangmm::Cursor::Kind::FunctionDecl || kind == clangmm::Cursor::Kind::CXXMethod || + kind == clangmm::Cursor::Kind::Constructor || kind == clangmm::Cursor::Kind::Destructor || + kind == clangmm::Cursor::Kind::ConversionFunction) { + auto referenced = cursor.get_referenced(); + if (referenced && referenced == cursor) { + std::string result; + std::string specifier; + if (kind == clangmm::Cursor::Kind::FunctionDecl || + kind == clangmm::Cursor::Kind::CXXMethod) { + auto start_offset = cursor.get_source_range().get_start().get_offset(); + auto end_offset = token_offsets.first; + + // To accurately get result type with needed namespace and class/struct names: + int angle_brackets = 0; + for (size_t c = 0; c < clang_tokens->size(); ++c) { + auto &token = (*clang_tokens)[c]; + auto &token_offsets = clang_tokens_offsets[c]; + if ((token_offsets.first.line == start_offset.line && + token_offsets.second.line != end_offset.line && + token_offsets.first.index >= start_offset.index) || + (token_offsets.first.line > start_offset.line && + token_offsets.second.line < end_offset.line) || + (token_offsets.first.line != start_offset.line && + token_offsets.second.line == end_offset.line && + token_offsets.second.index <= end_offset.index) || + (token_offsets.first.line == start_offset.line && + token_offsets.second.line == end_offset.line && + token_offsets.first.index >= start_offset.index && + token_offsets.second.index <= end_offset.index)) { + auto token_spelling = token.get_spelling(); + if (token.get_kind() == clangmm::Token::Kind::Identifier) { + if (c == 0 || (*clang_tokens)[c - 1].get_spelling() != "::") { + auto name = token_spelling; + auto parent = token.get_cursor().get_type().get_cursor().get_semantic_parent(); + while (parent && parent.get_kind() != + clangmm::Cursor::Kind::TranslationUnit) { + auto spelling = parent.get_token_spelling(); + name.insert(0, spelling + "::"); + parent = parent.get_semantic_parent(); + } + result += name; + } else + result += token_spelling; + } else if ((token_spelling == "*" || token_spelling == "&") && + !result.empty() && result.back() != '*' && result.back() != '&') + result += ' ' + token_spelling; + else if (token_spelling == "extern" || token_spelling == "static" || + token_spelling == "virtual" || token_spelling == "friend") + continue; + else if (token_spelling == "," || + (token_spelling.size() > 1 && token_spelling != "::" && + angle_brackets == 0)) + result += token_spelling + ' '; + else { + if (token_spelling == "<") + ++angle_brackets; + else if (token_spelling == ">") + --angle_brackets; + result += token_spelling; + } + } + } + + if (!result.empty() && result.back() != '*' && result.back() != '&' && + result.back() != ' ') + result += ' '; + + if (clang_CXXMethod_isConst(cursor.cx_cursor)) + specifier = " const"; + } + + auto name = cursor.get_spelling(); + auto parent = cursor.get_semantic_parent(); + std::vector<std::string> semantic_parents; + while (parent && parent.get_kind() != clangmm::Cursor::Kind::TranslationUnit) { + auto spelling = parent.get_spelling() + "::"; + if (spelling != "::") { + semantic_parents.emplace_back(spelling); + name.insert(0, spelling); + } + parent = parent.get_semantic_parent(); + } + + std::string arguments; + for (auto &argument_cursor: cursor.get_arguments()) { + auto argument_type = argument_cursor.get_type().get_spelling(); + for (auto it = semantic_parents.rbegin(); it != semantic_parents.rend(); ++it) { + size_t pos = argument_type.find(*it); + if (pos == 0 || (pos != std::string::npos && argument_type[pos - 1] == ' ')) + argument_type.erase(pos, it->size()); + } + auto argument = argument_cursor.get_spelling(); + if (!arguments.empty()) + arguments += ", "; + arguments += argument_type; + if (!arguments.empty() && arguments.back() != '*' && arguments.back() != '&') + arguments += ' '; + arguments += argument; + } + return result + name + '(' + arguments + ")" + specifier + " {}"; + } } - else - result+=token_spelling; - } - else if((token_spelling=="*" || token_spelling=="&") && !result.empty() && result.back()!='*' && result.back()!='&') - result+=' '+token_spelling; - else if(token_spelling=="extern" || token_spelling=="static" || token_spelling=="virtual" || token_spelling=="friend") - continue; - else if(token_spelling=="," || (token_spelling.size()>1 && token_spelling!="::" && angle_brackets==0)) - result+=token_spelling+' '; - else { - if(token_spelling=="<") - ++angle_brackets; - else if(token_spelling==">") - --angle_brackets; - result+=token_spelling; - } } - } - - if(!result.empty() && result.back()!='*' && result.back()!='&' && result.back()!=' ') - result+=' '; - - if(clang_CXXMethod_isConst(cursor.cx_cursor)) - specifier=" const"; } - - auto name=cursor.get_spelling(); - auto parent=cursor.get_semantic_parent(); - std::vector<std::string> semantic_parents; - while(parent && parent.get_kind()!=clangmm::Cursor::Kind::TranslationUnit) { - auto spelling=parent.get_spelling()+"::"; - if(spelling!="::") { - semantic_parents.emplace_back(spelling); - name.insert(0, spelling); - } - parent=parent.get_semantic_parent(); + } + } + Info::get().print("No method found at current cursor location"); + return std::string(); + }; + + get_methods = [this]() { + std::vector<std::pair<Offset, std::string> > methods; + if (!parsed) { + Info::get().print("Buffer is parsing"); + return methods; + } + clangmm::Offset last_offset{static_cast<unsigned>(-1), static_cast<unsigned>(-1)}; + for (auto &token: *clang_tokens) { + if (token.is_identifier()) { + auto cursor = token.get_cursor(); + auto kind = cursor.get_kind(); + if (kind == clangmm::Cursor::Kind::FunctionDecl || kind == clangmm::Cursor::Kind::CXXMethod || + kind == clangmm::Cursor::Kind::Constructor || kind == clangmm::Cursor::Kind::Destructor || + kind == clangmm::Cursor::Kind::FunctionTemplate || + kind == clangmm::Cursor::Kind::ConversionFunction) { + auto offset = cursor.get_source_location().get_offset(); + if (offset == last_offset) + continue; + last_offset = offset; + + std::string method; + if (kind != clangmm::Cursor::Kind::Constructor && kind != clangmm::Cursor::Kind::Destructor) { + method += cursor.get_type().get_result().get_spelling(); + auto pos = method.find(" "); + if (pos != std::string::npos) + method.erase(pos, 1); + method += " "; + } + method += cursor.get_display_name(); + + std::string prefix; + auto parent = cursor.get_semantic_parent(); + while (parent && parent.get_kind() != clangmm::Cursor::Kind::TranslationUnit) { + prefix.insert(0, parent.get_display_name() + (prefix.empty() ? "" : "::")); + parent = parent.get_semantic_parent(); + } + + method = Glib::Markup::escape_text(method); + //Add bold method token + size_t token_end_pos = method.find('('); + if (token_end_pos == std::string::npos) + continue; + auto token_start_pos = token_end_pos; + while (token_start_pos != 0 && method[token_start_pos] != ' ') + --token_start_pos; + method.insert(token_end_pos, "</b>"); + method.insert(token_start_pos, "<b>"); + + if (!prefix.empty()) + prefix += ':'; + prefix += std::to_string(offset.line) + ": "; + prefix = Glib::Markup::escape_text(prefix); + + methods.emplace_back(Offset(offset.line - 1, offset.index - 1), prefix + method); } - - std::string arguments; - for(auto &argument_cursor: cursor.get_arguments()) { - auto argument_type=argument_cursor.get_type().get_spelling(); - for(auto it=semantic_parents.rbegin();it!=semantic_parents.rend();++it) { - size_t pos=argument_type.find(*it); - if(pos==0 || (pos!=std::string::npos && argument_type[pos-1]==' ')) - argument_type.erase(pos, it->size()); - } - auto argument=argument_cursor.get_spelling(); - if(!arguments.empty()) - arguments+=", "; - arguments+=argument_type; - if(!arguments.empty() && arguments.back()!='*' && arguments.back()!='&') - arguments+=' '; - arguments+=argument; + } + } + if (methods.empty()) + Info::get().print("No methods found in current buffer"); + + return methods; + }; + + get_token_data = [this]() -> std::vector<std::string> { + clangmm::Cursor cursor; + + std::vector<std::string> data; + if (!parsed) { + if (selected_completion_string) { + cursor = clangmm::CompletionString(selected_completion_string).get_cursor(clang_tu->cx_tu); + if (!cursor) { + Info::get().print("No symbol found"); + return data; } - return result+name+'('+arguments+")"+specifier+" {}"; - } + } else { + Info::get().print("Buffer is parsing"); + return data; } - } } - } - } - Info::get().print("No method found at current cursor location"); - return std::string(); - }; - - get_methods=[this](){ - std::vector<std::pair<Offset, std::string> > methods; - if(!parsed) { - Info::get().print("Buffer is parsing"); - return methods; - } - clangmm::Offset last_offset{static_cast<unsigned>(-1), static_cast<unsigned>(-1)}; - for(auto &token: *clang_tokens) { - if(token.is_identifier()) { - auto cursor=token.get_cursor(); - auto kind=cursor.get_kind(); - if(kind==clangmm::Cursor::Kind::FunctionDecl || kind==clangmm::Cursor::Kind::CXXMethod || - kind==clangmm::Cursor::Kind::Constructor || kind==clangmm::Cursor::Kind::Destructor || - kind==clangmm::Cursor::Kind::FunctionTemplate || kind==clangmm::Cursor::Kind::ConversionFunction) { - auto offset=cursor.get_source_location().get_offset(); - if(offset==last_offset) - continue; - last_offset=offset; - - std::string method; - if(kind!=clangmm::Cursor::Kind::Constructor && kind!=clangmm::Cursor::Kind::Destructor) { - method+=cursor.get_type().get_result().get_spelling(); - auto pos=method.find(" "); - if(pos!=std::string::npos) - method.erase(pos, 1); - method+=" "; - } - method+=cursor.get_display_name(); - - std::string prefix; - auto parent=cursor.get_semantic_parent(); - while(parent && parent.get_kind()!=clangmm::Cursor::Kind::TranslationUnit) { - prefix.insert(0, parent.get_display_name()+(prefix.empty()?"":"::")); - parent=parent.get_semantic_parent(); - } - - method=Glib::Markup::escape_text(method); - //Add bold method token - size_t token_end_pos=method.find('('); - if(token_end_pos==std::string::npos) - continue; - auto token_start_pos=token_end_pos; - while(token_start_pos!=0 && method[token_start_pos]!=' ') - --token_start_pos; - method.insert(token_end_pos, "</b>"); - method.insert(token_start_pos, "<b>"); - - if(!prefix.empty()) - prefix+=':'; - prefix+=std::to_string(offset.line)+": "; - prefix=Glib::Markup::escape_text(prefix); - - methods.emplace_back(Offset(offset.line-1, offset.index-1), prefix+method); + + if (!cursor) { + auto identifier = get_identifier(); + if (identifier) + cursor = identifier.cursor.get_canonical(); } - } - } - if(methods.empty()) - Info::get().print("No methods found in current buffer"); - - return methods; - }; - - get_token_data=[this]() -> std::vector<std::string> { - clangmm::Cursor cursor; - - std::vector<std::string> data; - if(!parsed) { - if(selected_completion_string) { - cursor=clangmm::CompletionString(selected_completion_string).get_cursor(clang_tu->cx_tu); - if(!cursor) { - Info::get().print("No symbol found"); - return data; + + if (cursor) { + data.emplace_back("clang"); + + std::string symbol; + clangmm::Cursor last_cursor; + auto it = data.end(); + do { + auto token_spelling = cursor.get_token_spelling(); + if (!token_spelling.empty() && token_spelling != "__1" && token_spelling.compare(0, 5, "__cxx") != 0) { + it = data.emplace(it, token_spelling); + if (symbol.empty()) + symbol = token_spelling; + else + symbol.insert(0, token_spelling + "::"); + } + last_cursor = cursor; + cursor = cursor.get_semantic_parent(); + } while (cursor.get_kind() != clangmm::Cursor::Kind::TranslationUnit); + + if (last_cursor.get_kind() != clangmm::Cursor::Kind::Namespace) + data.emplace(++data.begin(), ""); + + auto url = Documentation::CppReference::get_url(symbol); + if (!url.empty()) + return {url}; } - } - else { - Info::get().print("Buffer is parsing"); + + if (data.empty()) + Info::get().print("No symbol found at current cursor location"); + return data; - } - } - - if(!cursor) { - auto identifier=get_identifier(); - if(identifier) - cursor=identifier.cursor.get_canonical(); - } - - if(cursor) { - data.emplace_back("clang"); - - std::string symbol; - clangmm::Cursor last_cursor; - auto it=data.end(); - do { - auto token_spelling=cursor.get_token_spelling(); - if(!token_spelling.empty() && token_spelling!="__1" && token_spelling.compare(0, 5, "__cxx")!=0) { - it=data.emplace(it, token_spelling); - if(symbol.empty()) - symbol=token_spelling; - else - symbol.insert(0, token_spelling+"::"); + }; + + goto_next_diagnostic = [this]() { + if (!parsed) { + Info::get().print("Buffer is parsing"); + return; } - last_cursor=cursor; - cursor=cursor.get_semantic_parent(); - } while(cursor.get_kind()!=clangmm::Cursor::Kind::TranslationUnit); - - if(last_cursor.get_kind()!=clangmm::Cursor::Kind::Namespace) - data.emplace(++data.begin(), ""); - - auto url=Documentation::CppReference::get_url(symbol); - if(!url.empty()) - return {url}; - } - - if(data.empty()) - Info::get().print("No symbol found at current cursor location"); - - return data; - }; - - goto_next_diagnostic=[this]() { - if(!parsed) { - Info::get().print("Buffer is parsing"); - return; - } - place_cursor_at_next_diagnostic(); - }; - - get_fix_its=[this]() { - if(!parsed) { - Info::get().print("Buffer is parsing"); - return std::vector<FixIt>(); - } - if(fix_its.empty()) - Info::get().print("No fix-its found in current buffer"); - return fix_its; - }; - - get_documentation_template=[this]() { - if(!parsed) { - Info::get().print("Buffer is parsing"); - return std::tuple<Source::Offset, std::string, size_t>(Source::Offset(), "", 0); - } - auto identifier=get_identifier(); - if(identifier) { - auto cursor=identifier.cursor.get_canonical(); - if(!clang_Range_isNull(clang_Cursor_getCommentRange(cursor.cx_cursor))) { - Info::get().print("Symbol is already documented"); - return std::tuple<Source::Offset, std::string, size_t>(Source::Offset(), "", 0); - } - auto clang_offsets=cursor.get_source_range().get_offsets(); - auto source_offset=Offset(clang_offsets.first.line-1, 0, cursor.get_source_location().get_path()); - std::string tabs; - for(size_t c=0;c<clang_offsets.first.index-1;++c) - tabs+=' '; - auto first_line=tabs+"/**\n"; - auto second_line=tabs+" * \n"; - auto iter_offset=first_line.size()+second_line.size()-1; - - std::string param_lines; - for(int c=0;c<clang_Cursor_getNumArguments(cursor.cx_cursor);++c) - param_lines+=tabs+" * @param "+clangmm::Cursor(clang_Cursor_getArgument(cursor.cx_cursor, c)).get_spelling()+'\n'; - - std::string return_line; - auto return_spelling=cursor.get_type().get_result().get_spelling(); - if(!return_spelling.empty() && return_spelling!="void") - return_line+=tabs+" * @return\n"; - - auto documentation=first_line+second_line; - if(!param_lines.empty() || !return_line.empty()) - documentation+=tabs+" *\n"; - - documentation+=param_lines+return_line+tabs+" */\n"; - - return std::tuple<Source::Offset, std::string, size_t>(source_offset, documentation, iter_offset); - } - else { - Info::get().print("No symbol found at current cursor location"); - return std::tuple<Source::Offset, std::string, size_t>(Source::Offset(), "", 0); - } - }; + place_cursor_at_next_diagnostic(); + }; + + get_fix_its = [this]() { + if (!parsed) { + Info::get().print("Buffer is parsing"); + return std::vector<FixIt>(); + } + if (fix_its.empty()) + Info::get().print("No fix-its found in current buffer"); + return fix_its; + }; + + get_documentation_template = [this]() { + if (!parsed) { + Info::get().print("Buffer is parsing"); + return std::tuple<Source::Offset, std::string, size_t>(Source::Offset(), "", 0); + } + auto identifier = get_identifier(); + if (identifier) { + auto cursor = identifier.cursor.get_canonical(); + if (!clang_Range_isNull(clang_Cursor_getCommentRange(cursor.cx_cursor))) { + Info::get().print("Symbol is already documented"); + return std::tuple<Source::Offset, std::string, size_t>(Source::Offset(), "", 0); + } + auto clang_offsets = cursor.get_source_range().get_offsets(); + auto source_offset = Offset(clang_offsets.first.line - 1, 0, cursor.get_source_location().get_path()); + std::string tabs; + for (size_t c = 0; c < clang_offsets.first.index - 1; ++c) + tabs += ' '; + auto first_line = tabs + "/**\n"; + auto second_line = tabs + " * \n"; + auto iter_offset = first_line.size() + second_line.size() - 1; + + std::string param_lines; + for (int c = 0; c < clang_Cursor_getNumArguments(cursor.cx_cursor); ++c) + param_lines += tabs + " * @param " + + clangmm::Cursor(clang_Cursor_getArgument(cursor.cx_cursor, c)).get_spelling() + '\n'; + + std::string return_line; + auto return_spelling = cursor.get_type().get_result().get_spelling(); + if (!return_spelling.empty() && return_spelling != "void") + return_line += tabs + " * @return\n"; + + auto documentation = first_line + second_line; + if (!param_lines.empty() || !return_line.empty()) + documentation += tabs + " *\n"; + + documentation += param_lines + return_line + tabs + " */\n"; + + return std::tuple<Source::Offset, std::string, size_t>(source_offset, documentation, iter_offset); + } else { + Info::get().print("No symbol found at current cursor location"); + return std::tuple<Source::Offset, std::string, size_t>(Source::Offset(), "", 0); + } + }; } Source::ClangViewRefactor::Identifier Source::ClangViewRefactor::get_identifier() { - if(!parsed) - return Identifier(); - auto iter=get_buffer()->get_insert()->get_iter(); - auto line=static_cast<unsigned>(iter.get_line()); - auto index=static_cast<unsigned>(iter.get_line_index()); - for(size_t c=clang_tokens->size()-1;c!=static_cast<size_t>(-1);--c) { - auto &token=(*clang_tokens)[c]; - if(token.is_identifier()) { - auto &token_offsets=clang_tokens_offsets[c]; - if(line==token_offsets.first.line-1 && index>=token_offsets.first.index-1 && index <=token_offsets.second.index-1) { - auto referenced=token.get_cursor().get_referenced(); - if(referenced) - return Identifier(token.get_spelling(), referenced); - } + if (!parsed) + return Identifier(); + auto iter = get_buffer()->get_insert()->get_iter(); + auto line = static_cast<unsigned>(iter.get_line()); + auto index = static_cast<unsigned>(iter.get_line_index()); + for (size_t c = clang_tokens->size() - 1; c != static_cast<size_t>(-1); --c) { + auto &token = (*clang_tokens)[c]; + if (token.is_identifier()) { + auto &token_offsets = clang_tokens_offsets[c]; + if (line == token_offsets.first.line - 1 && index >= token_offsets.first.index - 1 && + index <= token_offsets.second.index - 1) { + auto referenced = token.get_cursor().get_referenced(); + if (referenced) + return Identifier(token.get_spelling(), referenced); + } + } } - } - return Identifier(); + return Identifier(); } void Source::ClangViewRefactor::wait_parsing() { - std::unique_ptr<Dialog::Message> message; - std::vector<Source::ClangView*> clang_views; - for(auto &view: views) { - if(auto clang_view=dynamic_cast<Source::ClangView*>(view)) { - if(!clang_view->parsed && !clang_view->selected_completion_string) { - clang_views.emplace_back(clang_view); - if(!message) - message=std::make_unique<Dialog::Message>("Please wait while all buffers finish parsing"); - } + std::unique_ptr<Dialog::Message> message; + std::vector<Source::ClangView *> clang_views; + for (auto &view: views) { + if (auto clang_view = dynamic_cast<Source::ClangView *>(view)) { + if (!clang_view->parsed && !clang_view->selected_completion_string) { + clang_views.emplace_back(clang_view); + if (!message) + message = std::make_unique<Dialog::Message>("Please wait while all buffers finish parsing"); + } + } } - } - if(message) { - for(;;) { - while(Gtk::Main::events_pending()) - Gtk::Main::iteration(false); - bool all_parsed=true; - for(auto &clang_view: clang_views) { - if(!clang_view->parsed) { - all_parsed=false; - break; + if (message) { + for (;;) { + while (Gtk::Main::events_pending()) + Gtk::Main::iteration(false); + bool all_parsed = true; + for (auto &clang_view: clang_views) { + if (!clang_view->parsed) { + all_parsed = false; + break; + } + } + if (all_parsed) + break; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } - } - if(all_parsed) - break; - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + message->hide(); } - message->hide(); - } } void Source::ClangViewRefactor::tag_similar_identifiers(const Identifier &identifier) { - if(parsed) { - if(identifier && last_tagged_identifier!=identifier) { - for(auto &mark: similar_identifiers_marks) { - get_buffer()->remove_tag(similar_identifiers_tag, mark.first->get_iter(), mark.second->get_iter()); - get_buffer()->delete_mark(mark.first); - get_buffer()->delete_mark(mark.second); - } - similar_identifiers_marks.clear(); - auto offsets=clang_tokens->get_similar_token_offsets(identifier.kind, identifier.spelling, identifier.cursor.get_all_usr_extended()); - for(auto &offset: offsets) { - auto start_iter=get_buffer()->get_iter_at_line_index(offset.first.line-1, offset.first.index-1); - auto end_iter=get_buffer()->get_iter_at_line_index(offset.second.line-1, offset.second.index-1); - get_buffer()->apply_tag(similar_identifiers_tag, start_iter, end_iter); - similar_identifiers_marks.emplace_back(get_buffer()->create_mark(start_iter), get_buffer()->create_mark(end_iter)); - } - last_tagged_identifier=identifier; + if (parsed) { + if (identifier && last_tagged_identifier != identifier) { + for (auto &mark: similar_identifiers_marks) { + get_buffer()->remove_tag(similar_identifiers_tag, mark.first->get_iter(), mark.second->get_iter()); + get_buffer()->delete_mark(mark.first); + get_buffer()->delete_mark(mark.second); + } + similar_identifiers_marks.clear(); + auto offsets = clang_tokens->get_similar_token_offsets(identifier.kind, identifier.spelling, + identifier.cursor.get_all_usr_extended()); + for (auto &offset: offsets) { + auto start_iter = get_buffer()->get_iter_at_line_index(offset.first.line - 1, offset.first.index - 1); + auto end_iter = get_buffer()->get_iter_at_line_index(offset.second.line - 1, offset.second.index - 1); + get_buffer()->apply_tag(similar_identifiers_tag, start_iter, end_iter); + similar_identifiers_marks.emplace_back(get_buffer()->create_mark(start_iter), + get_buffer()->create_mark(end_iter)); + } + last_tagged_identifier = identifier; + } } - } - if(!identifier && last_tagged_identifier) { - for(auto &mark: similar_identifiers_marks) { - get_buffer()->remove_tag(similar_identifiers_tag, mark.first->get_iter(), mark.second->get_iter()); - get_buffer()->delete_mark(mark.first); - get_buffer()->delete_mark(mark.second); + if (!identifier && last_tagged_identifier) { + for (auto &mark: similar_identifiers_marks) { + get_buffer()->remove_tag(similar_identifiers_tag, mark.first->get_iter(), mark.second->get_iter()); + get_buffer()->delete_mark(mark.first); + get_buffer()->delete_mark(mark.second); + } + similar_identifiers_marks.clear(); + last_tagged_identifier = Identifier(); } - similar_identifiers_marks.clear(); - last_tagged_identifier=Identifier(); - } } -Source::ClangView::ClangView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language): - BaseView(file_path, language), ClangViewParse(file_path, language), ClangViewAutocomplete(file_path, language), ClangViewRefactor(file_path, language) { - if(language) { - get_source_buffer()->set_highlight_syntax(true); - get_source_buffer()->set_language(language); - } - - do_delete_object.connect([this]() { - if(delete_thread.joinable()) - delete_thread.join(); - delete this; - }); +Source::ClangView::ClangView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language) : + BaseView(file_path, language), ClangViewParse(file_path, language), ClangViewAutocomplete(file_path, language), + ClangViewRefactor(file_path, language) { + if (language) { + get_source_buffer()->set_highlight_syntax(true); + get_source_buffer()->set_language(language); + } + + do_delete_object.connect([this]() { + if (delete_thread.joinable()) + delete_thread.join(); + delete this; + }); } void Source::ClangView::full_reparse() { - auto print_error=[this] { - Terminal::get().async_print("Error: failed to reparse "+file_path.string()+". Please reopen the file manually.\n", true); - }; - full_reparse_needed=false; - if(full_reparse_running) { - print_error(); - return; - } - else { - auto expected=ParseState::PROCESSING; - if(!parse_state.compare_exchange_strong(expected, ParseState::RESTARTING)) { - expected=ParseState::RESTARTING; - if(!parse_state.compare_exchange_strong(expected, ParseState::RESTARTING)) { + auto print_error = [this] { + Terminal::get().async_print( + "Error: failed to reparse " + file_path.string() + ". Please reopen the file manually.\n", true); + }; + full_reparse_needed = false; + if (full_reparse_running) { print_error(); return; - } + } else { + auto expected = ParseState::PROCESSING; + if (!parse_state.compare_exchange_strong(expected, ParseState::RESTARTING)) { + expected = ParseState::RESTARTING; + if (!parse_state.compare_exchange_strong(expected, ParseState::RESTARTING)) { + print_error(); + return; + } + } + autocomplete.state = Autocomplete::State::IDLE; + soft_reparse_needed = false; + full_reparse_running = true; + if (full_reparse_thread.joinable()) + full_reparse_thread.join(); + full_reparse_thread = std::thread([this]() { + if (parse_thread.joinable()) + parse_thread.join(); + if (autocomplete.thread.joinable()) + autocomplete.thread.join(); + dispatcher.post([this] { + parse_initialize(); + full_reparse_running = false; + }); + }); } - autocomplete.state=Autocomplete::State::IDLE; - soft_reparse_needed=false; - full_reparse_running=true; - if(full_reparse_thread.joinable()) - full_reparse_thread.join(); - full_reparse_thread=std::thread([this](){ - if(parse_thread.joinable()) - parse_thread.join(); - if(autocomplete.thread.joinable()) - autocomplete.thread.join(); - dispatcher.post([this] { - parse_initialize(); - full_reparse_running=false; - }); - }); - } } void Source::ClangView::async_delete() { - delayed_show_arguments_connection.disconnect(); - - views.erase(this); - std::set<boost::filesystem::path> project_paths_in_use; - for(auto &view: views) { - if(dynamic_cast<ClangView*>(view)) { - auto build=Project::Build::create(view->file_path); - if(!build->project_path.empty()) - project_paths_in_use.emplace(build->project_path); - } - } - Usages::Clang::erase_unused_caches(project_paths_in_use); - Usages::Clang::cache_in_progress(); - - if(!get_buffer()->get_modified()) { - if(full_reparse_needed) - full_reparse(); - else if(soft_reparse_needed) - soft_reparse(); - } - - auto before_parse_time=std::time(nullptr); - delete_thread=std::thread([this, before_parse_time, project_paths_in_use=std::move(project_paths_in_use)] { - while(!parsed) - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - - delayed_reparse_connection.disconnect(); - parse_state=ParseState::STOP; - dispatcher.disconnect(); - - if(get_buffer()->get_modified()) { - std::ifstream stream(file_path.string(), std::ios::binary); - if(stream) { - std::string buffer; - buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>()); - if(language && (language->get_id()=="chdr" || language->get_id()=="cpphdr")) - clangmm::remove_include_guard(buffer); - clang_tu->reparse(buffer); - clang_tokens = clang_tu->get_tokens(); - } - else - clang_tokens=nullptr; + delayed_show_arguments_connection.disconnect(); + delayed_tag_similar_identifiers_connection.disconnect(); + + views.erase(this); + std::set<boost::filesystem::path> project_paths_in_use; + for (auto &view: views) { + if (dynamic_cast<ClangView *>(view)) { + auto build = Project::Build::create(view->file_path); + if (!build->project_path.empty()) + project_paths_in_use.emplace(build->project_path); + } } - - if(clang_tokens) { - auto build=Project::Build::create(file_path); - Usages::Clang::cache(build->project_path, build->get_default_path(), file_path, before_parse_time, project_paths_in_use, clang_tu.get(), clang_tokens.get()); + Usages::Clang::erase_unused_caches(project_paths_in_use); + Usages::Clang::cache_in_progress(); + + if (!get_buffer()->get_modified()) { + if (full_reparse_needed) + full_reparse(); + else if (soft_reparse_needed) + soft_reparse(); } - - if(full_reparse_thread.joinable()) - full_reparse_thread.join(); - if(parse_thread.joinable()) - parse_thread.join(); - if(autocomplete.thread.joinable()) - autocomplete.thread.join(); - do_delete_object(); - }); + + auto before_parse_time = std::time(nullptr); + delete_thread = std::thread([this, before_parse_time, project_paths_in_use = std::move(project_paths_in_use)] { + while (!parsed) + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + delayed_reparse_connection.disconnect(); + parse_state = ParseState::STOP; + dispatcher.disconnect(); + + if (get_buffer()->get_modified()) { + std::ifstream stream(file_path.string(), std::ios::binary); + if (stream) { + std::string buffer; + buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>()); + if (language && (language->get_id() == "chdr" || language->get_id() == "cpphdr")) + clangmm::remove_include_guard(buffer); + clang_tu->reparse(buffer); + clang_tokens = clang_tu->get_tokens(); + } else + clang_tokens = nullptr; + } + + if (clang_tokens) { + auto build = Project::Build::create(file_path); + Usages::Clang::cache(build->project_path, build->get_default_path(), file_path, before_parse_time, + project_paths_in_use, clang_tu.get(), clang_tokens.get()); + } + + if (full_reparse_thread.joinable()) + full_reparse_thread.join(); + if (parse_thread.joinable()) + parse_thread.join(); + if (autocomplete.thread.joinable()) + autocomplete.thread.join(); + do_delete_object(); + }); } diff --git a/src/source_clang.h b/src/source_clang.h index 76aeef8d..b4184525 100644 --- a/src/source_clang.h +++ b/src/source_clang.h @@ -1,4 +1,5 @@ #pragma once + #include <thread> #include <atomic> #include <mutex> @@ -10,102 +11,134 @@ #include "autocomplete.h" namespace Source { - class ClangViewParse : public View { - protected: - enum class ParseState {PROCESSING, RESTARTING, STOP}; - enum class ParseProcessState {IDLE, STARTING, PREPROCESSING, PROCESSING, POSTPROCESSING}; - - public: - ClangViewParse(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); - - bool save() override; - void configure() override; - - void soft_reparse(bool delayed=false) override; - protected: - Dispatcher dispatcher; - void parse_initialize(); - std::unique_ptr<clangmm::TranslationUnit> clang_tu; - std::unique_ptr<clangmm::Tokens> clang_tokens; - std::vector<std::pair<clangmm::Offset, clangmm::Offset>> clang_tokens_offsets; - sigc::connection delayed_reparse_connection; - - void show_type_tooltips(const Gdk::Rectangle &rectangle) override; - - std::vector<FixIt> fix_its; - - std::thread parse_thread; - std::mutex parse_mutex; - std::atomic<ParseState> parse_state; - std::atomic<ParseProcessState> parse_process_state; - - CXCompletionString selected_completion_string=nullptr; - private: - Glib::ustring parse_thread_buffer; - - static const std::unordered_map<int, std::string> &clang_types(); - void update_syntax(); - std::set<std::string> last_syntax_tags; - - void update_diagnostics(); - std::vector<clangmm::Diagnostic> clang_diagnostics; - - static clangmm::Index clang_index; - }; - - class ClangViewAutocomplete : public virtual ClangViewParse { - public: - ClangViewAutocomplete(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); - protected: - Autocomplete autocomplete; - std::unique_ptr<clangmm::CodeCompleteResults> code_complete_results; - std::vector<CXCompletionString> completion_strings; - sigc::connection delayed_show_arguments_connection; - private: - bool is_possible_parameter(); - bool show_arguments; - const std::unordered_map<std::string, std::string> &autocomplete_manipulators_map(); - }; - - class ClangViewRefactor : public virtual ClangViewParse { - class Identifier { + class ClangViewParse : public View { + protected: + enum class ParseState { + PROCESSING, RESTARTING, STOP + }; + enum class ParseProcessState { + IDLE, STARTING, PREPROCESSING, PROCESSING, POSTPROCESSING + }; + public: - Identifier(const std::string &spelling, const clangmm::Cursor &cursor) - : kind(cursor.get_kind()), spelling(spelling), usr_extended(cursor.get_usr_extended()), cursor(cursor) {} - Identifier() : kind(static_cast<clangmm::Cursor::Kind>(0)) {} - - operator bool() const { return static_cast<int>(kind)!=0; } - bool operator==(const Identifier &rhs) const { return spelling==rhs.spelling && usr_extended==rhs.usr_extended; } - bool operator!=(const Identifier &rhs) const { return !(*this==rhs); } - bool operator<(const Identifier &rhs) const { return spelling<rhs.spelling || (spelling==rhs.spelling && usr_extended<rhs.usr_extended); } - clangmm::Cursor::Kind kind; - std::string spelling; - std::string usr_extended; - clangmm::Cursor cursor; + ClangViewParse(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); + + bool save() override; + + void configure() override; + + void soft_reparse(bool delayed = false) override; + + protected: + Dispatcher dispatcher; + + void parse_initialize(); + + std::unique_ptr<clangmm::TranslationUnit> clang_tu; + std::unique_ptr<clangmm::Tokens> clang_tokens; + std::vector<std::pair<clangmm::Offset, clangmm::Offset>> clang_tokens_offsets; + sigc::connection delayed_reparse_connection; + + void show_type_tooltips(const Gdk::Rectangle &rectangle) override; + + std::vector<FixIt> fix_its; + + std::thread parse_thread; + std::mutex parse_mutex; + std::atomic<ParseState> parse_state; + std::atomic<ParseProcessState> parse_process_state; + + CXCompletionString selected_completion_string = nullptr; + private: + Glib::ustring parse_thread_buffer; + + static const std::unordered_map<int, std::string> &clang_types(); + + void update_syntax(); + + std::set<std::string> last_syntax_tags; + + void update_diagnostics(); + + std::vector<clangmm::Diagnostic> clang_diagnostics; + + static clangmm::Index clang_index; + }; + + class ClangViewAutocomplete : public virtual ClangViewParse { + public: + ClangViewAutocomplete(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); + + protected: + Autocomplete autocomplete; + std::unique_ptr<clangmm::CodeCompleteResults> code_complete_results; + std::vector<CXCompletionString> completion_strings; + sigc::connection delayed_show_arguments_connection; + private: + bool is_possible_parameter(); + + bool show_arguments; + + const std::unordered_map<std::string, std::string> &autocomplete_manipulators_map(); + }; + + class ClangViewRefactor : public virtual ClangViewParse { + class Identifier { + public: + Identifier(const std::string &spelling, const clangmm::Cursor &cursor) + : kind(cursor.get_kind()), spelling(spelling), usr_extended(cursor.get_usr_extended()), + cursor(cursor) {} + + Identifier() : kind(static_cast<clangmm::Cursor::Kind>(0)) {} + + operator bool() const { return static_cast<int>(kind) != 0; } + + bool operator==(const Identifier &rhs) const { + return spelling == rhs.spelling && usr_extended == rhs.usr_extended; + } + + bool operator!=(const Identifier &rhs) const { return !(*this == rhs); } + + bool operator<(const Identifier &rhs) const { + return spelling < rhs.spelling || (spelling == rhs.spelling && usr_extended < rhs.usr_extended); + } + + clangmm::Cursor::Kind kind; + std::string spelling; + std::string usr_extended; + clangmm::Cursor cursor; + }; + + public: + ClangViewRefactor(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); + + protected: + sigc::connection delayed_tag_similar_identifiers_connection; + private: + Identifier get_identifier(); + + void wait_parsing(); + + std::list<std::pair<Glib::RefPtr<Gtk::TextMark>, Glib::RefPtr<Gtk::TextMark> > > similar_identifiers_marks; + + void tag_similar_identifiers(const Identifier &identifier); + + Glib::RefPtr<Gtk::TextTag> similar_identifiers_tag; + Identifier last_tagged_identifier; + }; + + class ClangView : public ClangViewAutocomplete, public ClangViewRefactor { + public: + ClangView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); + + void full_reparse() override; + + void async_delete(); + + private: + Glib::Dispatcher do_delete_object; + std::thread delete_thread; + std::thread full_reparse_thread; + bool full_reparse_running = false; }; - public: - ClangViewRefactor(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); - private: - Identifier get_identifier(); - void wait_parsing(); - - std::list<std::pair<Glib::RefPtr<Gtk::TextMark>, Glib::RefPtr<Gtk::TextMark> > > similar_identifiers_marks; - void tag_similar_identifiers(const Identifier &identifier); - Glib::RefPtr<Gtk::TextTag> similar_identifiers_tag; - Identifier last_tagged_identifier; - }; - - class ClangView : public ClangViewAutocomplete, public ClangViewRefactor { - public: - ClangView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); - - void full_reparse() override; - void async_delete(); - - private: - Glib::Dispatcher do_delete_object; - std::thread delete_thread; - std::thread full_reparse_thread; - bool full_reparse_running=false; - }; } diff --git a/src/source_diff.cc b/src/source_diff.cc index 2ed28dc2..e0bec547 100644 --- a/src/source_diff.cc +++ b/src/source_diff.cc @@ -6,362 +6,366 @@ #include <boost/version.hpp> Source::DiffView::Renderer::Renderer() : Gsv::GutterRenderer() { - set_padding(4, 0); + set_padding(4, 0); } -void Source::DiffView::Renderer::draw_vfunc(const Cairo::RefPtr<Cairo::Context> &cr, const Gdk::Rectangle &background_area, - const Gdk::Rectangle &cell_area, Gtk::TextIter &start, Gtk::TextIter &end, - Gsv::GutterRendererState p6) { - if(start.has_tag(tag_added) || end.has_tag(tag_added)) { - cr->set_source_rgba(0.0, 1.0, 0.0, 0.5); - cr->rectangle(cell_area.get_x(), cell_area.get_y(), 4, cell_area.get_height()); - cr->fill(); - } - else if(start.has_tag(tag_modified) || end.has_tag(tag_modified)) { - cr->set_source_rgba(0.9, 0.9, 0.0, 0.75); - cr->rectangle(cell_area.get_x(), cell_area.get_y(), 4, cell_area.get_height()); - cr->fill(); - } - if(start.has_tag(tag_removed_below) || end.has_tag(tag_removed_below)) { - cr->set_source_rgba(0.75, 0.0, 0.0, 0.5); - cr->rectangle(cell_area.get_x()-4, cell_area.get_y()+cell_area.get_height()-2, 8, 2); - cr->fill(); - } - if(start.has_tag(tag_removed_above) || end.has_tag(tag_removed_above)) { - cr->set_source_rgba(0.75, 0.0, 0.0, 0.5); - cr->rectangle(cell_area.get_x()-4, cell_area.get_y(), 8, 2); - cr->fill(); - } +void +Source::DiffView::Renderer::draw_vfunc(const Cairo::RefPtr<Cairo::Context> &cr, const Gdk::Rectangle &background_area, + const Gdk::Rectangle &cell_area, Gtk::TextIter &start, Gtk::TextIter &end, + Gsv::GutterRendererState p6) { + if (start.has_tag(tag_added) || end.has_tag(tag_added)) { + cr->set_source_rgba(0.0, 1.0, 0.0, 0.5); + cr->rectangle(cell_area.get_x(), cell_area.get_y(), 4, cell_area.get_height()); + cr->fill(); + } else if (start.has_tag(tag_modified) || end.has_tag(tag_modified)) { + cr->set_source_rgba(0.9, 0.9, 0.0, 0.75); + cr->rectangle(cell_area.get_x(), cell_area.get_y(), 4, cell_area.get_height()); + cr->fill(); + } + if (start.has_tag(tag_removed_below) || end.has_tag(tag_removed_below)) { + cr->set_source_rgba(0.75, 0.0, 0.0, 0.5); + cr->rectangle(cell_area.get_x() - 4, cell_area.get_y() + cell_area.get_height() - 2, 8, 2); + cr->fill(); + } + if (start.has_tag(tag_removed_above) || end.has_tag(tag_removed_above)) { + cr->set_source_rgba(0.75, 0.0, 0.0, 0.5); + cr->rectangle(cell_area.get_x() - 4, cell_area.get_y(), 8, 2); + cr->fill(); + } } -Source::DiffView::DiffView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language) : BaseView(file_path, language), renderer(new Renderer()) { - boost::system::error_code ec; - canonical_file_path=boost::filesystem::canonical(file_path, ec); - if(ec) - canonical_file_path=file_path; - - renderer->tag_added=get_buffer()->create_tag("git_added"); - renderer->tag_modified=get_buffer()->create_tag("git_modified"); - renderer->tag_removed=get_buffer()->create_tag("git_removed"); - renderer->tag_removed_below=get_buffer()->create_tag(); - renderer->tag_removed_above=get_buffer()->create_tag(); - - configure(); +Source::DiffView::DiffView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language) : BaseView( + file_path, language), renderer(new Renderer()) { + boost::system::error_code ec; + canonical_file_path = boost::filesystem::canonical(file_path, ec); + if (ec) + canonical_file_path = file_path; + + renderer->tag_added = get_buffer()->create_tag("git_added"); + renderer->tag_modified = get_buffer()->create_tag("git_modified"); + renderer->tag_removed = get_buffer()->create_tag("git_removed"); + renderer->tag_removed_below = get_buffer()->create_tag(); + renderer->tag_removed_above = get_buffer()->create_tag(); + + configure(); } Source::DiffView::~DiffView() { - dispatcher.disconnect(); - if(repository) { - get_gutter(Gtk::TextWindowType::TEXT_WINDOW_LEFT)->remove(renderer.get()); - buffer_insert_connection.disconnect(); - buffer_erase_connection.disconnect(); - monitor_changed_connection.disconnect(); - delayed_buffer_changed_connection.disconnect(); - delayed_monitor_changed_connection.disconnect(); - - parse_stop=true; - if(parse_thread.joinable()) - parse_thread.join(); - } + dispatcher.disconnect(); + if (repository) { + get_gutter(Gtk::TextWindowType::TEXT_WINDOW_LEFT)->remove(renderer.get()); + buffer_insert_connection.disconnect(); + buffer_erase_connection.disconnect(); + monitor_changed_connection.disconnect(); + delayed_buffer_changed_connection.disconnect(); + delayed_monitor_changed_connection.disconnect(); + + parse_stop = true; + if (parse_thread.joinable()) + parse_thread.join(); + } } void Source::DiffView::configure() { - if(Config::get().source.show_git_diff) { - if(repository) - return; - } - else if(repository) { - get_gutter(Gtk::TextWindowType::TEXT_WINDOW_LEFT)->remove(renderer.get()); - buffer_insert_connection.disconnect(); - buffer_erase_connection.disconnect(); - monitor_changed_connection.disconnect(); - delayed_buffer_changed_connection.disconnect(); - delayed_monitor_changed_connection.disconnect(); - - parse_stop=true; - if(parse_thread.joinable()) - parse_thread.join(); - repository=nullptr; - diff=nullptr; - - return; - } - else - return; - - try { - repository=Git::get_repository(this->file_path.parent_path()); - } - catch(const std::exception &) { - return; - } - - get_gutter(Gtk::TextWindowType::TEXT_WINDOW_LEFT)->insert(renderer.get(), -40); - parse_state=ParseState::STARTING; - parse_stop=false; - monitor_changed=false; - - buffer_insert_connection=get_buffer()->signal_insert().connect([this](const Gtk::TextBuffer::iterator &iter ,const Glib::ustring &text, int) { - //Do not perform git diff if no newline is added and line is already marked as added - if(!iter.starts_line() && iter.has_tag(renderer->tag_added)) { - bool newline=false; - for(auto &c: text.raw()) { - if(c=='\n') { - newline=true; - break; - } - } - if(!newline) + if (Config::get().source.show_git_diff) { + if (repository) + return; + } else if (repository) { + get_gutter(Gtk::TextWindowType::TEXT_WINDOW_LEFT)->remove(renderer.get()); + buffer_insert_connection.disconnect(); + buffer_erase_connection.disconnect(); + monitor_changed_connection.disconnect(); + delayed_buffer_changed_connection.disconnect(); + delayed_monitor_changed_connection.disconnect(); + + parse_stop = true; + if (parse_thread.joinable()) + parse_thread.join(); + repository = nullptr; + diff = nullptr; + return; - } - //Remove tag_removed_above/below if newline is inserted - else if(!text.empty() && text[0]=='\n' && iter.has_tag(renderer->tag_removed)) { - auto start_iter=get_buffer()->get_iter_at_line(iter.get_line()); - auto end_iter=get_iter_at_line_end(iter.get_line()); - end_iter.forward_char(); - get_buffer()->remove_tag(renderer->tag_removed_above, start_iter, end_iter); - get_buffer()->remove_tag(renderer->tag_removed_below, start_iter, end_iter); - } - parse_state=ParseState::IDLE; - delayed_buffer_changed_connection.disconnect(); - delayed_buffer_changed_connection=Glib::signal_timeout().connect([this]() { - parse_state=ParseState::STARTING; - return false; - }, 250); - }, false); - - buffer_erase_connection=get_buffer()->signal_erase().connect([this](const Gtk::TextBuffer::iterator &start_iter, const Gtk::TextBuffer::iterator &end_iter) { - //Do not perform git diff if start_iter and end_iter is at the same line in addition to the line is tagged added - if(start_iter.get_line()==end_iter.get_line() && start_iter.has_tag(renderer->tag_added)) - return; - - parse_state=ParseState::IDLE; - delayed_buffer_changed_connection.disconnect(); - delayed_buffer_changed_connection=Glib::signal_timeout().connect([this]() { - parse_state=ParseState::STARTING; - return false; - }, 250); - }, false); - - monitor_changed_connection=repository->monitor->signal_changed().connect([this](const Glib::RefPtr<Gio::File> &file, - const Glib::RefPtr<Gio::File>&, - Gio::FileMonitorEvent monitor_event) { - if(monitor_event!=Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { - delayed_monitor_changed_connection.disconnect(); - delayed_monitor_changed_connection=Glib::signal_timeout().connect([this]() { - monitor_changed=true; - parse_state=ParseState::STARTING; - std::unique_lock<std::mutex> lock(parse_mutex); - diff=nullptr; - return false; - }, 500); - } - }); - - parse_thread=std::thread([this]() { - std::string status_branch; + } else + return; + try { - diff=get_diff(); - status_branch=repository->get_branch(); + repository = Git::get_repository(this->file_path.parent_path()); } - catch(const std::exception &) { - status_branch=""; + catch (const std::exception &) { + return; } - dispatcher.post([this, status_branch=std::move(status_branch)] { - this->status_branch=status_branch; - if(update_status_branch) - update_status_branch(this); - }); - - try { - while(true) { - while(!parse_stop && parse_state!=ParseState::STARTING && parse_state!=ParseState::PROCESSING) - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - if(parse_stop) - break; - std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock); - auto expected=ParseState::STARTING; - if(parse_state.compare_exchange_strong(expected, ParseState::PREPROCESSING)) { - dispatcher.post([this] { - auto expected=ParseState::PREPROCESSING; - std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock); - if(parse_lock.try_lock()) { - if(parse_state.compare_exchange_strong(expected, ParseState::PROCESSING)) - parse_buffer=get_buffer()->get_text(); - parse_lock.unlock(); - } - else - parse_state.compare_exchange_strong(expected, ParseState::STARTING); - }); + + get_gutter(Gtk::TextWindowType::TEXT_WINDOW_LEFT)->insert(renderer.get(), -40); + parse_state = ParseState::STARTING; + parse_stop = false; + monitor_changed = false; + + buffer_insert_connection = get_buffer()->signal_insert().connect( + [this](const Gtk::TextBuffer::iterator &iter, const Glib::ustring &text, int) { + //Do not perform git diff if no newline is added and line is already marked as added + if (!iter.starts_line() && iter.has_tag(renderer->tag_added)) { + bool newline = false; + for (auto &c: text.raw()) { + if (c == '\n') { + newline = true; + break; + } + } + if (!newline) + return; + } + //Remove tag_removed_above/below if newline is inserted + else if (!text.empty() && text[0] == '\n' && iter.has_tag(renderer->tag_removed)) { + auto start_iter = get_buffer()->get_iter_at_line(iter.get_line()); + auto end_iter = get_iter_at_line_end(iter.get_line()); + end_iter.forward_char(); + get_buffer()->remove_tag(renderer->tag_removed_above, start_iter, end_iter); + get_buffer()->remove_tag(renderer->tag_removed_below, start_iter, end_iter); + } + parse_state = ParseState::IDLE; + delayed_buffer_changed_connection.disconnect(); + delayed_buffer_changed_connection = Glib::signal_timeout().connect([this]() { + parse_state = ParseState::STARTING; + return false; + }, 250); + }, false); + + buffer_erase_connection = get_buffer()->signal_erase().connect( + [this](const Gtk::TextBuffer::iterator &start_iter, const Gtk::TextBuffer::iterator &end_iter) { + //Do not perform git diff if start_iter and end_iter is at the same line in addition to the line is tagged added + if (start_iter.get_line() == end_iter.get_line() && start_iter.has_tag(renderer->tag_added)) + return; + + parse_state = ParseState::IDLE; + delayed_buffer_changed_connection.disconnect(); + delayed_buffer_changed_connection = Glib::signal_timeout().connect([this]() { + parse_state = ParseState::STARTING; + return false; + }, 250); + }, false); + + monitor_changed_connection = repository->monitor->signal_changed().connect( + [this](const Glib::RefPtr<Gio::File> &file, + const Glib::RefPtr<Gio::File> &, + Gio::FileMonitorEvent monitor_event) { + if (monitor_event != Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { + delayed_monitor_changed_connection.disconnect(); + delayed_monitor_changed_connection = Glib::signal_timeout().connect([this]() { + monitor_changed = true; + parse_state = ParseState::STARTING; + std::unique_lock<std::mutex> lock(parse_mutex); + diff = nullptr; + return false; + }, 500); + } + }); + + parse_thread = std::thread([this]() { + std::string status_branch; + try { + diff = get_diff(); + status_branch = repository->get_branch(); } - else if (parse_state==ParseState::PROCESSING && parse_lock.try_lock()) { - bool expected_monitor_changed=true; - if(monitor_changed.compare_exchange_strong(expected_monitor_changed, false)) { - try { - diff=get_diff(); - dispatcher.post([this, status_branch=repository->get_branch()] { - this->status_branch=status_branch; - if(update_status_branch) - update_status_branch(this); - }); + catch (const std::exception &) { + status_branch = ""; + } + dispatcher.post([this, status_branch = std::move(status_branch)] { + this->status_branch = status_branch; + if (update_status_branch) + update_status_branch(this); + }); + + try { + while (true) { + while (!parse_stop && parse_state != ParseState::STARTING && parse_state != ParseState::PROCESSING) + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + if (parse_stop) + break; + std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock); + auto expected = ParseState::STARTING; + if (parse_state.compare_exchange_strong(expected, ParseState::PREPROCESSING)) { + dispatcher.post([this] { + auto expected = ParseState::PREPROCESSING; + std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock); + if (parse_lock.try_lock()) { + if (parse_state.compare_exchange_strong(expected, ParseState::PROCESSING)) + parse_buffer = get_buffer()->get_text(); + parse_lock.unlock(); + } else + parse_state.compare_exchange_strong(expected, ParseState::STARTING); + }); + } else if (parse_state == ParseState::PROCESSING && parse_lock.try_lock()) { + bool expected_monitor_changed = true; + if (monitor_changed.compare_exchange_strong(expected_monitor_changed, false)) { + try { + diff = get_diff(); + dispatcher.post([this, status_branch = repository->get_branch()] { + this->status_branch = status_branch; + if (update_status_branch) + update_status_branch(this); + }); + } + catch (const std::exception &) { + dispatcher.post([this] { + get_buffer()->remove_tag(renderer->tag_added, get_buffer()->begin(), + get_buffer()->end()); + get_buffer()->remove_tag(renderer->tag_modified, get_buffer()->begin(), + get_buffer()->end()); + get_buffer()->remove_tag(renderer->tag_removed, get_buffer()->begin(), + get_buffer()->end()); + get_buffer()->remove_tag(renderer->tag_removed_below, get_buffer()->begin(), + get_buffer()->end()); + get_buffer()->remove_tag(renderer->tag_removed_above, get_buffer()->begin(), + get_buffer()->end()); + renderer->queue_draw(); + this->status_branch = ""; + if (update_status_branch) + update_status_branch(this); + }); + } + } + if (diff) + lines = diff->get_lines(parse_buffer.raw()); + else { + lines.added.clear(); + lines.modified.clear(); + lines.removed.clear(); + } + auto expected = ParseState::PROCESSING; + if (parse_state.compare_exchange_strong(expected, ParseState::POSTPROCESSING)) { + parse_lock.unlock(); + dispatcher.post([this] { + std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock); + if (parse_lock.try_lock()) { + auto expected = ParseState::POSTPROCESSING; + if (parse_state.compare_exchange_strong(expected, ParseState::IDLE)) + update_lines(); + } + }); + } + } } - catch(const std::exception &) { - dispatcher.post([this] { + } + catch (const std::exception &e) { + dispatcher.post([this, e_what = e.what()] { get_buffer()->remove_tag(renderer->tag_added, get_buffer()->begin(), get_buffer()->end()); get_buffer()->remove_tag(renderer->tag_modified, get_buffer()->begin(), get_buffer()->end()); get_buffer()->remove_tag(renderer->tag_removed, get_buffer()->begin(), get_buffer()->end()); get_buffer()->remove_tag(renderer->tag_removed_below, get_buffer()->begin(), get_buffer()->end()); get_buffer()->remove_tag(renderer->tag_removed_above, get_buffer()->begin(), get_buffer()->end()); renderer->queue_draw(); - this->status_branch=""; - if(update_status_branch) - update_status_branch(this); - }); - } - } - if(diff) - lines=diff->get_lines(parse_buffer.raw()); - else { - lines.added.clear(); - lines.modified.clear(); - lines.removed.clear(); - } - auto expected=ParseState::PROCESSING; - if(parse_state.compare_exchange_strong(expected, ParseState::POSTPROCESSING)) { - parse_lock.unlock(); - dispatcher.post([this] { - std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock); - if(parse_lock.try_lock()) { - auto expected=ParseState::POSTPROCESSING; - if(parse_state.compare_exchange_strong(expected, ParseState::IDLE)) - update_lines(); - } + Terminal::get().print(std::string("Error (git): ") + e_what + '\n', true); }); - } } - } - } - catch(const std::exception &e) { - dispatcher.post([this, e_what=e.what()] { - get_buffer()->remove_tag(renderer->tag_added, get_buffer()->begin(), get_buffer()->end()); - get_buffer()->remove_tag(renderer->tag_modified, get_buffer()->begin(), get_buffer()->end()); - get_buffer()->remove_tag(renderer->tag_removed, get_buffer()->begin(), get_buffer()->end()); - get_buffer()->remove_tag(renderer->tag_removed_below, get_buffer()->begin(), get_buffer()->end()); - get_buffer()->remove_tag(renderer->tag_removed_above, get_buffer()->begin(), get_buffer()->end()); - renderer->queue_draw(); - Terminal::get().print(std::string("Error (git): ")+e_what+'\n', true); - }); - } - }); + }); } void Source::DiffView::rename(const boost::filesystem::path &path) { - Source::BaseView::rename(path); - - std::lock_guard<std::mutex> lock(canonical_file_path_mutex); - boost::system::error_code ec; - canonical_file_path=boost::filesystem::canonical(path, ec); - if(ec) - canonical_file_path=path; + Source::BaseView::rename(path); + + std::lock_guard<std::mutex> lock(canonical_file_path_mutex); + boost::system::error_code ec; + canonical_file_path = boost::filesystem::canonical(path, ec); + if (ec) + canonical_file_path = path; } void Source::DiffView::git_goto_next_diff() { - auto iter=get_buffer()->get_insert()->get_iter(); - auto insert_iter=iter; - bool wrapped=false; - iter.forward_char(); - for(;;) { - auto toggled_tags=iter.get_toggled_tags(); - for(auto &toggled_tag: toggled_tags) { - if(toggled_tag->property_name()=="git_added" || - toggled_tag->property_name()=="git_modified" || - toggled_tag->property_name()=="git_removed") { - get_buffer()->place_cursor(iter); - scroll_to(get_buffer()->get_insert(), 0.0, 1.0, 0.5); - return; - } - } - if(wrapped && (iter==insert_iter || iter==get_buffer()->end())) - break; - if(!wrapped && iter==get_buffer()->end()) { - iter=get_buffer()->begin(); - wrapped=true; + auto iter = get_buffer()->get_insert()->get_iter(); + auto insert_iter = iter; + bool wrapped = false; + iter.forward_char(); + for (;;) { + auto toggled_tags = iter.get_toggled_tags(); + for (auto &toggled_tag: toggled_tags) { + if (toggled_tag->property_name() == "git_added" || + toggled_tag->property_name() == "git_modified" || + toggled_tag->property_name() == "git_removed") { + get_buffer()->place_cursor(iter); + scroll_to(get_buffer()->get_insert(), 0.0, 1.0, 0.5); + return; + } + } + if (wrapped && (iter == insert_iter || iter == get_buffer()->end())) + break; + if (!wrapped && iter == get_buffer()->end()) { + iter = get_buffer()->begin(); + wrapped = true; + } else + iter.forward_char(); } - else - iter.forward_char(); - } - Info::get().print("No changes found in current buffer"); + Info::get().print("No changes found in current buffer"); } std::string Source::DiffView::git_get_diff_details() { - std::string details; - if(diff) { - auto line_nr=get_buffer()->get_insert()->get_iter().get_line(); - auto iter=get_buffer()->get_iter_at_line(line_nr); - if(iter.has_tag(renderer->tag_removed_above)) - --line_nr; - std::unique_lock<std::mutex> lock(parse_mutex); - parse_buffer=get_buffer()->get_text(); - details=diff->get_details(parse_buffer.raw(), line_nr); - } - if(details.empty()) - Info::get().print("No changes found at current line"); - return details; + std::string details; + if (diff) { + auto line_nr = get_buffer()->get_insert()->get_iter().get_line(); + auto iter = get_buffer()->get_iter_at_line(line_nr); + if (iter.has_tag(renderer->tag_removed_above)) + --line_nr; + std::unique_lock<std::mutex> lock(parse_mutex); + parse_buffer = get_buffer()->get_text(); + details = diff->get_details(parse_buffer.raw(), line_nr); + } + if (details.empty()) + Info::get().print("No changes found at current line"); + return details; } ///Return repository diff instance. Throws exception on error std::unique_ptr<Git::Repository::Diff> Source::DiffView::get_diff() { - auto work_path=filesystem::get_normal_path(repository->get_work_path()); - boost::filesystem::path relative_path; - { - std::unique_lock<std::mutex> lock(canonical_file_path_mutex); - relative_path=filesystem::get_relative_path(canonical_file_path, work_path); - if(relative_path.empty()) - throw std::runtime_error("not a relative path"); - } - return std::make_unique<Git::Repository::Diff>(repository->get_diff(relative_path)); + auto work_path = filesystem::get_normal_path(repository->get_work_path()); + boost::filesystem::path relative_path; + { + std::unique_lock<std::mutex> lock(canonical_file_path_mutex); + relative_path = filesystem::get_relative_path(canonical_file_path, work_path); + if (relative_path.empty()) + throw std::runtime_error("not a relative path"); + } + return std::make_unique<Git::Repository::Diff>(repository->get_diff(relative_path)); } void Source::DiffView::update_lines() { - get_buffer()->remove_tag(renderer->tag_added, get_buffer()->begin(), get_buffer()->end()); - get_buffer()->remove_tag(renderer->tag_modified, get_buffer()->begin(), get_buffer()->end()); - get_buffer()->remove_tag(renderer->tag_removed, get_buffer()->begin(), get_buffer()->end()); - get_buffer()->remove_tag(renderer->tag_removed_below, get_buffer()->begin(), get_buffer()->end()); - get_buffer()->remove_tag(renderer->tag_removed_above, get_buffer()->begin(), get_buffer()->end()); - - for(auto &added: lines.added) { - auto start_iter=get_buffer()->get_iter_at_line(added.first); - auto end_iter=get_iter_at_line_end(added.second-1); - end_iter.forward_char(); - get_buffer()->apply_tag(renderer->tag_added, start_iter, end_iter); - } - for(auto &modified: lines.modified) { - auto start_iter=get_buffer()->get_iter_at_line(modified.first); - auto end_iter=get_iter_at_line_end(modified.second-1); - end_iter.forward_char(); - get_buffer()->apply_tag(renderer->tag_modified, start_iter, end_iter); - } - for(auto &line_nr: lines.removed) { - Gtk::TextIter removed_start, removed_end; - if(line_nr>=0) { - auto start_iter=get_buffer()->get_iter_at_line(line_nr); - removed_start=start_iter; - auto end_iter=get_iter_at_line_end(line_nr); - end_iter.forward_char(); - removed_end=end_iter; - get_buffer()->apply_tag(renderer->tag_removed_below, start_iter, end_iter); + get_buffer()->remove_tag(renderer->tag_added, get_buffer()->begin(), get_buffer()->end()); + get_buffer()->remove_tag(renderer->tag_modified, get_buffer()->begin(), get_buffer()->end()); + get_buffer()->remove_tag(renderer->tag_removed, get_buffer()->begin(), get_buffer()->end()); + get_buffer()->remove_tag(renderer->tag_removed_below, get_buffer()->begin(), get_buffer()->end()); + get_buffer()->remove_tag(renderer->tag_removed_above, get_buffer()->begin(), get_buffer()->end()); + + for (auto &added: lines.added) { + auto start_iter = get_buffer()->get_iter_at_line(added.first); + auto end_iter = get_iter_at_line_end(added.second - 1); + end_iter.forward_char(); + get_buffer()->apply_tag(renderer->tag_added, start_iter, end_iter); } - if(line_nr+1<get_buffer()->get_line_count()) { - auto start_iter=get_buffer()->get_iter_at_line(line_nr+1); - if(line_nr<0) - removed_start=start_iter; - auto end_iter=get_iter_at_line_end(line_nr+1); - end_iter.forward_char(); - removed_end=end_iter; - get_buffer()->apply_tag(renderer->tag_removed_above, start_iter, end_iter); + for (auto &modified: lines.modified) { + auto start_iter = get_buffer()->get_iter_at_line(modified.first); + auto end_iter = get_iter_at_line_end(modified.second - 1); + end_iter.forward_char(); + get_buffer()->apply_tag(renderer->tag_modified, start_iter, end_iter); } - get_buffer()->apply_tag(renderer->tag_removed, removed_start, removed_end); - } - - renderer->queue_draw(); + for (auto &line_nr: lines.removed) { + Gtk::TextIter removed_start, removed_end; + if (line_nr >= 0) { + auto start_iter = get_buffer()->get_iter_at_line(line_nr); + removed_start = start_iter; + auto end_iter = get_iter_at_line_end(line_nr); + end_iter.forward_char(); + removed_end = end_iter; + get_buffer()->apply_tag(renderer->tag_removed_below, start_iter, end_iter); + } + if (line_nr + 1 < get_buffer()->get_line_count()) { + auto start_iter = get_buffer()->get_iter_at_line(line_nr + 1); + if (line_nr < 0) + removed_start = start_iter; + auto end_iter = get_iter_at_line_end(line_nr + 1); + end_iter.forward_char(); + removed_end = end_iter; + get_buffer()->apply_tag(renderer->tag_removed_above, start_iter, end_iter); + } + get_buffer()->apply_tag(renderer->tag_removed, removed_start, removed_end); + } + + renderer->queue_draw(); } diff --git a/src/source_diff.h b/src/source_diff.h index d1a7ece8..5f48f931 100644 --- a/src/source_diff.h +++ b/src/source_diff.h @@ -1,4 +1,5 @@ #pragma once + #include "source_base.h" #include <boost/filesystem.hpp> #include "dispatcher.h" @@ -10,60 +11,67 @@ #include "git.h" namespace Source { - class DiffView : virtual public Source::BaseView { - enum class ParseState {IDLE, STARTING, PREPROCESSING, PROCESSING, POSTPROCESSING}; - - class Renderer : public Gsv::GutterRenderer { + class DiffView : virtual public Source::BaseView { + enum class ParseState { + IDLE, STARTING, PREPROCESSING, PROCESSING, POSTPROCESSING + }; + + class Renderer : public Gsv::GutterRenderer { + public: + Renderer(); + + Glib::RefPtr<Gtk::TextTag> tag_added; + Glib::RefPtr<Gtk::TextTag> tag_modified; + Glib::RefPtr<Gtk::TextTag> tag_removed; + Glib::RefPtr<Gtk::TextTag> tag_removed_below; + Glib::RefPtr<Gtk::TextTag> tag_removed_above; + + protected: + void draw_vfunc(const Cairo::RefPtr<Cairo::Context> &cr, const Gdk::Rectangle &background_area, + const Gdk::Rectangle &cell_area, Gtk::TextIter &start, Gtk::TextIter &end, + Gsv::GutterRendererState p6) override; + }; + public: - Renderer(); - - Glib::RefPtr<Gtk::TextTag> tag_added; - Glib::RefPtr<Gtk::TextTag> tag_modified; - Glib::RefPtr<Gtk::TextTag> tag_removed; - Glib::RefPtr<Gtk::TextTag> tag_removed_below; - Glib::RefPtr<Gtk::TextTag> tag_removed_above; - - protected: - void draw_vfunc(const Cairo::RefPtr<Cairo::Context> &cr, const Gdk::Rectangle &background_area, - const Gdk::Rectangle &cell_area, Gtk::TextIter &start, Gtk::TextIter &end, - Gsv::GutterRendererState p6) override; + DiffView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); + + ~DiffView(); + + void configure() override; + + void rename(const boost::filesystem::path &path) override; + + void git_goto_next_diff(); + + std::string git_get_diff_details(); + + /// Use canonical path to follow symbolic links + boost::filesystem::path canonical_file_path; + private: + std::mutex canonical_file_path_mutex; + + std::unique_ptr<Renderer> renderer; + Dispatcher dispatcher; + + std::shared_ptr<Git::Repository> repository; + std::unique_ptr<Git::Repository::Diff> diff; + + std::unique_ptr<Git::Repository::Diff> get_diff(); + + std::thread parse_thread; + std::atomic<ParseState> parse_state; + std::mutex parse_mutex; + std::atomic<bool> parse_stop; + Glib::ustring parse_buffer; + sigc::connection buffer_insert_connection; + sigc::connection buffer_erase_connection; + sigc::connection monitor_changed_connection; + sigc::connection delayed_buffer_changed_connection; + sigc::connection delayed_monitor_changed_connection; + std::atomic<bool> monitor_changed; + + Git::Repository::Diff::Lines lines; + + void update_lines(); }; - public: - DiffView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); - ~DiffView(); - - void configure() override; - - void rename(const boost::filesystem::path &path) override; - - void git_goto_next_diff(); - std::string git_get_diff_details(); - - /// Use canonical path to follow symbolic links - boost::filesystem::path canonical_file_path; - private: - std::mutex canonical_file_path_mutex; - - std::unique_ptr<Renderer> renderer; - Dispatcher dispatcher; - - std::shared_ptr<Git::Repository> repository; - std::unique_ptr<Git::Repository::Diff> diff; - std::unique_ptr<Git::Repository::Diff> get_diff(); - - std::thread parse_thread; - std::atomic<ParseState> parse_state; - std::mutex parse_mutex; - std::atomic<bool> parse_stop; - Glib::ustring parse_buffer; - sigc::connection buffer_insert_connection; - sigc::connection buffer_erase_connection; - sigc::connection monitor_changed_connection; - sigc::connection delayed_buffer_changed_connection; - sigc::connection delayed_monitor_changed_connection; - std::atomic<bool> monitor_changed; - - Git::Repository::Diff::Lines lines; - void update_lines(); - }; } diff --git a/src/source_language_protocol.cc b/src/source_language_protocol.cc index d6d40eee..68c3b37c 100644 --- a/src/source_language_protocol.cc +++ b/src/source_language_protocol.cc @@ -4,1405 +4,1549 @@ #include "terminal.h" #include "project.h" #include "filesystem.h" + #ifdef JUCI_ENABLE_DEBUG #include "debug_lldb.h" #endif + #include "menu.h" #include <regex> #include <future> #include <limits> -const bool output_messages_and_errors=false; +const bool output_messages_and_errors = false; -LanguageProtocol::Client::Client(std::string root_uri_, std::string language_id_) : root_uri(std::move(root_uri_)), language_id(std::move(language_id_)) { - process = std::make_unique<TinyProcessLib::Process>(language_id+"-language-server", root_uri, - [this](const char *bytes, size_t n) { - server_message_stream.write(bytes, n); - parse_server_message(); - }, [](const char *bytes, size_t n) { - std::cerr.write(bytes, n); - }, true); +LanguageProtocol::Client::Client(std::string root_uri_, std::string language_id_) : root_uri(std::move(root_uri_)), + language_id( + std::move(language_id_)) { + process = std::make_unique<TinyProcessLib::Process>(language_id + "-language-server", root_uri, + [this](const char *bytes, size_t n) { + server_message_stream.write(bytes, n); + parse_server_message(); + }, [](const char *bytes, size_t n) { + std::cerr.write(bytes, n); + }, true); } -std::shared_ptr<LanguageProtocol::Client> LanguageProtocol::Client::get(const boost::filesystem::path &file_path, const std::string &language_id) { - std::string root_uri; - auto build=Project::Build::create(file_path); - if(!build->project_path.empty()) - root_uri=build->project_path.string(); - else - root_uri=file_path.parent_path().string(); - - auto cache_id=root_uri+'|'+language_id; - - static std::unordered_map<std::string, std::weak_ptr<Client>> cache; - static std::mutex mutex; - std::lock_guard<std::mutex> lock(mutex); - auto it=cache.find(cache_id); - if(it==cache.end()) - it=cache.emplace(cache_id, std::weak_ptr<Client>()).first; - auto instance=it->second.lock(); - if(!instance) - it->second=instance=std::shared_ptr<Client>(new Client(root_uri, language_id), [](Client *client_ptr) { - std::thread delete_thread([client_ptr] { - delete client_ptr; - }); - delete_thread.detach(); - }); - return instance; +std::shared_ptr<LanguageProtocol::Client> +LanguageProtocol::Client::get(const boost::filesystem::path &file_path, const std::string &language_id) { + std::string root_uri; + auto build = Project::Build::create(file_path); + if (!build->project_path.empty()) + root_uri = build->project_path.string(); + else + root_uri = file_path.parent_path().string(); + + auto cache_id = root_uri + '|' + language_id; + + static std::unordered_map<std::string, std::weak_ptr<Client>> cache; + static std::mutex mutex; + std::lock_guard<std::mutex> lock(mutex); + auto it = cache.find(cache_id); + if (it == cache.end()) + it = cache.emplace(cache_id, std::weak_ptr<Client>()).first; + auto instance = it->second.lock(); + if (!instance) + it->second = instance = std::shared_ptr<Client>(new Client(root_uri, language_id), [](Client *client_ptr) { + std::thread delete_thread([client_ptr] { + delete client_ptr; + }); + delete_thread.detach(); + }); + return instance; } LanguageProtocol::Client::~Client() { - std::promise<void> result_processed; - write_request(nullptr, "shutdown", "", [this, &result_processed](const boost::property_tree::ptree &result, bool error) { - if(!error) - this->write_notification("exit", ""); - result_processed.set_value(); - }); - result_processed.get_future().get(); - - std::unique_lock<std::mutex> lock(timeout_threads_mutex); - for(auto &thread: timeout_threads) - thread.join(); - - int exit_status=-1; - for(size_t c=0;c<20;++c) { - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - if(process->try_get_exit_status(exit_status)) - break; - } - if(output_messages_and_errors) - std::cout << "Language server exit status: " << exit_status << std::endl; - if(exit_status==-1) - process->kill(); + std::promise<void> result_processed; + write_request(nullptr, "shutdown", "", + [this, &result_processed](const boost::property_tree::ptree &result, bool error) { + if (!error) + this->write_notification("exit", ""); + result_processed.set_value(); + }); + result_processed.get_future().get(); + + std::unique_lock<std::mutex> lock(timeout_threads_mutex); + for (auto &thread: timeout_threads) + thread.join(); + + int exit_status = -1; + for (size_t c = 0; c < 20; ++c) { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + if (process->try_get_exit_status(exit_status)) + break; + } + if (output_messages_and_errors) + std::cout << "Language server exit status: " << exit_status << std::endl; + if (exit_status == -1) + process->kill(); } LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::LanguageProtocolView *view) { - if(view) { - std::unique_lock<std::mutex> lock(views_mutex); - views.emplace(view); - } - - std::lock_guard<std::mutex> lock(initialize_mutex); - - if(initialized) - return capabilities; - - std::promise<void> result_processed; - write_request(nullptr, "initialize", "\"processId\":"+std::to_string(process->get_id())+",\"rootUri\":\"file://"+root_uri+"\",\"capabilities\":{\"workspace\":{\"didChangeConfiguration\":{\"dynamicRegistration\":true},\"didChangeWatchedFiles\":{\"dynamicRegistration\":true},\"symbol\":{\"dynamicRegistration\":true},\"executeCommand\":{\"dynamicRegistration\":true}},\"textDocument\":{\"synchronization\":{\"dynamicRegistration\":true,\"willSave\":true,\"willSaveWaitUntil\":true,\"didSave\":true},\"completion\":{\"dynamicRegistration\":true,\"completionItem\":{\"snippetSupport\":true}},\"hover\":{\"dynamicRegistration\":true},\"signatureHelp\":{\"dynamicRegistration\":true},\"definition\":{\"dynamicRegistration\":true},\"references\":{\"dynamicRegistration\":true},\"documentHighlight\":{\"dynamicRegistration\":true},\"documentSymbol\":{\"dynamicRegistration\":true},\"codeAction\":{\"dynamicRegistration\":true},\"codeLens\":{\"dynamicRegistration\":true},\"formatting\":{\"dynamicRegistration\":true},\"rangeFormatting\":{\"dynamicRegistration\":true},\"onTypeFormatting\":{\"dynamicRegistration\":true},\"rename\":{\"dynamicRegistration\":true},\"documentLink\":{\"dynamicRegistration\":true}}},\"initializationOptions\":{\"omitInitBuild\":true},\"trace\":\"off\"", [this, &result_processed](const boost::property_tree::ptree &result, bool error) { - if(!error) { - auto capabilities_pt=result.find("capabilities"); - if(capabilities_pt!=result.not_found()) { - capabilities.text_document_sync=static_cast<LanguageProtocol::Capabilities::TextDocumentSync>(capabilities_pt->second.get<unsigned>("textDocumentSync", 0)); - capabilities.hover=capabilities_pt->second.get<bool>("hoverProvider", false); - capabilities.completion=capabilities_pt->second.find("completionProvider")!=capabilities_pt->second.not_found()?true:false; - capabilities.definition=capabilities_pt->second.get<bool>("definitionProvider", false); - capabilities.references=capabilities_pt->second.get<bool>("referencesProvider", false); - capabilities.document_highlight=capabilities_pt->second.get<bool>("documentHighlightProvider", false); - capabilities.workspace_symbol=capabilities_pt->second.get<bool>("workspaceSymbolProvider", false); - capabilities.document_formatting=capabilities_pt->second.get<bool>("documentFormattingProvider", false); - capabilities.document_range_formatting=capabilities_pt->second.get<bool>("documentRangeFormattingProvider", false); - capabilities.rename=capabilities_pt->second.get<bool>("renameProvider", false); - } - - write_notification("initialized", ""); - if(language_id=="rust") - write_notification("workspace/didChangeConfiguration", "\"settings\":{\"rust\":{\"sysroot\":null,\"target\":null,\"rustflags\":null,\"clear_env_rust_log\":true,\"build_lib\":null,\"build_bin\":null,\"cfg_test\":false,\"unstable_features\":false,\"wait_to_build\":500,\"show_warnings\":true,\"goto_def_racer_fallback\":false,\"use_crate_blacklist\":true,\"build_on_save\":false,\"workspace_mode\":true,\"analyze_package\":null,\"features\":[],\"all_features\":false,\"no_default_features\":false}}"); + if (view) { + std::unique_lock<std::mutex> lock(views_mutex); + views.emplace(view); } - result_processed.set_value(); - }); - result_processed.get_future().get(); - - initialized=true; - return capabilities; + + std::lock_guard<std::mutex> lock(initialize_mutex); + + if (initialized) + return capabilities; + + std::promise<void> result_processed; + write_request(nullptr, "initialize", + "\"processId\":" + std::to_string(process->get_id()) + ",\"rootUri\":\"file://" + root_uri + + "\",\"capabilities\":{\"workspace\":{\"didChangeConfiguration\":{\"dynamicRegistration\":true},\"didChangeWatchedFiles\":{\"dynamicRegistration\":true},\"symbol\":{\"dynamicRegistration\":true},\"executeCommand\":{\"dynamicRegistration\":true}},\"textDocument\":{\"synchronization\":{\"dynamicRegistration\":true,\"willSave\":true,\"willSaveWaitUntil\":true,\"didSave\":true},\"completion\":{\"dynamicRegistration\":true,\"completionItem\":{\"snippetSupport\":true}},\"hover\":{\"dynamicRegistration\":true},\"signatureHelp\":{\"dynamicRegistration\":true},\"definition\":{\"dynamicRegistration\":true},\"references\":{\"dynamicRegistration\":true},\"documentHighlight\":{\"dynamicRegistration\":true},\"documentSymbol\":{\"dynamicRegistration\":true},\"codeAction\":{\"dynamicRegistration\":true},\"codeLens\":{\"dynamicRegistration\":true},\"formatting\":{\"dynamicRegistration\":true},\"rangeFormatting\":{\"dynamicRegistration\":true},\"onTypeFormatting\":{\"dynamicRegistration\":true},\"rename\":{\"dynamicRegistration\":true},\"documentLink\":{\"dynamicRegistration\":true}}},\"initializationOptions\":{\"omitInitBuild\":true},\"trace\":\"off\"", + [this, &result_processed](const boost::property_tree::ptree &result, bool error) { + if (!error) { + auto capabilities_pt = result.find("capabilities"); + if (capabilities_pt != result.not_found()) { + capabilities.text_document_sync = static_cast<LanguageProtocol::Capabilities::TextDocumentSync>(capabilities_pt->second.get<unsigned>( + "textDocumentSync", 0)); + capabilities.hover = capabilities_pt->second.get<bool>("hoverProvider", false); + capabilities.completion = capabilities_pt->second.find("completionProvider") != + capabilities_pt->second.not_found() ? true : false; + capabilities.definition = capabilities_pt->second.get<bool>("definitionProvider", false); + capabilities.references = capabilities_pt->second.get<bool>("referencesProvider", false); + capabilities.document_highlight = capabilities_pt->second.get<bool>( + "documentHighlightProvider", false); + capabilities.workspace_symbol = capabilities_pt->second.get<bool>( + "workspaceSymbolProvider", false); + capabilities.document_formatting = capabilities_pt->second.get<bool>( + "documentFormattingProvider", false); + capabilities.document_range_formatting = capabilities_pt->second.get<bool>( + "documentRangeFormattingProvider", false); + capabilities.rename = capabilities_pt->second.get<bool>("renameProvider", false); + } + + write_notification("initialized", ""); + if (language_id == "rust") + write_notification("workspace/didChangeConfiguration", + "\"settings\":{\"rust\":{\"sysroot\":null,\"target\":null,\"rustflags\":null,\"clear_env_rust_log\":true,\"build_lib\":null,\"build_bin\":null,\"cfg_test\":false,\"unstable_features\":false,\"wait_to_build\":500,\"show_warnings\":true,\"goto_def_racer_fallback\":false,\"use_crate_blacklist\":true,\"build_on_save\":false,\"workspace_mode\":true,\"analyze_package\":null,\"features\":[],\"all_features\":false,\"no_default_features\":false}}"); + } + result_processed.set_value(); + }); + result_processed.get_future().get(); + + initialized = true; + return capabilities; } void LanguageProtocol::Client::close(Source::LanguageProtocolView *view) { - { - std::unique_lock<std::mutex> lock(views_mutex); - auto it=views.find(view); - if(it!=views.end()) - views.erase(it); - } - std::unique_lock<std::mutex> lock(read_write_mutex); - for(auto it=handlers.begin();it!=handlers.end();) { - if(it->second.first==view) - it=handlers.erase(it); - else - it++; - } + { + std::unique_lock<std::mutex> lock(views_mutex); + auto it = views.find(view); + if (it != views.end()) + views.erase(it); + } + std::unique_lock<std::mutex> lock(read_write_mutex); + for (auto it = handlers.begin(); it != handlers.end();) { + if (it->second.first == view) + it = handlers.erase(it); + else + it++; + } } void LanguageProtocol::Client::parse_server_message() { - if(!header_read) { - std::string line; - while(!header_read && std::getline(server_message_stream, line)) { - if(!line.empty()) { - if(line.back()=='\r') - line.pop_back(); - if(line.compare(0, 16, "Content-Length: ")==0) { - try { - server_message_size=static_cast<size_t>(std::stoul(line.substr(16))); - } - catch(...) {} + if (!header_read) { + std::string line; + while (!header_read && std::getline(server_message_stream, line)) { + if (!line.empty()) { + if (line.back() == '\r') + line.pop_back(); + if (line.compare(0, 16, "Content-Length: ") == 0) { + try { + server_message_size = static_cast<size_t>(std::stoul(line.substr(16))); + } + catch (...) {} + } + } + if (line.empty()) { + server_message_content_pos = server_message_stream.tellg(); + server_message_size += server_message_content_pos; + header_read = true; + } } - } - if(line.empty()) { - server_message_content_pos=server_message_stream.tellg(); - server_message_size+=server_message_content_pos; - header_read=true; - } } - } - - if(header_read) { - server_message_stream.seekg(0, std::ios::end); - size_t read_size=server_message_stream.tellg(); - std::stringstream tmp; - if(read_size>=server_message_size) { - if(read_size>server_message_size) { - server_message_stream.seekg(server_message_size, std::ios::beg); - server_message_stream.seekp(server_message_size, std::ios::beg); - for(size_t c=server_message_size;c<read_size;++c) { - tmp.put(server_message_stream.get()); - server_message_stream.put(' '); - } - } - - server_message_stream.seekg(server_message_content_pos, std::ios::beg); - boost::property_tree::ptree pt; - boost::property_tree::read_json(server_message_stream, pt); - - if(output_messages_and_errors) { - std::cout << "language server: "; - boost::property_tree::write_json(std::cout, pt); - } - - auto message_id=pt.get<size_t>("id", 0); - auto result_it=pt.find("result"); - auto error_it=pt.find("error"); - { - std::unique_lock<std::mutex> lock(read_write_mutex); - if(result_it!=pt.not_found()) { - if(message_id) { - auto id_it=handlers.find(message_id); - if(id_it!=handlers.end()) { - auto function=std::move(id_it->second.second); - handlers.erase(id_it->first); - lock.unlock(); - function(result_it->second, false); - lock.lock(); + + if (header_read) { + server_message_stream.seekg(0, std::ios::end); + size_t read_size = server_message_stream.tellg(); + std::stringstream tmp; + if (read_size >= server_message_size) { + if (read_size > server_message_size) { + server_message_stream.seekg(server_message_size, std::ios::beg); + server_message_stream.seekp(server_message_size, std::ios::beg); + for (size_t c = server_message_size; c < read_size; ++c) { + tmp.put(server_message_stream.get()); + server_message_stream.put(' '); + } } - } - } - else if(error_it!=pt.not_found()) { - if(!output_messages_and_errors) - boost::property_tree::write_json(std::cerr, pt); - if(message_id) { - auto id_it=handlers.find(message_id); - if(id_it!=handlers.end()) { - auto function=std::move(id_it->second.second); - handlers.erase(id_it->first); - lock.unlock(); - function(result_it->second, true); - lock.lock(); + + server_message_stream.seekg(server_message_content_pos, std::ios::beg); + boost::property_tree::ptree pt; + boost::property_tree::read_json(server_message_stream, pt); + + if (output_messages_and_errors) { + std::cout << "language server: "; + boost::property_tree::write_json(std::cout, pt); } - } - } - else { - auto method_it=pt.find("method"); - if(method_it!=pt.not_found()) { - auto params_it=pt.find("params"); - if(params_it!=pt.not_found()) { - lock.unlock(); - handle_server_request(method_it->second.get_value<std::string>(""), params_it->second); - lock.lock(); + + auto message_id = pt.get<size_t>("id", 0); + auto result_it = pt.find("result"); + auto error_it = pt.find("error"); + { + std::unique_lock<std::mutex> lock(read_write_mutex); + if (result_it != pt.not_found()) { + if (message_id) { + auto id_it = handlers.find(message_id); + if (id_it != handlers.end()) { + auto function = std::move(id_it->second.second); + handlers.erase(id_it->first); + lock.unlock(); + function(result_it->second, false); + lock.lock(); + } + } + } else if (error_it != pt.not_found()) { + if (!output_messages_and_errors) + boost::property_tree::write_json(std::cerr, pt); + if (message_id) { + auto id_it = handlers.find(message_id); + if (id_it != handlers.end()) { + auto function = std::move(id_it->second.second); + handlers.erase(id_it->first); + lock.unlock(); + function(result_it->second, true); + lock.lock(); + } + } + } else { + auto method_it = pt.find("method"); + if (method_it != pt.not_found()) { + auto params_it = pt.find("params"); + if (params_it != pt.not_found()) { + lock.unlock(); + handle_server_request(method_it->second.get_value<std::string>(""), params_it->second); + lock.lock(); + } + } + } + } + + server_message_stream = std::stringstream(); + header_read = false; + server_message_size = static_cast<size_t>(-1); + + tmp.seekg(0, std::ios::end); + if (tmp.tellg() > 0) { + tmp.seekg(0, std::ios::beg); + server_message_stream << tmp.rdbuf(); + parse_server_message(); } - } } - } - - server_message_stream=std::stringstream(); - header_read=false; - server_message_size=static_cast<size_t>(-1); - - tmp.seekg(0, std::ios::end); - if(tmp.tellg()>0) { - tmp.seekg(0, std::ios::beg); - server_message_stream << tmp.rdbuf(); - parse_server_message(); - } } - } } -void LanguageProtocol::Client::write_request(Source::LanguageProtocolView *view, const std::string &method, const std::string ¶ms, std::function<void(const boost::property_tree::ptree &, bool error)> &&function) { - std::unique_lock<std::mutex> lock(read_write_mutex); - if(function) { - handlers.emplace(message_id, std::make_pair(view, std::move(function))); - - auto message_id=this->message_id; - std::unique_lock<std::mutex> lock(timeout_threads_mutex); - timeout_threads.emplace_back([this, message_id] { - for(size_t c=0;c<20;++c) { - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - std::unique_lock<std::mutex> lock(read_write_mutex); - auto id_it=handlers.find(message_id); - if(id_it==handlers.end()) - return; - } - std::unique_lock<std::mutex> lock(read_write_mutex); - auto id_it=handlers.find(message_id); - if(id_it!=handlers.end()) { - auto function=std::move(id_it->second.second); - handlers.erase(id_it->first); - lock.unlock(); - function(boost::property_tree::ptree(), false); - lock.lock(); - } - }); - } - std::string content("{\"jsonrpc\":\"2.0\",\"id\":"+std::to_string(message_id++)+",\"method\":\""+method+"\",\"params\":{"+params+"}}"); - auto message="Content-Length: "+std::to_string(content.size())+"\r\n\r\n"+content; - if(output_messages_and_errors) - std::cout << "Language client: " << content << std::endl; - if(!process->write(message)) { - Terminal::get().async_print("Error writing to language protocol server. Please close and reopen all project source files.\n", true); - auto id_it=handlers.find(message_id-1); - if(id_it!=handlers.end()) { - auto function=std::move(id_it->second.second); - handlers.erase(id_it->first); - lock.unlock(); - function(boost::property_tree::ptree(), false); - lock.lock(); +void LanguageProtocol::Client::write_request(Source::LanguageProtocolView *view, const std::string &method, + const std::string ¶ms, + std::function<void(const boost::property_tree::ptree &, + bool error)> &&function) { + std::unique_lock<std::mutex> lock(read_write_mutex); + if (function) { + handlers.emplace(message_id, std::make_pair(view, std::move(function))); + + auto message_id = this->message_id; + std::unique_lock<std::mutex> lock(timeout_threads_mutex); + timeout_threads.emplace_back([this, message_id] { + for (size_t c = 0; c < 20; ++c) { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + std::unique_lock<std::mutex> lock(read_write_mutex); + auto id_it = handlers.find(message_id); + if (id_it == handlers.end()) + return; + } + std::unique_lock<std::mutex> lock(read_write_mutex); + auto id_it = handlers.find(message_id); + if (id_it != handlers.end()) { + auto function = std::move(id_it->second.second); + handlers.erase(id_it->first); + lock.unlock(); + function(boost::property_tree::ptree(), false); + lock.lock(); + } + }); + } + std::string content("{\"jsonrpc\":\"2.0\",\"id\":" + std::to_string(message_id++) + ",\"method\":\"" + method + + "\",\"params\":{" + params + "}}"); + auto message = "Content-Length: " + std::to_string(content.size()) + "\r\n\r\n" + content; + if (output_messages_and_errors) + std::cout << "Language client: " << content << std::endl; + if (!process->write(message)) { + Terminal::get().async_print( + "Error writing to language protocol server. Please close and reopen all project source files.\n", true); + auto id_it = handlers.find(message_id - 1); + if (id_it != handlers.end()) { + auto function = std::move(id_it->second.second); + handlers.erase(id_it->first); + lock.unlock(); + function(boost::property_tree::ptree(), false); + lock.lock(); + } } - } } void LanguageProtocol::Client::write_notification(const std::string &method, const std::string ¶ms) { - std::unique_lock<std::mutex> lock(read_write_mutex); - std::string content("{\"jsonrpc\":\"2.0\",\"method\":\""+method+"\",\"params\":{"+params+"}}"); - auto message="Content-Length: "+std::to_string(content.size())+"\r\n\r\n"+content; - if(output_messages_and_errors) - std::cout << "Language client: " << content << std::endl; - process->write(message); + std::unique_lock<std::mutex> lock(read_write_mutex); + std::string content("{\"jsonrpc\":\"2.0\",\"method\":\"" + method + "\",\"params\":{" + params + "}}"); + auto message = "Content-Length: " + std::to_string(content.size()) + "\r\n\r\n" + content; + if (output_messages_and_errors) + std::cout << "Language client: " << content << std::endl; + process->write(message); } -void LanguageProtocol::Client::handle_server_request(const std::string &method, const boost::property_tree::ptree ¶ms) { - if(method=="textDocument/publishDiagnostics") { - std::vector<Diagnostic> diagnostics; - auto uri=params.get<std::string>("uri", ""); - if(!uri.empty()) { - auto diagnostics_pt=params.get_child("diagnostics", boost::property_tree::ptree()); - for(auto it=diagnostics_pt.begin();it!=diagnostics_pt.end();++it) { - auto range_it=it->second.find("range"); - if(range_it!=it->second.not_found()) { - auto start_it=range_it->second.find("start"); - auto end_it=range_it->second.find("end"); - if(start_it!=range_it->second.not_found() && start_it!=range_it->second.not_found()) { - diagnostics.emplace_back(Diagnostic{it->second.get<std::string>("message", ""), - std::make_pair<Source::Offset, Source::Offset>(Source::Offset(start_it->second.get<unsigned>("line", 0), start_it->second.get<unsigned>("character", 0)), - Source::Offset(end_it->second.get<unsigned>("line", 0), end_it->second.get<unsigned>("character", 0))), - it->second.get<unsigned>("severity", 0), uri}); - } - } - } - std::unique_lock<std::mutex> lock(views_mutex); - for(auto view: views) { - if(view->uri==uri) { - view->update_diagnostics(std::move(diagnostics)); - break; +void +LanguageProtocol::Client::handle_server_request(const std::string &method, const boost::property_tree::ptree ¶ms) { + if (method == "textDocument/publishDiagnostics") { + std::vector<Diagnostic> diagnostics; + auto uri = params.get<std::string>("uri", ""); + if (!uri.empty()) { + auto diagnostics_pt = params.get_child("diagnostics", boost::property_tree::ptree()); + for (auto it = diagnostics_pt.begin(); it != diagnostics_pt.end(); ++it) { + auto range_it = it->second.find("range"); + if (range_it != it->second.not_found()) { + auto start_it = range_it->second.find("start"); + auto end_it = range_it->second.find("end"); + if (start_it != range_it->second.not_found() && start_it != range_it->second.not_found()) { + diagnostics.emplace_back(Diagnostic{it->second.get<std::string>("message", ""), + std::make_pair<Source::Offset, Source::Offset>( + Source::Offset( + start_it->second.get<unsigned>("line", 0), + start_it->second.get<unsigned>("character", + 0)), + Source::Offset( + end_it->second.get<unsigned>("line", 0), + end_it->second.get<unsigned>("character", + 0))), + it->second.get<unsigned>("severity", 0), uri}); + } + } + } + std::unique_lock<std::mutex> lock(views_mutex); + for (auto view: views) { + if (view->uri == uri) { + view->update_diagnostics(std::move(diagnostics)); + break; + } + } } - } } - } } -Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language, std::string language_id_) - : Source::BaseView(file_path, language), Source::View(file_path, language), uri("file://"+file_path.string()), language_id(std::move(language_id_)), client(LanguageProtocol::Client::get(file_path, language_id)), autocomplete(this, interactive_completion, last_keyval, false) { - configure(); - get_source_buffer()->set_language(language); - get_source_buffer()->set_highlight_syntax(true); - - similar_symbol_tag=get_buffer()->create_tag(); - similar_symbol_tag->property_weight()=Pango::WEIGHT_ULTRAHEAVY; - - status_state="initializing..."; - if(update_status_state) - update_status_state(this); - - if(language_id=="javascript") { - boost::filesystem::path project_path; - auto build=Project::Build::create(file_path); - if(auto npm_build=dynamic_cast<Project::NpmBuild*>(build.get())) { - boost::system::error_code ec; - if(!npm_build->project_path.empty() && boost::filesystem::exists(npm_build->project_path/".flowconfig", ec)) { - auto executable=npm_build->project_path/"node_modules"/".bin"/"flow"; // It is recommended to use Flow binary installed in project, despite the security risk of doing so... - if(boost::filesystem::exists(executable, ec)) - flow_coverage_executable=executable; - else - flow_coverage_executable=filesystem::find_executable("flow"); - } +Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path &file_path, + Glib::RefPtr<Gsv::Language> language, std::string language_id_) + : Source::BaseView(file_path, language), Source::View(file_path, language), uri("file://" + file_path.string()), + language_id(std::move(language_id_)), client(LanguageProtocol::Client::get(file_path, language_id)), + autocomplete(this, interactive_completion, last_keyval, false) { + configure(); + get_source_buffer()->set_language(language); + get_source_buffer()->set_highlight_syntax(true); + + similar_symbol_tag = get_buffer()->create_tag(); + similar_symbol_tag->property_weight() = Pango::WEIGHT_ULTRAHEAVY; + + status_state = "initializing..."; + if (update_status_state) + update_status_state(this); + + if (language_id == "javascript") { + boost::filesystem::path project_path; + auto build = Project::Build::create(file_path); + if (auto npm_build = dynamic_cast<Project::NpmBuild *>(build.get())) { + boost::system::error_code ec; + if (!npm_build->project_path.empty() && + boost::filesystem::exists(npm_build->project_path / ".flowconfig", ec)) { + auto executable = npm_build->project_path / "node_modules" / ".bin" / + "flow"; // It is recommended to use Flow binary installed in project, despite the security risk of doing so... + if (boost::filesystem::exists(executable, ec)) + flow_coverage_executable = executable; + else + flow_coverage_executable = filesystem::find_executable("flow"); + } + } } - } - - initialize_thread=std::thread([this] { - auto capabilities=client->initialize(this); - - if(!flow_coverage_executable.empty()) - add_flow_coverage_tooltips(true); - - dispatcher.post([this, capabilities] { - this->capabilities=capabilities; - - std::string text=get_buffer()->get_text(); - escape_text(text); - client->write_notification("textDocument/didOpen", "\"textDocument\":{\"uri\":\""+uri+"\",\"languageId\":\""+language_id+"\",\"version\":"+std::to_string(document_version++)+",\"text\":\""+text+"\"}"); - - setup_autocomplete(); - setup_navigation_and_refactoring(); - Menu::get().toggle_menu_items(); - - if(status_state=="initializing...") { - status_state=""; - if(update_status_state) - update_status_state(this); - } + + initialize_thread = std::thread([this] { + auto capabilities = client->initialize(this); + + if (!flow_coverage_executable.empty()) + add_flow_coverage_tooltips(true); + + dispatcher.post([this, capabilities] { + this->capabilities = capabilities; + + std::string text = get_buffer()->get_text(); + escape_text(text); + client->write_notification("textDocument/didOpen", + "\"textDocument\":{\"uri\":\"" + uri + "\",\"languageId\":\"" + language_id + + "\",\"version\":" + std::to_string(document_version++) + ",\"text\":\"" + text + + "\"}"); + + setup_autocomplete(); + setup_navigation_and_refactoring(); + Menu::get().toggle_menu_items(); + + if (status_state == "initializing...") { + status_state = ""; + if (update_status_state) + update_status_state(this); + } + }); }); - }); - - get_buffer()->signal_changed().connect([this] { - get_buffer()->remove_tag(similar_symbol_tag, get_buffer()->begin(), get_buffer()->end()); - }); - - get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { - if(mark->get_name() == "insert") { - delayed_tag_similar_symbols_connection.disconnect(); - delayed_tag_similar_symbols_connection=Glib::signal_timeout().connect([this]() { - tag_similar_symbols(); - return false; - }, 200); - } - }); - - get_buffer()->signal_insert().connect([this](const Gtk::TextBuffer::iterator &start, const Glib::ustring &text_, int bytes) { - std::string content_changes; - if(capabilities.text_document_sync==LanguageProtocol::Capabilities::TextDocumentSync::NONE) - return; - if(capabilities.text_document_sync==LanguageProtocol::Capabilities::TextDocumentSync::INCREMENTAL) { - std::string text=text_; - escape_text(text); - content_changes="{\"range\":{\"start\":{\"line\": "+std::to_string(start.get_line())+",\"character\":"+std::to_string(start.get_line_offset())+"},\"end\":{\"line\":"+std::to_string(start.get_line())+",\"character\":"+std::to_string(start.get_line_offset())+"}},\"text\":\""+text+"\"}"; - } - else { - std::string text=get_buffer()->get_text(); - escape_text(text); - content_changes="{\"text\":\""+text+"\"}"; - } - client->write_notification("textDocument/didChange", "\"textDocument\":{\"uri\":\""+uri+"\",\"version\":"+std::to_string(document_version++)+"},\"contentChanges\":["+content_changes+"]"); - }, false); - - get_buffer()->signal_erase().connect([this](const Gtk::TextBuffer::iterator &start, const Gtk::TextBuffer::iterator &end) { - std::string content_changes; - if(capabilities.text_document_sync==LanguageProtocol::Capabilities::TextDocumentSync::NONE) - return; - if(capabilities.text_document_sync==LanguageProtocol::Capabilities::TextDocumentSync::INCREMENTAL) - content_changes="{\"range\":{\"start\":{\"line\": "+std::to_string(start.get_line())+",\"character\":"+std::to_string(start.get_line_offset())+"},\"end\":{\"line\":"+std::to_string(end.get_line())+",\"character\":"+std::to_string(end.get_line_offset())+"}},\"text\":\"\"}"; - else { - std::string text=get_buffer()->get_text(); - escape_text(text); - content_changes="{\"text\":\""+text+"\"}"; - } - client->write_notification("textDocument/didChange", "\"textDocument\":{\"uri\":\""+uri+"\",\"version\":"+std::to_string(document_version++)+"},\"contentChanges\":["+content_changes+"]"); - }, false); + + get_buffer()->signal_changed().connect([this] { + get_buffer()->remove_tag(similar_symbol_tag, get_buffer()->begin(), get_buffer()->end()); + }); + + get_buffer()->signal_mark_set().connect( + [this](const Gtk::TextBuffer::iterator &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { + if (mark->get_name() == "insert") { + delayed_tag_similar_symbols_connection.disconnect(); + delayed_tag_similar_symbols_connection = Glib::signal_timeout().connect([this]() { + tag_similar_symbols(); + return false; + }, 200); + } + }); + + get_buffer()->signal_insert().connect( + [this](const Gtk::TextBuffer::iterator &start, const Glib::ustring &text_, int bytes) { + std::string content_changes; + if (capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::NONE) + return; + if (capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::INCREMENTAL) { + std::string text = text_; + escape_text(text); + content_changes = + "{\"range\":{\"start\":{\"line\": " + std::to_string(start.get_line()) + ",\"character\":" + + std::to_string(start.get_line_offset()) + "},\"end\":{\"line\":" + + std::to_string(start.get_line()) + ",\"character\":" + + std::to_string(start.get_line_offset()) + "}},\"text\":\"" + text + "\"}"; + } else { + std::string text = get_buffer()->get_text(); + escape_text(text); + content_changes = "{\"text\":\"" + text + "\"}"; + } + client->write_notification("textDocument/didChange", + "\"textDocument\":{\"uri\":\"" + uri + "\",\"version\":" + + std::to_string(document_version++) + "},\"contentChanges\":[" + + content_changes + "]"); + }, false); + + get_buffer()->signal_erase().connect( + [this](const Gtk::TextBuffer::iterator &start, const Gtk::TextBuffer::iterator &end) { + std::string content_changes; + if (capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::NONE) + return; + if (capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::INCREMENTAL) + content_changes = + "{\"range\":{\"start\":{\"line\": " + std::to_string(start.get_line()) + ",\"character\":" + + std::to_string(start.get_line_offset()) + "},\"end\":{\"line\":" + + std::to_string(end.get_line()) + ",\"character\":" + std::to_string(end.get_line_offset()) + + "}},\"text\":\"\"}"; + else { + std::string text = get_buffer()->get_text(); + escape_text(text); + content_changes = "{\"text\":\"" + text + "\"}"; + } + client->write_notification("textDocument/didChange", + "\"textDocument\":{\"uri\":\"" + uri + "\",\"version\":" + + std::to_string(document_version++) + "},\"contentChanges\":[" + + content_changes + "]"); + }, false); } Source::LanguageProtocolView::~LanguageProtocolView() { - if(initialize_thread.joinable()) - initialize_thread.join(); - - autocomplete.state=Autocomplete::State::IDLE; - if(autocomplete.thread.joinable()) - autocomplete.thread.join(); - - client->write_notification("textDocument/didClose", "\"textDocument\":{\"uri\":\""+uri+"\"}"); - client->close(this); - - client=nullptr; + delayed_tag_similar_symbols_connection.disconnect(); + + if (initialize_thread.joinable()) + initialize_thread.join(); + + autocomplete.state = Autocomplete::State::IDLE; + if (autocomplete.thread.joinable()) + autocomplete.thread.join(); + + client->write_notification("textDocument/didClose", "\"textDocument\":{\"uri\":\"" + uri + "\"}"); + client->close(this); + + client = nullptr; } bool Source::LanguageProtocolView::save() { - if(!Source::View::save()) - return false; - - if(!flow_coverage_executable.empty()) - add_flow_coverage_tooltips(false); - - return true; + if (!Source::View::save()) + return false; + + if (!flow_coverage_executable.empty()) + add_flow_coverage_tooltips(false); + + return true; } void Source::LanguageProtocolView::setup_navigation_and_refactoring() { - if(capabilities.document_formatting) { - format_style=[this](bool continue_without_style_file) { - if(!continue_without_style_file) { - bool has_style_file=false; - auto style_file_search_path=this->file_path.parent_path(); - auto style_file='.'+language_id+"-format"; - - while(true) { - if(boost::filesystem::exists(style_file_search_path/style_file)) { - has_style_file=true; - break; - } - if(style_file_search_path==style_file_search_path.root_directory()) - break; - style_file_search_path=style_file_search_path.parent_path(); - } - - if(!has_style_file && !continue_without_style_file) - return; - } - - class Replace { - public: - Offset start, end; - std::string text; - }; - std::vector<Replace> replaces; - std::promise<void> result_processed; - - std::string method; - std::string params; - std::string options("\"tabSize\":"+std::to_string(tab_size)+",\"insertSpaces\":"+(tab_char==' '?"true":"false")); - if(get_buffer()->get_has_selection() && capabilities.document_range_formatting) { - method="textDocument/rangeFormatting"; - Gtk::TextIter start, end; - get_buffer()->get_selection_bounds(start, end); - params="\"textDocument\":{\"uri\":\""+uri+"\"},\"range\":{\"start\":{\"line\":"+std::to_string(start.get_line())+",\"character\":"+std::to_string(start.get_line_offset())+"},\"end\":{\"line\":"+std::to_string(end.get_line())+",\"character\":"+std::to_string(end.get_line_offset())+"}},\"options\":{"+options+"}"; - } - else { - method="textDocument/formatting"; - params="\"textDocument\":{\"uri\":\""+uri+"\"},\"options\":{"+options+"}"; - } - - client->write_request(this, method, params, [&replaces, &result_processed](const boost::property_tree::ptree &result, bool error) { - if(!error) { - for(auto it=result.begin();it!=result.end();++it) { - auto range_it=it->second.find("range"); - auto text_it=it->second.find("newText"); - if(range_it!=it->second.not_found() && text_it!=it->second.not_found()) { - auto start_it=range_it->second.find("start"); - auto end_it=range_it->second.find("end"); - if(start_it!=range_it->second.not_found() && end_it!=range_it->second.not_found()) { - try { - replaces.emplace_back(Replace{Offset(start_it->second.get<unsigned>("line"), start_it->second.get<unsigned>("character")), - Offset(end_it->second.get<unsigned>("line"), end_it->second.get<unsigned>("character")), - text_it->second.get_value<std::string>()}); - } - catch(...) {} - } - } - } - } - result_processed.set_value(); - }); - result_processed.get_future().get(); - - auto end_iter=get_buffer()->end(); - if(replaces.size()==1 && - replaces[0].start.line==0 && replaces[0].start.index==0 && - (replaces[0].end.line>static_cast<unsigned>(end_iter.get_line()) || - (replaces[0].end.line==static_cast<unsigned>(end_iter.get_line()) && replaces[0].end.index>=static_cast<unsigned>(end_iter.get_line_offset())))) { - unescape_text(replaces[0].text); - replace_text(replaces[0].text); - } - else { - get_buffer()->begin_user_action(); - for(auto it=replaces.rbegin();it!=replaces.rend();++it) { - auto start=get_iter_at_line_pos(it->start.line, it->start.index); - auto end=get_iter_at_line_pos(it->end.line, it->end.index); - get_buffer()->erase(start, end); - start=get_iter_at_line_pos(it->start.line, it->start.index); - unescape_text(it->text); - get_buffer()->insert(start, it->text); - } - get_buffer()->end_user_action(); - } - }; - } - - if(capabilities.definition) { - get_declaration_location=[this]() { - auto offset=get_declaration(get_buffer()->get_insert()->get_iter()); - if(!offset) - Info::get().print("No declaration found"); - return offset; - }; - get_declaration_or_implementation_locations=[this]() { - std::vector<Offset> offsets; - auto offset=get_declaration_location(); - if(offset) - offsets.emplace_back(std::move(offset)); - return offsets; - }; - } - - if(capabilities.references) { - get_usages=[this] { - auto iter=get_buffer()->get_insert()->get_iter(); - std::vector<std::pair<Offset, std::string>> usages; - std::vector<Offset> end_offsets; - std::promise<void> result_processed; - client->write_request(this, "textDocument/references", "\"textDocument\":{\"uri\":\""+uri+"\"}, \"position\": {\"line\": "+std::to_string(iter.get_line())+", \"character\": "+std::to_string(iter.get_line_offset())+"}, \"context\": {\"includeDeclaration\": true}", [&usages, &end_offsets, &result_processed](const boost::property_tree::ptree &result, bool error) { - if(!error) { - try { - for(auto it=result.begin();it!=result.end();++it) { - auto path=it->second.get<std::string>("uri", ""); - if(path.size()>=7) { - path.erase(0, 7); - auto range_it=it->second.find("range"); - if(range_it!=it->second.not_found()) { - auto start_it=range_it->second.find("start"); - auto end_it=range_it->second.find("end"); - if(start_it!=range_it->second.not_found() && end_it!=range_it->second.not_found()) { - usages.emplace_back(std::make_pair(Offset(start_it->second.get<unsigned>("line"), start_it->second.get<unsigned>("character"), path), "")); - end_offsets.emplace_back(end_it->second.get<unsigned>("line"), end_it->second.get<unsigned>("character")); - } + if (capabilities.document_formatting) { + format_style = [this](bool continue_without_style_file) { + if (!continue_without_style_file) { + bool has_style_file = false; + auto style_file_search_path = this->file_path.parent_path(); + auto style_file = '.' + language_id + "-format"; + + while (true) { + if (boost::filesystem::exists(style_file_search_path / style_file)) { + has_style_file = true; + break; + } + if (style_file_search_path == style_file_search_path.root_directory()) + break; + style_file_search_path = style_file_search_path.parent_path(); } - } + + if (!has_style_file && !continue_without_style_file) + return; } - } - catch(...) { - usages.clear(); - end_offsets.clear(); - } - } - result_processed.set_value(); - }); - result_processed.get_future().get(); - - auto embolden_token=[](std::string &line_, unsigned token_start_pos, unsigned token_end_pos) { - Glib::ustring line=line_; - if(token_start_pos>=line.size() || token_end_pos>=line.size()) - return; - - //markup token as bold - size_t pos=0; - while((pos=line.find('&', pos))!=Glib::ustring::npos) { - size_t pos2=line.find(';', pos+2); - if(token_start_pos>pos) { - token_start_pos+=pos2-pos; - token_end_pos+=pos2-pos; - } - else if(token_end_pos>pos) - token_end_pos+=pos2-pos; - else - break; - pos=pos2+1; - } - line.insert(token_end_pos, "</b>"); - line.insert(token_start_pos, "<b>"); - - size_t start_pos=0; - while(start_pos<line.size() && (line[start_pos]==' ' || line[start_pos]=='\t')) - ++start_pos; - if(start_pos>0) - line.erase(0, start_pos); - - line_=line.raw(); - }; - - std::map<boost::filesystem::path, std::vector<std::string>> file_lines; - size_t c=static_cast<size_t>(-1); - for(auto &usage: usages) { - ++c; - auto view_it=views.end(); - for(auto it=views.begin();it!=views.end();++it) { - if(usage.first.file_path==(*it)->file_path) { - view_it=it; - break; - } - } - if(view_it!=views.end()) { - if(usage.first.line<static_cast<unsigned>((*view_it)->get_buffer()->get_line_count())) { - auto start=(*view_it)->get_buffer()->get_iter_at_line(usage.first.line); - auto end=start; - end.forward_to_line_end(); - usage.second=Glib::Markup::escape_text((*view_it)->get_buffer()->get_text(start, end)); - embolden_token(usage.second, usage.first.index, end_offsets[c].index); - } - } - else { - auto it=file_lines.find(usage.first.file_path); - if(it==file_lines.end()) { - std::ifstream ifs(usage.first.file_path.string()); - if(ifs) { - std::vector<std::string> lines; - std::string line; - while(std::getline(ifs, line)) { - if(!line.empty() && line.back()=='\r') - line.pop_back(); - lines.emplace_back(line); - } - auto pair=file_lines.emplace(usage.first.file_path, lines); - it=pair.first; + + class Replace { + public: + Offset start, end; + std::string text; + }; + std::vector<Replace> replaces; + std::promise<void> result_processed; + + std::string method; + std::string params; + std::string options("\"tabSize\":" + std::to_string(tab_size) + ",\"insertSpaces\":" + + (tab_char == ' ' ? "true" : "false")); + if (get_buffer()->get_has_selection() && capabilities.document_range_formatting) { + method = "textDocument/rangeFormatting"; + Gtk::TextIter start, end; + get_buffer()->get_selection_bounds(start, end); + params = "\"textDocument\":{\"uri\":\"" + uri + "\"},\"range\":{\"start\":{\"line\":" + + std::to_string(start.get_line()) + ",\"character\":" + + std::to_string(start.get_line_offset()) + "},\"end\":{\"line\":" + + std::to_string(end.get_line()) + ",\"character\":" + std::to_string(end.get_line_offset()) + + "}},\"options\":{" + options + "}"; + } else { + method = "textDocument/formatting"; + params = "\"textDocument\":{\"uri\":\"" + uri + "\"},\"options\":{" + options + "}"; } - else { - auto pair=file_lines.emplace(usage.first.file_path, std::vector<std::string>()); - it=pair.first; + + client->write_request(this, method, params, + [&replaces, &result_processed](const boost::property_tree::ptree &result, + bool error) { + if (!error) { + for (auto it = result.begin(); it != result.end(); ++it) { + auto range_it = it->second.find("range"); + auto text_it = it->second.find("newText"); + if (range_it != it->second.not_found() && + text_it != it->second.not_found()) { + auto start_it = range_it->second.find("start"); + auto end_it = range_it->second.find("end"); + if (start_it != range_it->second.not_found() && + end_it != range_it->second.not_found()) { + try { + replaces.emplace_back( + Replace{Offset(start_it->second.get<unsigned>("line"), + start_it->second.get<unsigned>( + "character")), + Offset(end_it->second.get<unsigned>("line"), + end_it->second.get<unsigned>( + "character")), + text_it->second.get_value<std::string>()}); + } + catch (...) {} + } + } + } + } + result_processed.set_value(); + }); + result_processed.get_future().get(); + + auto end_iter = get_buffer()->end(); + if (replaces.size() == 1 && + replaces[0].start.line == 0 && replaces[0].start.index == 0 && + (replaces[0].end.line > static_cast<unsigned>(end_iter.get_line()) || + (replaces[0].end.line == static_cast<unsigned>(end_iter.get_line()) && + replaces[0].end.index >= static_cast<unsigned>(end_iter.get_line_offset())))) { + unescape_text(replaces[0].text); + replace_text(replaces[0].text); + } else { + get_buffer()->begin_user_action(); + for (auto it = replaces.rbegin(); it != replaces.rend(); ++it) { + auto start = get_iter_at_line_pos(it->start.line, it->start.index); + auto end = get_iter_at_line_pos(it->end.line, it->end.index); + get_buffer()->erase(start, end); + start = get_iter_at_line_pos(it->start.line, it->start.index); + unescape_text(it->text); + get_buffer()->insert(start, it->text); + } + get_buffer()->end_user_action(); } - } - - if(usage.first.line<it->second.size()) { - usage.second=it->second[usage.first.line]; - embolden_token(usage.second, usage.first.index, end_offsets[c].index); - } - } - } - - if(usages.empty()) - Info::get().print("No symbol found at current cursor location"); - - return usages; - }; - } - - get_token_spelling=[this]() -> std::string { - auto start=get_buffer()->get_insert()->get_iter(); - auto end=start; - auto previous=start; - while(previous.backward_char() && ((*previous>='A' && *previous<='Z') || (*previous>='a' && *previous<='z') || (*previous>='0' && *previous<='9') || *previous=='_') && start.backward_char()) {} - while(((*end>='A' && *end<='Z') || (*end>='a' && *end<='z') || (*end>='0' && *end<='9') || *end=='_') && end.forward_char()) {} - - if(start==end) { - Info::get().print("No valid symbol found at current cursor location"); - return ""; + hide_tooltips(); + }; } - - return get_buffer()->get_text(start, end); - }; - - if(capabilities.rename) { - rename_similar_tokens=[this](const std::string &text) { - class Usages { - public: - boost::filesystem::path path; - std::unique_ptr<std::string> new_text; - std::vector<std::pair<Offset, Offset>> offsets; - }; - - auto previous_text=get_token_spelling(); - if(previous_text.empty()) - return; - - auto iter=get_buffer()->get_insert()->get_iter(); - std::vector<Usages> usages; - std::promise<void> result_processed; - client->write_request(this, "textDocument/rename", "\"textDocument\":{\"uri\":\""+uri+"\"}, \"position\": {\"line\": "+std::to_string(iter.get_line())+", \"character\": "+std::to_string(iter.get_line_offset())+"}, \"newName\": \""+text+"\"", [this, &usages, &result_processed](const boost::property_tree::ptree &result, bool error) { - if(!error) { - boost::filesystem::path project_path; - auto build=Project::Build::create(file_path); - if(!build->project_path.empty()) - project_path=build->project_path; - else - project_path=file_path.parent_path(); - try { - auto changes_it=result.find("changes"); - if(changes_it!=result.not_found()) { - for(auto file_it=changes_it->second.begin();file_it!=changes_it->second.end();++file_it) { - auto path=file_it->first; - if(path.size()>=7) { - path.erase(0, 7); - if(filesystem::file_in_path(path, project_path)) { - usages.emplace_back(Usages{path, nullptr, std::vector<std::pair<Offset, Offset>>()}); - for(auto edit_it=file_it->second.begin();edit_it!=file_it->second.end();++edit_it) { - auto range_it=edit_it->second.find("range"); - if(range_it!=edit_it->second.not_found()) { - auto start_it=range_it->second.find("start"); - auto end_it=range_it->second.find("end"); - if(start_it!=range_it->second.not_found() && end_it!=range_it->second.not_found()) - usages.back().offsets.emplace_back(std::make_pair(Offset(start_it->second.get<unsigned>("line"), start_it->second.get<unsigned>("character")), - Offset(end_it->second.get<unsigned>("line"), end_it->second.get<unsigned>("character")))); - } + + if (capabilities.definition) { + get_declaration_location = [this]() { + auto offset = get_declaration(get_buffer()->get_insert()->get_iter()); + if (!offset) + Info::get().print("No declaration found"); + return offset; + }; + get_declaration_or_implementation_locations = [this]() { + std::vector<Offset> offsets; + auto offset = get_declaration_location(); + if (offset) + offsets.emplace_back(std::move(offset)); + return offsets; + }; + } + + if (capabilities.references) { + get_usages = [this] { + auto iter = get_buffer()->get_insert()->get_iter(); + std::vector<std::pair<Offset, std::string>> usages; + std::vector<Offset> end_offsets; + std::promise<void> result_processed; + client->write_request(this, "textDocument/references", + "\"textDocument\":{\"uri\":\"" + uri + "\"}, \"position\": {\"line\": " + + std::to_string(iter.get_line()) + ", \"character\": " + + std::to_string(iter.get_line_offset()) + + "}, \"context\": {\"includeDeclaration\": true}", + [&usages, &end_offsets, &result_processed](const boost::property_tree::ptree &result, + bool error) { + if (!error) { + try { + for (auto it = result.begin(); it != result.end(); ++it) { + auto path = it->second.get<std::string>("uri", ""); + if (path.size() >= 7) { + path.erase(0, 7); + auto range_it = it->second.find("range"); + if (range_it != it->second.not_found()) { + auto start_it = range_it->second.find("start"); + auto end_it = range_it->second.find("end"); + if (start_it != range_it->second.not_found() && + end_it != range_it->second.not_found()) { + usages.emplace_back(std::make_pair( + Offset(start_it->second.get<unsigned>("line"), + start_it->second.get<unsigned>( + "character"), path), "")); + end_offsets.emplace_back( + end_it->second.get<unsigned>("line"), + end_it->second.get<unsigned>("character")); + } + } + } + } + } + catch (...) { + usages.clear(); + end_offsets.clear(); + } + } + result_processed.set_value(); + }); + result_processed.get_future().get(); + + auto embolden_token = [](std::string &line_, unsigned token_start_pos, unsigned token_end_pos) { + Glib::ustring line = line_; + if (token_start_pos >= line.size() || token_end_pos >= line.size()) + return; + + //markup token as bold + size_t pos = 0; + while ((pos = line.find('&', pos)) != Glib::ustring::npos) { + size_t pos2 = line.find(';', pos + 2); + if (token_start_pos > pos) { + token_start_pos += pos2 - pos; + token_end_pos += pos2 - pos; + } else if (token_end_pos > pos) + token_end_pos += pos2 - pos; + else + break; + pos = pos2 + 1; + } + line.insert(token_end_pos, "</b>"); + line.insert(token_start_pos, "<b>"); + + size_t start_pos = 0; + while (start_pos < line.size() && (line[start_pos] == ' ' || line[start_pos] == '\t')) + ++start_pos; + if (start_pos > 0) + line.erase(0, start_pos); + + line_ = line.raw(); + }; + + std::map<boost::filesystem::path, std::vector<std::string>> file_lines; + size_t c = static_cast<size_t>(-1); + for (auto &usage: usages) { + ++c; + auto view_it = views.end(); + for (auto it = views.begin(); it != views.end(); ++it) { + if (usage.first.file_path == (*it)->file_path) { + view_it = it; + break; } - } } - } - } - else { - auto changes_pt=result.get_child("documentChanges", boost::property_tree::ptree()); - for(auto change_it=changes_pt.begin();change_it!=changes_pt.end();++change_it) { - auto document_it=change_it->second.find("textDocument"); - if(document_it!=change_it->second.not_found()) { - auto path=document_it->second.get<std::string>("uri", ""); - if(path.size()>=7) { - path.erase(0, 7); - if(filesystem::file_in_path(path, project_path)) { - usages.emplace_back(Usages{path, std::make_unique<std::string>(), std::vector<std::pair<Offset, Offset>>()}); - auto edits_pt=change_it->second.get_child("edits", boost::property_tree::ptree()); - for(auto edit_it=edits_pt.begin();edit_it!=edits_pt.end();++edit_it) { - auto new_text_it=edit_it->second.find("newText"); - if(new_text_it!=edit_it->second.not_found()) { - auto range_it=edit_it->second.find("range"); - if(range_it!=edit_it->second.not_found()) { - auto start_it=range_it->second.find("start"); - auto end_it=range_it->second.find("end"); - if(start_it!=range_it->second.not_found() && end_it!=range_it->second.not_found()) { - auto end_line=end_it->second.get<size_t>("line"); - if(end_line>std::numeric_limits<int>::max()) - end_line=std::numeric_limits<int>::max(); - *usages.back().new_text=new_text_it->second.get_value<std::string>(); - usages.back().offsets.emplace_back(std::make_pair(Offset(start_it->second.get<unsigned>("line"), start_it->second.get<unsigned>("character")), - Offset(static_cast<unsigned>(end_line), end_it->second.get<unsigned>("character")))); + if (view_it != views.end()) { + if (usage.first.line < static_cast<unsigned>((*view_it)->get_buffer()->get_line_count())) { + auto start = (*view_it)->get_buffer()->get_iter_at_line(usage.first.line); + auto end = start; + end.forward_to_line_end(); + usage.second = Glib::Markup::escape_text((*view_it)->get_buffer()->get_text(start, end)); + embolden_token(usage.second, usage.first.index, end_offsets[c].index); + } + } else { + auto it = file_lines.find(usage.first.file_path); + if (it == file_lines.end()) { + std::ifstream ifs(usage.first.file_path.string()); + if (ifs) { + std::vector<std::string> lines; + std::string line; + while (std::getline(ifs, line)) { + if (!line.empty() && line.back() == '\r') + line.pop_back(); + lines.emplace_back(line); } - } + auto pair = file_lines.emplace(usage.first.file_path, lines); + it = pair.first; + } else { + auto pair = file_lines.emplace(usage.first.file_path, std::vector<std::string>()); + it = pair.first; } - } } - } + + if (usage.first.line < it->second.size()) { + usage.second = it->second[usage.first.line]; + embolden_token(usage.second, usage.first.index, end_offsets[c].index); + } } - } } - } - catch(...) { - usages.clear(); - } - } - result_processed.set_value(); - }); - result_processed.get_future().get(); - - std::vector<Usages*> usages_renamed; - for(auto &usage: usages) { - auto view_it=views.end(); - for(auto it=views.begin();it!=views.end();++it) { - if((*it)->file_path==usage.path) { - view_it=it; - break; - } - } - if(view_it!=views.end()) { - (*view_it)->get_buffer()->begin_user_action(); - auto iter=get_buffer()->get_insert()->get_iter(); - auto line=iter.get_line(); - auto offset=iter.get_line_offset(); - for(auto offset_it=usage.offsets.rbegin();offset_it!=usage.offsets.rend();++offset_it) { - auto start_iter=(*view_it)->get_iter_at_line_pos(offset_it->first.line, offset_it->first.index); - auto end_iter=(*view_it)->get_iter_at_line_pos(offset_it->second.line, offset_it->second.index); - (*view_it)->get_buffer()->erase(start_iter, end_iter); - start_iter=(*view_it)->get_iter_at_line_pos(offset_it->first.line, offset_it->first.index); - if(usage.new_text) - (*view_it)->get_buffer()->insert(start_iter, *usage.new_text); - else - (*view_it)->get_buffer()->insert(start_iter, text); - } - if(usage.new_text && get_buffer()->get_insert()->get_iter().is_end()) { - place_cursor_at_line_offset(line, offset); - hide_tooltips(); - scroll_to_cursor_delayed(this, true, false); - } - (*view_it)->get_buffer()->end_user_action(); - (*view_it)->save(); - usages_renamed.emplace_back(&usage); + + if (usages.empty()) + Info::get().print("No symbol found at current cursor location"); + + return usages; + }; + } + + get_token_spelling = [this]() -> std::string { + auto start = get_buffer()->get_insert()->get_iter(); + auto end = start; + auto previous = start; + while (previous.backward_char() && + ((*previous >= 'A' && *previous <= 'Z') || (*previous >= 'a' && *previous <= 'z') || + (*previous >= '0' && *previous <= '9') || *previous == '_') && start.backward_char()) {} + while (((*end >= 'A' && *end <= 'Z') || (*end >= 'a' && *end <= 'z') || (*end >= '0' && *end <= '9') || + *end == '_') && end.forward_char()) {} + + if (start == end) { + Info::get().print("No valid symbol found at current cursor location"); + return ""; } - else { - Glib::ustring buffer; - { - std::ifstream stream(usage.path.string(), std::ifstream::binary); - if(stream) - buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>()); - } - std::ofstream stream(usage.path.string(), std::ifstream::binary); - if(!buffer.empty() && stream) { - std::vector<size_t> lines_start_pos={0}; - for(size_t c=0;c<buffer.size();++c) { - if(buffer[c]=='\n') - lines_start_pos.emplace_back(c+1); - } - for(auto offset_it=usage.offsets.rbegin();offset_it!=usage.offsets.rend();++offset_it) { - auto start_line=offset_it->first.line; - auto end_line=offset_it->second.line; - if(start_line<lines_start_pos.size()) { - auto start=lines_start_pos[start_line]+offset_it->first.index; - unsigned end; - if(end_line>=lines_start_pos.size()) - end=buffer.size(); - else - end=lines_start_pos[end_line]+offset_it->second.index; - if(start<buffer.size() && end<=buffer.size()) { - if(usage.new_text) - buffer.replace(start, end-start, *usage.new_text); - else - buffer.replace(start, end-start, text); + + return get_buffer()->get_text(start, end); + }; + + if (capabilities.rename) { + rename_similar_tokens = [this](const std::string &text) { + class Usages { + public: + boost::filesystem::path path; + std::unique_ptr<std::string> new_text; + std::vector<std::pair<Offset, Offset>> offsets; + }; + + auto previous_text = get_token_spelling(); + if (previous_text.empty()) + return; + + auto iter = get_buffer()->get_insert()->get_iter(); + std::vector<Usages> usages; + std::promise<void> result_processed; + client->write_request(this, "textDocument/rename", + "\"textDocument\":{\"uri\":\"" + uri + "\"}, \"position\": {\"line\": " + + std::to_string(iter.get_line()) + ", \"character\": " + + std::to_string(iter.get_line_offset()) + "}, \"newName\": \"" + text + "\"", + [this, &usages, &result_processed](const boost::property_tree::ptree &result, + bool error) { + if (!error) { + boost::filesystem::path project_path; + auto build = Project::Build::create(file_path); + if (!build->project_path.empty()) + project_path = build->project_path; + else + project_path = file_path.parent_path(); + try { + auto changes_it = result.find("changes"); + if (changes_it != result.not_found()) { + for (auto file_it = changes_it->second.begin(); + file_it != changes_it->second.end(); ++file_it) { + auto path = file_it->first; + if (path.size() >= 7) { + path.erase(0, 7); + if (filesystem::file_in_path(path, project_path)) { + usages.emplace_back(Usages{path, nullptr, + std::vector<std::pair<Offset, Offset>>()}); + for (auto edit_it = file_it->second.begin(); + edit_it != file_it->second.end(); ++edit_it) { + auto range_it = edit_it->second.find("range"); + if (range_it != edit_it->second.not_found()) { + auto start_it = range_it->second.find("start"); + auto end_it = range_it->second.find("end"); + if (start_it != range_it->second.not_found() && + end_it != range_it->second.not_found()) + usages.back().offsets.emplace_back( + std::make_pair( + Offset(start_it->second.get<unsigned>( + "line"), + start_it->second.get<unsigned>( + "character")), + Offset(end_it->second.get<unsigned>( + "line"), + end_it->second.get<unsigned>( + "character")))); + } + } + } + } + } + } else { + auto changes_pt = result.get_child("documentChanges", + boost::property_tree::ptree()); + for (auto change_it = changes_pt.begin(); + change_it != changes_pt.end(); ++change_it) { + auto document_it = change_it->second.find("textDocument"); + if (document_it != change_it->second.not_found()) { + auto path = document_it->second.get<std::string>("uri", ""); + if (path.size() >= 7) { + path.erase(0, 7); + if (filesystem::file_in_path(path, project_path)) { + usages.emplace_back( + Usages{path, std::make_unique<std::string>(), + std::vector<std::pair<Offset, Offset>>()}); + auto edits_pt = change_it->second.get_child("edits", + boost::property_tree::ptree()); + for (auto edit_it = edits_pt.begin(); + edit_it != edits_pt.end(); ++edit_it) { + auto new_text_it = edit_it->second.find( + "newText"); + if (new_text_it != edit_it->second.not_found()) { + auto range_it = edit_it->second.find("range"); + if (range_it != edit_it->second.not_found()) { + auto start_it = range_it->second.find( + "start"); + auto end_it = range_it->second.find( + "end"); + if (start_it != + range_it->second.not_found() && + end_it != + range_it->second.not_found()) { + auto end_line = end_it->second.get<size_t>( + "line"); + if (end_line > + std::numeric_limits<int>::max()) + end_line = std::numeric_limits<int>::max(); + *usages.back().new_text = new_text_it->second.get_value<std::string>(); + usages.back().offsets.emplace_back( + std::make_pair( + Offset(start_it->second.get<unsigned>( + "line"), + start_it->second.get<unsigned>( + "character")), + Offset(static_cast<unsigned>(end_line), + end_it->second.get<unsigned>( + "character")))); + } + } + } + } + } + } + } + } + } + } + catch (...) { + usages.clear(); + } + } + result_processed.set_value(); + }); + result_processed.get_future().get(); + + std::vector<Usages *> usages_renamed; + for (auto &usage: usages) { + auto view_it = views.end(); + for (auto it = views.begin(); it != views.end(); ++it) { + if ((*it)->file_path == usage.path) { + view_it = it; + break; + } + } + if (view_it != views.end()) { + (*view_it)->get_buffer()->begin_user_action(); + auto iter = get_buffer()->get_insert()->get_iter(); + auto line = iter.get_line(); + auto offset = iter.get_line_offset(); + for (auto offset_it = usage.offsets.rbegin(); offset_it != usage.offsets.rend(); ++offset_it) { + auto start_iter = (*view_it)->get_iter_at_line_pos(offset_it->first.line, + offset_it->first.index); + auto end_iter = (*view_it)->get_iter_at_line_pos(offset_it->second.line, + offset_it->second.index); + (*view_it)->get_buffer()->erase(start_iter, end_iter); + start_iter = (*view_it)->get_iter_at_line_pos(offset_it->first.line, offset_it->first.index); + if (usage.new_text) + (*view_it)->get_buffer()->insert(start_iter, *usage.new_text); + else + (*view_it)->get_buffer()->insert(start_iter, text); + } + if (usage.new_text && get_buffer()->get_insert()->get_iter().is_end()) { + place_cursor_at_line_offset(line, offset); + hide_tooltips(); + scroll_to_cursor_delayed(this, true, false); + } + (*view_it)->get_buffer()->end_user_action(); + (*view_it)->save(); + usages_renamed.emplace_back(&usage); + } else { + Glib::ustring buffer; + { + std::ifstream stream(usage.path.string(), std::ifstream::binary); + if (stream) + buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>()); + } + std::ofstream stream(usage.path.string(), std::ifstream::binary); + if (!buffer.empty() && stream) { + std::vector<size_t> lines_start_pos = {0}; + for (size_t c = 0; c < buffer.size(); ++c) { + if (buffer[c] == '\n') + lines_start_pos.emplace_back(c + 1); + } + for (auto offset_it = usage.offsets.rbegin(); offset_it != usage.offsets.rend(); ++offset_it) { + auto start_line = offset_it->first.line; + auto end_line = offset_it->second.line; + if (start_line < lines_start_pos.size()) { + auto start = lines_start_pos[start_line] + offset_it->first.index; + unsigned end; + if (end_line >= lines_start_pos.size()) + end = buffer.size(); + else + end = lines_start_pos[end_line] + offset_it->second.index; + if (start < buffer.size() && end <= buffer.size()) { + if (usage.new_text) + buffer.replace(start, end - start, *usage.new_text); + else + buffer.replace(start, end - start, text); + } + } + } + stream.write(buffer.data(), buffer.bytes()); + usages_renamed.emplace_back(&usage); + } else + Terminal::get().print("Error: could not write to file " + usage.path.string() + '\n', true); } - } } - stream.write(buffer.data(), buffer.bytes()); - usages_renamed.emplace_back(&usage); - } - else - Terminal::get().print("Error: could not write to file "+usage.path.string()+'\n', true); - } - } - - if(!usages_renamed.empty()) { - Terminal::get().print("Renamed "); - Terminal::get().print(previous_text, true); - Terminal::get().print(" to "); - Terminal::get().print(text, true); - Terminal::get().print("\n"); - } + + if (!usages_renamed.empty()) { + Terminal::get().print("Renamed "); + Terminal::get().print(previous_text, true); + Terminal::get().print(" to "); + Terminal::get().print(text, true); + Terminal::get().print("\n"); + } + }; + } + + goto_next_diagnostic = [this]() { + place_cursor_at_next_diagnostic(); }; - } - - goto_next_diagnostic=[this]() { - place_cursor_at_next_diagnostic(); - }; } void Source::LanguageProtocolView::escape_text(std::string &text) { - for(size_t c=0;c<text.size();++c) { - if(text[c]=='\n') { - text.replace(c, 1, "\\n"); - ++c; - } - else if(text[c]=='\r') { - text.replace(c, 1, "\\r"); - ++c; - } - else if(text[c]=='\"') { - text.replace(c, 1, "\\\""); - ++c; + for (size_t c = 0; c < text.size(); ++c) { + if (text[c] == '\n') { + text.replace(c, 1, "\\n"); + ++c; + } else if (text[c] == '\r') { + text.replace(c, 1, "\\r"); + ++c; + } else if (text[c] == '\"') { + text.replace(c, 1, "\\\""); + ++c; + } } - } } void Source::LanguageProtocolView::unescape_text(std::string &text) { - for(size_t c=0;c<text.size();++c) { - if(text[c]=='\\' && c+1<text.size()) { - if(text[c+1]=='n') - text.replace(c, 2, "\n"); - else if(text[c+1]=='r') - text.replace(c, 2, "\r"); - else - text.erase(c, 1); + for (size_t c = 0; c < text.size(); ++c) { + if (text[c] == '\\' && c + 1 < text.size()) { + if (text[c + 1] == 'n') + text.replace(c, 2, "\n"); + else if (text[c + 1] == 'r') + text.replace(c, 2, "\r"); + else + text.erase(c, 1); + } } - } } void Source::LanguageProtocolView::update_diagnostics(std::vector<LanguageProtocol::Diagnostic> &&diagnostics) { - dispatcher.post([this, diagnostics=std::move(diagnostics)] { - clear_diagnostic_tooltips(); - num_warnings=0; - num_errors=0; - num_fix_its=0; - for(auto &diagnostic: diagnostics) { - if(diagnostic.uri==uri) { - auto start=get_iter_at_line_pos(diagnostic.offsets.first.line, diagnostic.offsets.first.index); - auto end=get_iter_at_line_pos(diagnostic.offsets.second.line, diagnostic.offsets.second.index); - - if(start==end) { - if(!end.is_end()) - end.forward_char(); - else - start.forward_char(); - } - - bool error=false; - std::string severity_tag_name; - if(diagnostic.severity>=2) { - severity_tag_name="def:warning"; - num_warnings++; - } - else { - severity_tag_name="def:error"; - num_errors++; - error=true; + dispatcher.post([this, diagnostics = std::move(diagnostics)] { + clear_diagnostic_tooltips(); + num_warnings = 0; + num_errors = 0; + num_fix_its = 0; + for (auto &diagnostic: diagnostics) { + if (diagnostic.uri == uri) { + auto start = get_iter_at_line_pos(diagnostic.offsets.first.line, diagnostic.offsets.first.index); + auto end = get_iter_at_line_pos(diagnostic.offsets.second.line, diagnostic.offsets.second.index); + + if (start == end) { + if (!end.is_end()) + end.forward_char(); + else + start.forward_char(); + } + + bool error = false; + std::string severity_tag_name; + if (diagnostic.severity >= 2) { + severity_tag_name = "def:warning"; + num_warnings++; + } else { + severity_tag_name = "def:error"; + num_errors++; + error = true; + } + + add_diagnostic_tooltip(start, end, diagnostic.spelling, error); + } } - - add_diagnostic_tooltip(start, end, diagnostic.spelling, error); - } - } - - for(auto &mark: flow_coverage_marks) - add_diagnostic_tooltip(mark.first->get_iter(), mark.second->get_iter(), flow_coverage_message, false); - - status_diagnostics=std::make_tuple(num_warnings+num_flow_coverage_warnings, num_errors, num_fix_its); - if(update_status_diagnostics) - update_status_diagnostics(this); - }); + + for (auto &mark: flow_coverage_marks) + add_diagnostic_tooltip(mark.first->get_iter(), mark.second->get_iter(), flow_coverage_message, false); + + status_diagnostics = std::make_tuple(num_warnings + num_flow_coverage_warnings, num_errors, num_fix_its); + if (update_status_diagnostics) + update_status_diagnostics(this); + }); } Gtk::TextIter Source::LanguageProtocolView::get_iter_at_line_pos(int line, int pos) { - return get_iter_at_line_offset(line, pos); + return get_iter_at_line_offset(line, pos); } void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rectangle) { - if(!capabilities.hover) - return; - - Gtk::TextIter iter; - int location_x, location_y; - window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, rectangle.get_x(), rectangle.get_y(), location_x, location_y); - location_x += (rectangle.get_width() - 1) / 2; - get_iter_at_location(iter, location_x, location_y); - Gdk::Rectangle iter_rectangle; - get_iter_location(iter, iter_rectangle); - if(iter.ends_line() && location_x > iter_rectangle.get_x()) - return; - - auto offset=iter.get_offset(); - - static int request_count=0; - request_count++; - auto current_request=request_count; - client->write_request(this, "textDocument/hover", "\"textDocument\": {\"uri\":\"file://"+file_path.string()+"\"}, \"position\": {\"line\": "+std::to_string(iter.get_line())+", \"character\": "+std::to_string(iter.get_line_offset())+"}", [this, offset, current_request](const boost::property_tree::ptree &result, bool error) { - if(!error) { - // hover result structure vary significantly from the different language servers - std::string content; - auto contents_pt=result.get_child("contents", boost::property_tree::ptree()); - for(auto it=contents_pt.begin();it!=contents_pt.end();++it) { - auto value=it->second.get<std::string>("value", ""); - if(!value.empty()) - content.insert(0, value+(content.empty()?"":"\n\n")); - else { - value=it->second.get_value<std::string>(""); - if(!value.empty()) - content+=(content.empty()?"":"\n\n")+value; - } - } - if(content.empty()) { - auto contents_it=result.find("contents"); - if(contents_it!=result.not_found()) { - content=contents_it->second.get<std::string>("value", ""); - if(content.empty()) - content=contents_it->second.get_value<std::string>(""); - } - } - if(!content.empty()) { - while(!content.empty() && content.back()=='\n') { content.pop_back(); } // Remove unnecessary newlines - dispatcher.post([this, offset, content=std::move(content), current_request] { - if(current_request!=request_count) - return; - if(offset>=get_buffer()->get_char_count()) - return; - type_tooltips.clear(); - auto create_tooltip_buffer=[this, offset, content=std::move(content)]() { - auto tooltip_buffer=Gtk::TextBuffer::create(get_buffer()->get_tag_table()); - tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), content); - + if (!capabilities.hover) + return; + + Gtk::TextIter iter; + int location_x, location_y; + window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, rectangle.get_x(), rectangle.get_y(), location_x, + location_y); + location_x += (rectangle.get_width() - 1) / 2; + get_iter_at_location(iter, location_x, location_y); + Gdk::Rectangle iter_rectangle; + get_iter_location(iter, iter_rectangle); + if (iter.ends_line() && location_x > iter_rectangle.get_x()) + return; + + auto offset = iter.get_offset(); + + static int request_count = 0; + request_count++; + auto current_request = request_count; + client->write_request(this, "textDocument/hover", "\"textDocument\": {\"uri\":\"file://" + file_path.string() + + "\"}, \"position\": {\"line\": " + + std::to_string(iter.get_line()) + ", \"character\": " + + std::to_string(iter.get_line_offset()) + "}", + [this, offset, current_request](const boost::property_tree::ptree &result, bool error) { + if (!error) { + // hover result structure vary significantly from the different language servers + std::string content; + auto contents_pt = result.get_child("contents", boost::property_tree::ptree()); + for (auto it = contents_pt.begin(); it != contents_pt.end(); ++it) { + auto value = it->second.get<std::string>("value", ""); + if (!value.empty()) + content.insert(0, value + (content.empty() ? "" : "\n\n")); + else { + value = it->second.get_value<std::string>(""); + if (!value.empty()) + content += (content.empty() ? "" : "\n\n") + value; + } + } + if (content.empty()) { + auto contents_it = result.find("contents"); + if (contents_it != result.not_found()) { + content = contents_it->second.get<std::string>("value", ""); + if (content.empty()) + content = contents_it->second.get_value<std::string>(""); + } + } + if (!content.empty()) { + dispatcher.post([this, offset, content = std::move(content), current_request] { + if (current_request != request_count) + return; + if (offset >= get_buffer()->get_char_count()) + return; + type_tooltips.clear(); + auto create_tooltip_buffer = [this, offset, content = std::move(content)]() { + auto tooltip_buffer = Gtk::TextBuffer::create( + get_buffer()->get_tag_table()); + tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), content); + #ifdef JUCI_ENABLE_DEBUG - if(language_id=="rust" && capabilities.definition) { - if(Debug::LLDB::get().is_stopped()) { - Glib::ustring value_type="Value"; - - auto start=get_buffer()->get_iter_at_offset(offset); - auto end=start; - auto previous=start; - while(previous.backward_char() && ((*previous>='A' && *previous<='Z') || (*previous>='a' && *previous<='z') || (*previous>='0' && *previous<='9') || *previous=='_') && start.backward_char()) {} - while(((*end>='A' && *end<='Z') || (*end>='a' && *end<='z') || (*end>='0' && *end<='9') || *end=='_') && end.forward_char()) {} - - auto offset=get_declaration(start); - Glib::ustring debug_value=Debug::LLDB::get().get_value(get_buffer()->get_text(start, end), offset.file_path, offset.line+1, offset.index+1); - if(debug_value.empty()) { - value_type="Return value"; - debug_value=Debug::LLDB::get().get_return_value(file_path, start.get_line()+1, start.get_line_index()+1); - } - if(!debug_value.empty()) { - size_t pos=debug_value.find(" = "); - if(pos!=Glib::ustring::npos) { - Glib::ustring::iterator iter; - while(!debug_value.validate(iter)) { - auto next_char_iter=iter; - next_char_iter++; - debug_value.replace(iter, next_char_iter, "?"); - } - tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), "\n\n"+value_type+": "+debug_value.substr(pos+3, debug_value.size()-(pos+3)-1)); - } - } - } - } + if(language_id=="rust" && capabilities.definition) { + if(Debug::LLDB::get().is_stopped()) { + Glib::ustring value_type="Value"; + + auto start=get_buffer()->get_iter_at_offset(offset); + auto end=start; + auto previous=start; + while(previous.backward_char() && ((*previous>='A' && *previous<='Z') || (*previous>='a' && *previous<='z') || (*previous>='0' && *previous<='9') || *previous=='_') && start.backward_char()) {} + while(((*end>='A' && *end<='Z') || (*end>='a' && *end<='z') || (*end>='0' && *end<='9') || *end=='_') && end.forward_char()) {} + + auto offset=get_declaration(start); + Glib::ustring debug_value=Debug::LLDB::get().get_value(get_buffer()->get_text(start, end), offset.file_path, offset.line+1, offset.index+1); + if(debug_value.empty()) { + value_type="Return value"; + debug_value=Debug::LLDB::get().get_return_value(file_path, start.get_line()+1, start.get_line_index()+1); + } + if(!debug_value.empty()) { + size_t pos=debug_value.find(" = "); + if(pos!=Glib::ustring::npos) { + Glib::ustring::iterator iter; + while(!debug_value.validate(iter)) { + auto next_char_iter=iter; + next_char_iter++; + debug_value.replace(iter, next_char_iter, "?"); + } + tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), "\n\n"+value_type+": "+debug_value.substr(pos+3, debug_value.size()-(pos+3)-1)); + } + } + } + } #endif - - return tooltip_buffer; - }; - - auto start=get_buffer()->get_iter_at_offset(offset); - auto end=start; - auto previous=start; - while(previous.backward_char() && ((*previous>='A' && *previous<='Z') || (*previous>='a' && *previous<='z') || (*previous>='0' && *previous<='9') || *previous=='_') && start.backward_char()) {} - while(((*end>='A' && *end<='Z') || (*end>='a' && *end<='z') || (*end>='0' && *end<='9') || *end=='_') && end.forward_char()) {} - type_tooltips.emplace_back(create_tooltip_buffer, this, get_buffer()->create_mark(start), get_buffer()->create_mark(end)); - type_tooltips.show(); - }); - } - } - }); + + return tooltip_buffer; + }; + + auto start = get_buffer()->get_iter_at_offset(offset); + auto end = start; + auto previous = start; + while (previous.backward_char() && ((*previous >= 'A' && *previous <= 'Z') || + (*previous >= 'a' && *previous <= 'z') || + (*previous >= '0' && *previous <= '9') || + *previous == '_') && + start.backward_char()) {} + while (((*end >= 'A' && *end <= 'Z') || (*end >= 'a' && *end <= 'z') || + (*end >= '0' && *end <= '9') || *end == '_') && end.forward_char()) {} + type_tooltips.emplace_back(create_tooltip_buffer, this, + get_buffer()->create_mark(start), + get_buffer()->create_mark(end)); + type_tooltips.show(); + }); + } + } + }); } void Source::LanguageProtocolView::tag_similar_symbols() { - if(!capabilities.document_highlight && !capabilities.references) - return; - - auto iter=get_buffer()->get_insert()->get_iter(); - std::string method; - if(capabilities.document_highlight) - method="textDocument/documentHighlight"; - else - method="textDocument/references"; - - static int request_count=0; - request_count++; - auto current_request=request_count; - client->write_request(this, method, "\"textDocument\":{\"uri\":\""+uri+"\"}, \"position\": {\"line\": "+std::to_string(iter.get_line())+", \"character\": "+std::to_string(iter.get_line_offset())+"}, \"context\": {\"includeDeclaration\": true}", [this, current_request](const boost::property_tree::ptree &result, bool error) { - if(!error) { - std::vector<std::pair<Offset, Offset>> offsets; - for(auto it=result.begin();it!=result.end();++it) { - if(capabilities.document_highlight || it->second.get<std::string>("uri", "")==uri) { - auto range_it=it->second.find("range"); - if(range_it!=it->second.not_found()) { - auto start_it=range_it->second.find("start"); - auto end_it=range_it->second.find("end"); - if(start_it!=range_it->second.not_found() && end_it!=range_it->second.not_found()) { - try { - offsets.emplace_back(std::make_pair(Offset(start_it->second.get<unsigned>("line"), start_it->second.get<unsigned>("character")), - Offset(end_it->second.get<unsigned>("line"), end_it->second.get<unsigned>("character")))); - } - catch(...) {} - } - } - } - } - dispatcher.post([this, offsets=std::move(offsets), current_request] { - if(current_request!=request_count) - return; - get_buffer()->remove_tag(similar_symbol_tag, get_buffer()->begin(), get_buffer()->end()); - for(auto &pair: offsets) { - auto start=get_iter_at_line_pos(pair.first.line, pair.first.index); - auto end=get_iter_at_line_pos(pair.second.line, pair.second.index); - get_buffer()->apply_tag(similar_symbol_tag, start, end); - } - }); - } - }); + if (!capabilities.document_highlight && !capabilities.references) + return; + + auto iter = get_buffer()->get_insert()->get_iter(); + std::string method; + if (capabilities.document_highlight) + method = "textDocument/documentHighlight"; + else + method = "textDocument/references"; + + static int request_count = 0; + request_count++; + auto current_request = request_count; + client->write_request(this, method, "\"textDocument\":{\"uri\":\"" + uri + "\"}, \"position\": {\"line\": " + + std::to_string(iter.get_line()) + ", \"character\": " + + std::to_string(iter.get_line_offset()) + + "}, \"context\": {\"includeDeclaration\": true}", + [this, current_request](const boost::property_tree::ptree &result, bool error) { + if (!error) { + std::vector<std::pair<Offset, Offset>> offsets; + for (auto it = result.begin(); it != result.end(); ++it) { + if (capabilities.document_highlight || + it->second.get<std::string>("uri", "") == uri) { + auto range_it = it->second.find("range"); + if (range_it != it->second.not_found()) { + auto start_it = range_it->second.find("start"); + auto end_it = range_it->second.find("end"); + if (start_it != range_it->second.not_found() && + end_it != range_it->second.not_found()) { + try { + offsets.emplace_back(std::make_pair( + Offset(start_it->second.get<unsigned>("line"), + start_it->second.get<unsigned>("character")), + Offset(end_it->second.get<unsigned>("line"), + end_it->second.get<unsigned>("character")))); + } + catch (...) {} + } + } + } + } + dispatcher.post([this, offsets = std::move(offsets), current_request] { + if (current_request != request_count) + return; + get_buffer()->remove_tag(similar_symbol_tag, get_buffer()->begin(), + get_buffer()->end()); + for (auto &pair: offsets) { + auto start = get_iter_at_line_pos(pair.first.line, pair.first.index); + auto end = get_iter_at_line_pos(pair.second.line, pair.second.index); + get_buffer()->apply_tag(similar_symbol_tag, start, end); + } + }); + } + }); } Source::Offset Source::LanguageProtocolView::get_declaration(const Gtk::TextIter &iter) { - auto offset=std::make_shared<Offset>(); - std::promise<void> result_processed; - client->write_request(this, "textDocument/definition", "\"textDocument\":{\"uri\":\""+uri+"\"}, \"position\": {\"line\": "+std::to_string(iter.get_line())+", \"character\": "+std::to_string(iter.get_line_offset())+"}", [offset, &result_processed](const boost::property_tree::ptree &result, bool error) { - if(!error) { - for(auto it=result.begin();it!=result.end();++it) { - auto uri=it->second.get<std::string>("uri", ""); - if(uri.compare(0, 7, "file://")==0) - uri.erase(0, 7); - auto range=it->second.find("range"); - if(range!=it->second.not_found()) { - auto start=range->second.find("start"); - if(start!=range->second.not_found()) - *offset=Offset(start->second.get<unsigned>("line", 0), start->second.get<unsigned>("character", 0), uri); - } - break; // TODO: can a language server return several definitions? - } - } - result_processed.set_value(); - }); - result_processed.get_future().get(); - return *offset; + auto offset = std::make_shared<Offset>(); + std::promise<void> result_processed; + client->write_request(this, "textDocument/definition", + "\"textDocument\":{\"uri\":\"" + uri + "\"}, \"position\": {\"line\": " + + std::to_string(iter.get_line()) + ", \"character\": " + + std::to_string(iter.get_line_offset()) + "}", + [offset, &result_processed](const boost::property_tree::ptree &result, bool error) { + if (!error) { + for (auto it = result.begin(); it != result.end(); ++it) { + auto uri = it->second.get<std::string>("uri", ""); + if (uri.compare(0, 7, "file://") == 0) + uri.erase(0, 7); + auto range = it->second.find("range"); + if (range != it->second.not_found()) { + auto start = range->second.find("start"); + if (start != range->second.not_found()) + *offset = Offset(start->second.get<unsigned>("line", 0), + start->second.get<unsigned>("character", 0), uri); + } + break; // TODO: can a language server return several definitions? + } + } + result_processed.set_value(); + }); + result_processed.get_future().get(); + return *offset; } void Source::LanguageProtocolView::setup_autocomplete() { - if(!capabilities.completion) - return; - - non_interactive_completion=[this] { - if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) - return; - autocomplete.run(); - }; - - autocomplete.reparse=[this] { - autocomplete_comment.clear(); - autocomplete_insert.clear(); - }; - - autocomplete.is_continue_key=[](guint keyval) { - if((keyval>='0' && keyval<='9') || (keyval>='a' && keyval<='z') || (keyval>='A' && keyval<='Z') || keyval=='_') - return true; - - return false; - }; - - autocomplete.is_restart_key=[this](guint keyval) { - auto iter=get_buffer()->get_insert()->get_iter(); - iter.backward_chars(2); - if(keyval=='.' || (keyval==':' && *iter==':')) - return true; - return false; - }; - - autocomplete.run_check=[this]() { - auto iter=get_buffer()->get_insert()->get_iter(); - iter.backward_char(); - if(!is_code_iter(iter)) - return false; - - std::string line=" "+get_line_before(); - const static std::regex dot_or_arrow("^.*[a-zA-Z0-9_\\)\\]\\>\"'](\\.)([a-zA-Z0-9_]*)$"); - const static std::regex colon_colon("^.*::([a-zA-Z0-9_]*)$"); - const static std::regex part_of_symbol("^.*[^a-zA-Z0-9_]+([a-zA-Z0-9_]{3,})$"); - std::smatch sm; - if(std::regex_match(line, sm, dot_or_arrow)) { - { - std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); - autocomplete.prefix=sm[2].str(); - } - if(autocomplete.prefix.size()==0 || autocomplete.prefix[0]<'0' || autocomplete.prefix[0]>'9') - return true; - } - else if(std::regex_match(line, sm, colon_colon)) { - { - std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); - autocomplete.prefix=sm[1].str(); - } - if(autocomplete.prefix.size()==0 || autocomplete.prefix[0]<'0' || autocomplete.prefix[0]>'9') - return true; - } - else if(std::regex_match(line, sm, part_of_symbol)) { - { - std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); - autocomplete.prefix=sm[1].str(); - } - if(autocomplete.prefix.size()==0 || autocomplete.prefix[0]<'0' || autocomplete.prefix[0]>'9') - return true; - } - else if(!interactive_completion) { - auto end_iter=get_buffer()->get_insert()->get_iter(); - auto iter=end_iter; - while(iter.backward_char() && autocomplete.is_continue_key(*iter)) {} - if(iter!=end_iter) - iter.forward_char(); - std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); - autocomplete.prefix=get_buffer()->get_text(iter, end_iter); - return true; - } - - return false; - }; - - autocomplete.before_add_rows=[this] { - status_state="autocomplete..."; - if(update_status_state) - update_status_state(this); - }; - - autocomplete.after_add_rows=[this] { - status_state=""; - if(update_status_state) - update_status_state(this); - }; - - autocomplete.on_add_rows_error=[this] { - autocomplete_comment.clear(); - autocomplete_insert.clear(); - }; - - autocomplete.add_rows=[this](std::string &buffer, int line_number, int column) { - if(autocomplete.state==Autocomplete::State::STARTING) { - autocomplete_comment.clear(); - autocomplete_insert.clear(); - std::promise<void> result_processed; - client->write_request(this, "textDocument/completion", "\"textDocument\":{\"uri\":\""+uri+"\"}, \"position\": {\"line\": "+std::to_string(line_number-1)+", \"character\": "+std::to_string(column-1)+"}", [this, &result_processed](const boost::property_tree::ptree &result, bool error) { - if(!error) { - auto begin=result.begin(); // rust language server is bugged - auto end=result.end(); - auto items_it=result.find("items"); // correct - if(items_it!=result.not_found()) { - begin=items_it->second.begin(); - end=items_it->second.end(); - } - for(auto it=begin;it!=end;++it) { - auto label=it->second.get<std::string>("label", ""); - auto detail=it->second.get<std::string>("detail", ""); - auto documentation=it->second.get<std::string>("documentation", ""); - auto insert=it->second.get<std::string>("insertText", ""); - if(insert.empty()) { - insert=label; - auto kind=it->second.get<unsigned>("kind", 0); - if(kind>=2 && kind<=3) { - bool found_bracket=false; - for(auto &chr: insert) { - if(chr=='(' || chr=='{') { - found_bracket=true; - break; - } - } - if(!found_bracket) - insert+="(${1:})"; - } + if (!capabilities.completion) + return; + + non_interactive_completion = [this] { + if (CompletionDialog::get() && CompletionDialog::get()->is_visible()) + return; + autocomplete.run(); + }; + + autocomplete.reparse = [this] { + autocomplete_comment.clear(); + autocomplete_insert.clear(); + }; + + autocomplete.is_continue_key = [](guint keyval) { + if ((keyval >= '0' && keyval <= '9') || (keyval >= 'a' && keyval <= 'z') || (keyval >= 'A' && keyval <= 'Z') || + keyval == '_') + return true; + + return false; + }; + + autocomplete.is_restart_key = [this](guint keyval) { + auto iter = get_buffer()->get_insert()->get_iter(); + iter.backward_chars(2); + if (keyval == '.' || (keyval == ':' && *iter == ':')) + return true; + return false; + }; + + autocomplete.run_check = [this]() { + auto iter = get_buffer()->get_insert()->get_iter(); + iter.backward_char(); + if (!is_code_iter(iter)) + return false; + + std::string line = " " + get_line_before(); + const static std::regex dot_or_arrow("^.*[a-zA-Z0-9_\\)\\]\\>\"'](\\.)([a-zA-Z0-9_]*)$"); + const static std::regex colon_colon("^.*::([a-zA-Z0-9_]*)$"); + const static std::regex part_of_symbol("^.*[^a-zA-Z0-9_]+([a-zA-Z0-9_]{3,})$"); + std::smatch sm; + if (std::regex_match(line, sm, dot_or_arrow)) { + { + std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); + autocomplete.prefix = sm[2].str(); } - if(!label.empty()) { - autocomplete.rows.emplace_back(std::move(label)); - autocomplete_comment.emplace_back(std::move(detail)); - if(!documentation.empty()) { - if(!autocomplete_comment.back().empty()) - autocomplete_comment.back()+="\n\n"; - autocomplete_comment.back()+=documentation; - } - autocomplete_insert.emplace_back(std::move(insert)); + if (autocomplete.prefix.size() == 0 || autocomplete.prefix[0] < '0' || autocomplete.prefix[0] > '9') + return true; + } else if (std::regex_match(line, sm, colon_colon)) { + { + std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); + autocomplete.prefix = sm[1].str(); } - } + if (autocomplete.prefix.size() == 0 || autocomplete.prefix[0] < '0' || autocomplete.prefix[0] > '9') + return true; + } else if (std::regex_match(line, sm, part_of_symbol)) { + { + std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); + autocomplete.prefix = sm[1].str(); + } + if (autocomplete.prefix.size() == 0 || autocomplete.prefix[0] < '0' || autocomplete.prefix[0] > '9') + return true; + } else if (!interactive_completion) { + auto end_iter = get_buffer()->get_insert()->get_iter(); + auto iter = end_iter; + while (iter.backward_char() && autocomplete.is_continue_key(*iter)) {} + if (iter != end_iter) + iter.forward_char(); + std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); + autocomplete.prefix = get_buffer()->get_text(iter, end_iter); + return true; } - result_processed.set_value(); - }); - result_processed.get_future().get(); - } - }; - - signal_key_press_event().connect([this](GdkEventKey *event) { - if((event->keyval==GDK_KEY_Tab || event->keyval==GDK_KEY_ISO_Left_Tab) && (event->state&GDK_SHIFT_MASK)==0) { - if(!autocomplete_marks.empty()) { - auto it=autocomplete_marks.begin(); - auto start=it->first->get_iter(); - auto end=it->second->get_iter(); - if(start==end) - return false; - autocomplete_keep_marks=true; - get_buffer()->select_range(it->first->get_iter(), it->second->get_iter()); - autocomplete_keep_marks=false; - get_buffer()->delete_mark(it->first); - get_buffer()->delete_mark(it->second); - autocomplete_marks.erase(it); - return true; - } - } - return false; - }, false); - - get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { - if(mark->get_name() == "insert") { - if(!autocomplete_keep_marks) { - for(auto &pair: autocomplete_marks) { - get_buffer()->delete_mark(pair.first); - get_buffer()->delete_mark(pair.second); + + return false; + }; + + autocomplete.before_add_rows = [this] { + status_state = "autocomplete..."; + if (update_status_state) + update_status_state(this); + }; + + autocomplete.after_add_rows = [this] { + status_state = ""; + if (update_status_state) + update_status_state(this); + }; + + autocomplete.on_add_rows_error = [this] { + autocomplete_comment.clear(); + autocomplete_insert.clear(); + }; + + autocomplete.add_rows = [this](std::string &buffer, int line_number, int column) { + if (autocomplete.state == Autocomplete::State::STARTING) { + autocomplete_comment.clear(); + autocomplete_insert.clear(); + std::promise<void> result_processed; + client->write_request(this, "textDocument/completion", + "\"textDocument\":{\"uri\":\"" + uri + "\"}, \"position\": {\"line\": " + + std::to_string(line_number - 1) + ", \"character\": " + std::to_string(column - 1) + + "}", + [this, &result_processed](const boost::property_tree::ptree &result, bool error) { + if (!error) { + auto begin = result.begin(); // rust language server is bugged + auto end = result.end(); + auto items_it = result.find("items"); // correct + if (items_it != result.not_found()) { + begin = items_it->second.begin(); + end = items_it->second.end(); + } + for (auto it = begin; it != end; ++it) { + auto label = it->second.get<std::string>("label", ""); + auto detail = it->second.get<std::string>("detail", ""); + auto documentation = it->second.get<std::string>("documentation", ""); + auto insert = it->second.get<std::string>("insertText", ""); + if (insert.empty()) { + insert = label; + auto kind = it->second.get<unsigned>("kind", 0); + if (kind >= 2 && kind <= 3) { + bool found_bracket = false; + for (auto &chr: insert) { + if (chr == '(' || chr == '{') { + found_bracket = true; + break; + } + } + if (!found_bracket) + insert += "(${1:})"; + } + } + if (!label.empty()) { + autocomplete.rows.emplace_back(std::move(label)); + autocomplete_comment.emplace_back(std::move(detail)); + if (!documentation.empty()) { + if (!autocomplete_comment.back().empty()) + autocomplete_comment.back() += "\n\n"; + autocomplete_comment.back() += documentation; + } + autocomplete_insert.emplace_back(std::move(insert)); + } + } + } + result_processed.set_value(); + }); + result_processed.get_future().get(); } - autocomplete_marks.clear(); - } - } - }); - - autocomplete.on_show = [this] { - for(auto &pair: autocomplete_marks) { - get_buffer()->delete_mark(pair.first); - get_buffer()->delete_mark(pair.second); - } - autocomplete_marks.clear(); - hide_tooltips(); - }; - - autocomplete.on_hide = [this] { - autocomplete_comment.clear(); - autocomplete_insert.clear(); - }; - - autocomplete.on_select = [this](unsigned int index, const std::string &text, bool hide_window) { - get_buffer()->erase(CompletionDialog::get()->start_mark->get_iter(), get_buffer()->get_insert()->get_iter()); - if(hide_window) { - Glib::ustring insert=autocomplete_insert[index]; - - // Do not insert function/template parameters if they already exist - auto iter=get_buffer()->get_insert()->get_iter(); - if(*iter=='(' || *iter=='<') { - auto bracket_pos=insert.find(*iter); - if(bracket_pos!=std::string::npos) { - insert=insert.substr(0, bracket_pos); + }; + + signal_key_press_event().connect([this](GdkEventKey *event) { + if ((event->keyval == GDK_KEY_Tab || event->keyval == GDK_KEY_ISO_Left_Tab) && + (event->state & GDK_SHIFT_MASK) == 0) { + if (!autocomplete_marks.empty()) { + auto it = autocomplete_marks.begin(); + auto start = it->first->get_iter(); + auto end = it->second->get_iter(); + if (start == end) + return false; + autocomplete_keep_marks = true; + get_buffer()->select_range(it->first->get_iter(), it->second->get_iter()); + autocomplete_keep_marks = false; + get_buffer()->delete_mark(it->first); + get_buffer()->delete_mark(it->second); + autocomplete_marks.erase(it); + return true; + } } - } - - // Find and add position marks that one can move to using tab-key - size_t pos1=0; - std::vector<std::pair<size_t, size_t>> mark_offsets; - while((pos1=insert.find("${"), pos1)!=Glib::ustring::npos) { - size_t pos2=insert.find(":", pos1+2); - if(pos2!=Glib::ustring::npos) { - size_t pos3=insert.find("}", pos2+1); - if(pos3!=Glib::ustring::npos) { - size_t length=pos3-pos2-1; - insert.erase(pos3, 1); - insert.erase(pos1, pos2-pos1+1); - mark_offsets.emplace_back(pos1, pos1+length); - pos1+=length; - } - else - break; + return false; + }, false); + + get_buffer()->signal_mark_set().connect( + [this](const Gtk::TextBuffer::iterator &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { + if (mark->get_name() == "insert") { + if (!autocomplete_keep_marks) { + for (auto &pair: autocomplete_marks) { + get_buffer()->delete_mark(pair.first); + get_buffer()->delete_mark(pair.second); + } + autocomplete_marks.clear(); + } + } + }); + + autocomplete.on_show = [this] { + for (auto &pair: autocomplete_marks) { + get_buffer()->delete_mark(pair.first); + get_buffer()->delete_mark(pair.second); } - else - break; - } - get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), insert); - for(auto &offset: mark_offsets) { - auto start=CompletionDialog::get()->start_mark->get_iter(); - auto end=start; - start.forward_chars(offset.first); - end.forward_chars(offset.second); - autocomplete_marks.emplace_back(get_buffer()->create_mark(start), get_buffer()->create_mark(end)); - } - if(!autocomplete_marks.empty()) { - auto it=autocomplete_marks.begin(); - autocomplete_keep_marks=true; - get_buffer()->select_range(it->first->get_iter(), it->second->get_iter()); - autocomplete_keep_marks=false; - get_buffer()->delete_mark(it->first); - get_buffer()->delete_mark(it->second); - autocomplete_marks.erase(it); - } - } - else - get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), text); - }; - - autocomplete.get_tooltip = [this](unsigned int index) { - return autocomplete_comment[index]; - }; + autocomplete_marks.clear(); + hide_tooltips(); + }; + + autocomplete.on_hide = [this] { + autocomplete_comment.clear(); + autocomplete_insert.clear(); + }; + + autocomplete.on_select = [this](unsigned int index, const std::string &text, bool hide_window) { + get_buffer()->erase(CompletionDialog::get()->start_mark->get_iter(), get_buffer()->get_insert()->get_iter()); + if (hide_window) { + Glib::ustring insert = autocomplete_insert[index]; + + // Do not insert function/template parameters if they already exist + auto iter = get_buffer()->get_insert()->get_iter(); + if (*iter == '(' || *iter == '<') { + auto bracket_pos = insert.find(*iter); + if (bracket_pos != std::string::npos) { + insert = insert.substr(0, bracket_pos); + } + } + + // Find and add position marks that one can move to using tab-key + size_t pos1 = 0; + std::vector<std::pair<size_t, size_t>> mark_offsets; + while ((pos1 = insert.find("${"), pos1) != Glib::ustring::npos) { + size_t pos2 = insert.find(":", pos1 + 2); + if (pos2 != Glib::ustring::npos) { + size_t pos3 = insert.find("}", pos2 + 1); + if (pos3 != Glib::ustring::npos) { + size_t length = pos3 - pos2 - 1; + insert.erase(pos3, 1); + insert.erase(pos1, pos2 - pos1 + 1); + mark_offsets.emplace_back(pos1, pos1 + length); + pos1 += length; + } else + break; + } else + break; + } + get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), insert); + for (auto &offset: mark_offsets) { + auto start = CompletionDialog::get()->start_mark->get_iter(); + auto end = start; + start.forward_chars(offset.first); + end.forward_chars(offset.second); + autocomplete_marks.emplace_back(get_buffer()->create_mark(start), get_buffer()->create_mark(end)); + } + if (!autocomplete_marks.empty()) { + auto it = autocomplete_marks.begin(); + autocomplete_keep_marks = true; + get_buffer()->select_range(it->first->get_iter(), it->second->get_iter()); + autocomplete_keep_marks = false; + get_buffer()->delete_mark(it->first); + get_buffer()->delete_mark(it->second); + autocomplete_marks.erase(it); + } + } else + get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), text); + }; + + autocomplete.get_tooltip = [this](unsigned int index) { + return autocomplete_comment[index]; + }; } void Source::LanguageProtocolView::add_flow_coverage_tooltips(bool called_in_thread) { - std::stringstream stdin_stream, stderr_stream; - auto stdout_stream=std::make_shared<std::stringstream>(); - auto exit_status=Terminal::get().process(stdin_stream, *stdout_stream, flow_coverage_executable.string()+" coverage --json "+file_path.string(), "", &stderr_stream); - auto f=[this, exit_status, stdout_stream] { - clear_diagnostic_tooltips(); - num_flow_coverage_warnings=0; - for(auto &mark: flow_coverage_marks) { - get_buffer()->delete_mark(mark.first); - get_buffer()->delete_mark(mark.second); - } - flow_coverage_marks.clear(); - - if(exit_status==0) { - boost::property_tree::ptree pt; - try { - boost::property_tree::read_json(*stdout_stream, pt); - auto uncovered_locs_pt=pt.get_child("expressions.uncovered_locs"); - for(auto it=uncovered_locs_pt.begin();it!=uncovered_locs_pt.end();++it) { - auto start_pt=it->second.get_child("start"); - auto start=get_iter_at_line_offset(start_pt.get<int>("line")-1, start_pt.get<int>("column")-1); - auto end_pt=it->second.get_child("end"); - auto end=get_iter_at_line_offset(end_pt.get<int>("line")-1, end_pt.get<int>("column")); - - add_diagnostic_tooltip(start, end, flow_coverage_message, false); - ++num_flow_coverage_warnings; - - flow_coverage_marks.emplace_back(get_buffer()->create_mark(start), get_buffer()->create_mark(end)); + std::stringstream stdin_stream, stderr_stream; + auto stdout_stream = std::make_shared<std::stringstream>(); + auto exit_status = Terminal::get().process(stdin_stream, *stdout_stream, + flow_coverage_executable.string() + " coverage --json " + + file_path.string(), "", &stderr_stream); + auto f = [this, exit_status, stdout_stream] { + clear_diagnostic_tooltips(); + num_flow_coverage_warnings = 0; + for (auto &mark: flow_coverage_marks) { + get_buffer()->delete_mark(mark.first); + get_buffer()->delete_mark(mark.second); } - } - catch(...) {} - } - status_diagnostics=std::make_tuple(num_warnings+num_flow_coverage_warnings, num_errors, num_fix_its); - if(update_status_diagnostics) - update_status_diagnostics(this); - }; - if(called_in_thread) - dispatcher.post(std::move(f)); - else - f(); + flow_coverage_marks.clear(); + + if (exit_status == 0) { + boost::property_tree::ptree pt; + try { + boost::property_tree::read_json(*stdout_stream, pt); + auto uncovered_locs_pt = pt.get_child("expressions.uncovered_locs"); + for (auto it = uncovered_locs_pt.begin(); it != uncovered_locs_pt.end(); ++it) { + auto start_pt = it->second.get_child("start"); + auto start = get_iter_at_line_offset(start_pt.get<int>("line") - 1, + start_pt.get<int>("column") - 1); + auto end_pt = it->second.get_child("end"); + auto end = get_iter_at_line_offset(end_pt.get<int>("line") - 1, end_pt.get<int>("column")); + + add_diagnostic_tooltip(start, end, flow_coverage_message, false); + ++num_flow_coverage_warnings; + + flow_coverage_marks.emplace_back(get_buffer()->create_mark(start), get_buffer()->create_mark(end)); + } + } + catch (...) {} + } + status_diagnostics = std::make_tuple(num_warnings + num_flow_coverage_warnings, num_errors, num_fix_its); + if (update_status_diagnostics) + update_status_diagnostics(this); + }; + if (called_in_thread) + dispatcher.post(std::move(f)); + else + f(); } diff --git a/src/source_language_protocol.h b/src/source_language_protocol.h index 967c6fc6..dccd3574 100644 --- a/src/source_language_protocol.h +++ b/src/source_language_protocol.h @@ -1,4 +1,5 @@ #pragma once + #include "autocomplete.h" #include "process.hpp" #include "source.h" @@ -10,125 +11,144 @@ #include <unordered_set> namespace Source { - class LanguageProtocolView; + class LanguageProtocolView; } namespace LanguageProtocol { - class Diagnostic { - public: - std::string spelling; - std::pair<Source::Offset, Source::Offset> offsets; - unsigned severity; - std::string uri; - }; - - class Capabilities { - public: - enum class TextDocumentSync { NONE = 0, - FULL, - INCREMENTAL }; - TextDocumentSync text_document_sync; - bool hover; - bool completion; - bool definition; - bool references; - bool document_highlight; - bool workspace_symbol; - bool document_formatting; - bool document_range_formatting; - bool rename; - }; - - class Client { - Client(std::string root_uri, std::string language_id); - std::string root_uri; - std::string language_id; - - Capabilities capabilities; - - std::unordered_set<Source::LanguageProtocolView *> views; - std::mutex views_mutex; - - std::mutex initialize_mutex; - - std::unique_ptr<TinyProcessLib::Process> process; - std::mutex read_write_mutex; - - std::stringstream server_message_stream; - size_t server_message_size = static_cast<size_t>(-1); - size_t server_message_content_pos; - bool header_read = false; - - size_t message_id = 1; - - std::unordered_map<size_t, std::pair<Source::LanguageProtocolView*, std::function<void(const boost::property_tree::ptree &, bool error)>>> handlers; - std::vector<std::thread> timeout_threads; - std::mutex timeout_threads_mutex; - - public: - static std::shared_ptr<Client> get(const boost::filesystem::path &file_path, const std::string &language_id); - - ~Client(); - - bool initialized = false; - Capabilities initialize(Source::LanguageProtocolView *view); - void close(Source::LanguageProtocolView *view); - - void parse_server_message(); - void write_request(Source::LanguageProtocolView *view, const std::string &method, const std::string ¶ms, std::function<void(const boost::property_tree::ptree &, bool)> &&function = nullptr); - void write_notification(const std::string &method, const std::string ¶ms); - void handle_server_request(const std::string &method, const boost::property_tree::ptree ¶ms); - }; + class Diagnostic { + public: + std::string spelling; + std::pair<Source::Offset, Source::Offset> offsets; + unsigned severity; + std::string uri; + }; + + class Capabilities { + public: + enum class TextDocumentSync { + NONE = 0, + FULL, + INCREMENTAL + }; + TextDocumentSync text_document_sync; + bool hover; + bool completion; + bool definition; + bool references; + bool document_highlight; + bool workspace_symbol; + bool document_formatting; + bool document_range_formatting; + bool rename; + }; + + class Client { + Client(std::string root_uri, std::string language_id); + + std::string root_uri; + std::string language_id; + + Capabilities capabilities; + + std::unordered_set<Source::LanguageProtocolView *> views; + std::mutex views_mutex; + + std::mutex initialize_mutex; + + std::unique_ptr<TinyProcessLib::Process> process; + std::mutex read_write_mutex; + + std::stringstream server_message_stream; + size_t server_message_size = static_cast<size_t>(-1); + size_t server_message_content_pos; + bool header_read = false; + + size_t message_id = 1; + + std::unordered_map<size_t, std::pair<Source::LanguageProtocolView *, std::function<void( + const boost::property_tree::ptree &, bool error)>>> handlers; + std::vector<std::thread> timeout_threads; + std::mutex timeout_threads_mutex; + + public: + static std::shared_ptr<Client> get(const boost::filesystem::path &file_path, const std::string &language_id); + + ~Client(); + + bool initialized = false; + + Capabilities initialize(Source::LanguageProtocolView *view); + + void close(Source::LanguageProtocolView *view); + + void parse_server_message(); + + void write_request(Source::LanguageProtocolView *view, const std::string &method, const std::string ¶ms, + std::function<void(const boost::property_tree::ptree &, bool)> &&function = nullptr); + + void write_notification(const std::string &method, const std::string ¶ms); + + void handle_server_request(const std::string &method, const boost::property_tree::ptree ¶ms); + }; } // namespace LanguageProtocol namespace Source { - class LanguageProtocolView : public View { - public: - LanguageProtocolView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language, std::string language_id_); - ~LanguageProtocolView(); - std::string uri; - - bool save() override; - - void update_diagnostics(std::vector<LanguageProtocol::Diagnostic> &&diagnostics); - - Gtk::TextIter get_iter_at_line_pos(int line, int pos) override; - - protected: - void show_type_tooltips(const Gdk::Rectangle &rectangle) override; - - private: - std::string language_id; - LanguageProtocol::Capabilities capabilities; - - std::shared_ptr<LanguageProtocol::Client> client; - - size_t document_version = 1; - - std::thread initialize_thread; - Dispatcher dispatcher; - - void setup_navigation_and_refactoring(); - - void escape_text(std::string &text); - void unescape_text(std::string &text); - - Glib::RefPtr<Gtk::TextTag> similar_symbol_tag; - void tag_similar_symbols(); - - Offset get_declaration(const Gtk::TextIter &iter); - - Autocomplete autocomplete; - void setup_autocomplete(); - std::vector<std::string> autocomplete_comment; - std::vector<std::string> autocomplete_insert; - std::list<std::pair<Glib::RefPtr<Gtk::TextBuffer::Mark>, Glib::RefPtr<Gtk::TextBuffer::Mark>>> autocomplete_marks; - bool autocomplete_keep_marks = false; - - boost::filesystem::path flow_coverage_executable; - std::vector<std::pair<Glib::RefPtr<Gtk::TextMark>, Glib::RefPtr<Gtk::TextMark>>> flow_coverage_marks; - const std::string flow_coverage_message="Not covered by Flow"; - size_t num_warnings=0, num_errors=0, num_fix_its=0, num_flow_coverage_warnings=0; - void add_flow_coverage_tooltips(bool called_in_thread); - }; + class LanguageProtocolView : public View { + public: + LanguageProtocolView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language, + std::string language_id_); + + ~LanguageProtocolView(); + + std::string uri; + + bool save() override; + + void update_diagnostics(std::vector<LanguageProtocol::Diagnostic> &&diagnostics); + + Gtk::TextIter get_iter_at_line_pos(int line, int pos) override; + + protected: + void show_type_tooltips(const Gdk::Rectangle &rectangle) override; + + private: + std::string language_id; + LanguageProtocol::Capabilities capabilities; + + std::shared_ptr<LanguageProtocol::Client> client; + + size_t document_version = 1; + + std::thread initialize_thread; + Dispatcher dispatcher; + + void setup_navigation_and_refactoring(); + + void escape_text(std::string &text); + + void unescape_text(std::string &text); + + Glib::RefPtr<Gtk::TextTag> similar_symbol_tag; + sigc::connection delayed_tag_similar_symbols_connection; + + void tag_similar_symbols(); + + Offset get_declaration(const Gtk::TextIter &iter); + + Autocomplete autocomplete; + + void setup_autocomplete(); + + std::vector<std::string> autocomplete_comment; + std::vector<std::string> autocomplete_insert; + std::list<std::pair<Glib::RefPtr<Gtk::TextBuffer::Mark>, Glib::RefPtr<Gtk::TextBuffer::Mark>>> autocomplete_marks; + bool autocomplete_keep_marks = false; + + boost::filesystem::path flow_coverage_executable; + std::vector<std::pair<Glib::RefPtr<Gtk::TextMark>, Glib::RefPtr<Gtk::TextMark>>> flow_coverage_marks; + const std::string flow_coverage_message = "Not covered by Flow"; + size_t num_warnings = 0, num_errors = 0, num_fix_its = 0, num_flow_coverage_warnings = 0; + + void add_flow_coverage_tooltips(bool called_in_thread); + }; } // namespace Source diff --git a/src/source_spellcheck.cc b/src/source_spellcheck.cc index f9247e46..ff2bb227 100644 --- a/src/source_spellcheck.cc +++ b/src/source_spellcheck.cc @@ -4,496 +4,509 @@ #include "selection_dialog.h" #include <iostream> -AspellConfig* Source::SpellCheckView::spellcheck_config=nullptr; - -Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language): BaseView(file_path, language) { - if(spellcheck_config==nullptr) - spellcheck_config=new_aspell_config(); - spellcheck_checker=nullptr; - spellcheck_error_tag=get_buffer()->create_tag("spellcheck_error"); - spellcheck_error_tag->property_underline()=Pango::Underline::UNDERLINE_ERROR; - - signal_key_press_event().connect([](GdkEventKey *event) { - if(SelectionDialog::get() && SelectionDialog::get()->is_visible()) { - if(SelectionDialog::get()->on_key_press(event)) - return true; - } - - return false; - }, false); - - //The following signal is added in case SpellCheckView is not subclassed - signal_key_press_event().connect([this](GdkEventKey *event) { - last_keyval=event->keyval; - return false; - }); - - get_buffer()->signal_changed().connect([this]() { - if(spellcheck_checker==nullptr) - return; - - delayed_spellcheck_suggestions_connection.disconnect(); - - auto iter=get_buffer()->get_insert()->get_iter(); - if(!is_word_iter(iter) && !iter.starts_line()) - iter.backward_char(); - - if(disable_spellcheck) { - if(is_word_iter(iter)) { - auto word=get_word(iter); - get_buffer()->remove_tag(spellcheck_error_tag, word.first, word.second); - } - return; - } - - if(!is_code_iter(iter)) { - if(last_keyval==GDK_KEY_Return || last_keyval==GDK_KEY_KP_Enter) { - auto previous_line_iter=iter; - while(previous_line_iter.backward_char() && !previous_line_iter.ends_line()) {} - if(previous_line_iter.backward_char()) { - get_buffer()->remove_tag(spellcheck_error_tag, previous_line_iter, iter); - if(!is_code_iter(previous_line_iter)) { - auto word=get_word(previous_line_iter); - spellcheck_word(word.first, word.second); - } - auto word=get_word(iter); - spellcheck_word(word.first, word.second); - } - } - else { - auto previous_iter=iter; - //When for instance using space to split two words - if(!iter.starts_line() && !iter.ends_line() && is_word_iter(iter) && - previous_iter.backward_char() && !previous_iter.starts_line() && !is_word_iter(previous_iter)) { - auto first=previous_iter; - auto second=iter; - if(first.backward_char()) { - get_buffer()->remove_tag(spellcheck_error_tag, first, second); - auto word=get_word(first); - spellcheck_word(word.first, word.second); - word=get_word(second); - spellcheck_word(word.first, word.second); - } +AspellConfig *Source::SpellCheckView::spellcheck_config = nullptr; + +Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language) + : BaseView(file_path, language) { + if (spellcheck_config == nullptr) + spellcheck_config = new_aspell_config(); + spellcheck_checker = nullptr; + spellcheck_error_tag = get_buffer()->create_tag("spellcheck_error"); + spellcheck_error_tag->property_underline() = Pango::Underline::UNDERLINE_ERROR; + + signal_key_press_event().connect([](GdkEventKey *event) { + if (SelectionDialog::get() && SelectionDialog::get()->is_visible()) { + if (SelectionDialog::get()->on_key_press(event)) + return true; } - else { - auto word=get_word(iter); - spellcheck_word(word.first, word.second); + + return false; + }, false); + + //The following signal is added in case SpellCheckView is not subclassed + signal_key_press_event().connect([this](GdkEventKey *event) { + last_keyval = event->keyval; + return false; + }); + + get_buffer()->signal_changed().connect([this]() { + if (spellcheck_checker == nullptr) + return; + + delayed_spellcheck_suggestions_connection.disconnect(); + + auto iter = get_buffer()->get_insert()->get_iter(); + if (!is_word_iter(iter) && !iter.starts_line()) + iter.backward_char(); + + if (disable_spellcheck) { + if (is_word_iter(iter)) { + auto word = get_word(iter); + get_buffer()->remove_tag(spellcheck_error_tag, word.first, word.second); + } + return; } - } - } - delayed_spellcheck_error_clear.disconnect(); - delayed_spellcheck_error_clear=Glib::signal_timeout().connect([this]() { - auto iter=get_buffer()->begin(); - Gtk::TextIter begin_no_spellcheck_iter; - if(spellcheck_all) { - bool spell_check=!get_source_buffer()->iter_has_context_class(iter, "no-spell-check"); - if(!spell_check) - begin_no_spellcheck_iter=iter; - while(iter!=get_buffer()->end()) { - if(!get_source_buffer()->iter_forward_to_context_class_toggle(iter, "no-spell-check")) - iter=get_buffer()->end(); - - spell_check=!spell_check; - if(!spell_check) - begin_no_spellcheck_iter=iter; - else - get_buffer()->remove_tag(spellcheck_error_tag, begin_no_spellcheck_iter, iter); + + if (!is_code_iter(iter)) { + if (last_keyval == GDK_KEY_Return || last_keyval == GDK_KEY_KP_Enter) { + auto previous_line_iter = iter; + while (previous_line_iter.backward_char() && !previous_line_iter.ends_line()) {} + if (previous_line_iter.backward_char()) { + get_buffer()->remove_tag(spellcheck_error_tag, previous_line_iter, iter); + if (!is_code_iter(previous_line_iter)) { + auto word = get_word(previous_line_iter); + spellcheck_word(word.first, word.second); + } + auto word = get_word(iter); + spellcheck_word(word.first, word.second); + } + } else { + auto previous_iter = iter; + //When for instance using space to split two words + if (!iter.starts_line() && !iter.ends_line() && is_word_iter(iter) && + previous_iter.backward_char() && !previous_iter.starts_line() && !is_word_iter(previous_iter)) { + auto first = previous_iter; + auto second = iter; + if (first.backward_char()) { + get_buffer()->remove_tag(spellcheck_error_tag, first, second); + auto word = get_word(first); + spellcheck_word(word.first, word.second); + word = get_word(second); + spellcheck_word(word.first, word.second); + } + } else { + auto word = get_word(iter); + spellcheck_word(word.first, word.second); + } + } } - return false; - } - - bool spell_check=get_source_buffer()->iter_has_context_class(iter, "string") || get_source_buffer()->iter_has_context_class(iter, "comment"); - if(!spell_check) - begin_no_spellcheck_iter=iter; - while(iter!=get_buffer()->end()) { - auto iter1=iter; - auto iter2=iter; - if(!get_source_buffer()->iter_forward_to_context_class_toggle(iter1, "string")) - iter1=get_buffer()->end(); - if(!get_source_buffer()->iter_forward_to_context_class_toggle(iter2, "comment")) - iter2=get_buffer()->end(); - - if(iter2<iter1) - iter=iter2; - else - iter=iter1; - spell_check=!spell_check; - if(!spell_check) - begin_no_spellcheck_iter=iter; - else - get_buffer()->remove_tag(spellcheck_error_tag, begin_no_spellcheck_iter, iter); - } - return false; - }, 1000); - }); - - // In case of for instance text paste or undo/redo - get_buffer()->signal_insert().connect([this](const Gtk::TextIter &start_iter, const Glib::ustring &inserted_string, int) { - if(spellcheck_checker==nullptr) - return; - - if(!disable_spellcheck) - return; - - auto iter=start_iter; - if(!is_word_iter(iter) && !iter.starts_line()) - iter.backward_char(); - if(is_word_iter(iter)) { - auto word=get_word(iter); - get_buffer()->remove_tag(spellcheck_error_tag, word.first, word.second); - } - }, false); - - get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator& iter, const Glib::RefPtr<Gtk::TextBuffer::Mark>& mark) { - if(spellcheck_checker==nullptr) - return; - - if(mark->get_name()=="insert") { - if(SelectionDialog::get()) - SelectionDialog::get()->hide(); - delayed_spellcheck_suggestions_connection.disconnect(); - delayed_spellcheck_suggestions_connection=Glib::signal_timeout().connect([this]() { - if(get_buffer()->get_insert()->get_iter().has_tag(spellcheck_error_tag)) { - SelectionDialog::create(this, get_buffer()->create_mark(get_buffer()->get_insert()->get_iter()), false); - auto word=get_word(get_buffer()->get_insert()->get_iter()); - if(*word.first=='\'' && word.second.get_offset()-word.first.get_offset()>=3) { - auto before_end=word.second; - if(before_end.backward_char() && *before_end=='\'') { - word.first.forward_char(); - word.second.backward_char(); + delayed_spellcheck_error_clear.disconnect(); + delayed_spellcheck_error_clear = Glib::signal_timeout().connect([this]() { + auto iter = get_buffer()->begin(); + Gtk::TextIter begin_no_spellcheck_iter; + if (spellcheck_all) { + bool spell_check = !get_source_buffer()->iter_has_context_class(iter, "no-spell-check"); + if (!spell_check) + begin_no_spellcheck_iter = iter; + while (iter != get_buffer()->end()) { + if (!get_source_buffer()->iter_forward_to_context_class_toggle(iter, "no-spell-check")) + iter = get_buffer()->end(); + + spell_check = !spell_check; + if (!spell_check) + begin_no_spellcheck_iter = iter; + else + get_buffer()->remove_tag(spellcheck_error_tag, begin_no_spellcheck_iter, iter); + } + return false; + } + + bool spell_check = get_source_buffer()->iter_has_context_class(iter, "string") || + get_source_buffer()->iter_has_context_class(iter, "comment"); + if (!spell_check) + begin_no_spellcheck_iter = iter; + while (iter != get_buffer()->end()) { + auto iter1 = iter; + auto iter2 = iter; + if (!get_source_buffer()->iter_forward_to_context_class_toggle(iter1, "string")) + iter1 = get_buffer()->end(); + if (!get_source_buffer()->iter_forward_to_context_class_toggle(iter2, "comment")) + iter2 = get_buffer()->end(); + + if (iter2 < iter1) + iter = iter2; + else + iter = iter1; + spell_check = !spell_check; + if (!spell_check) + begin_no_spellcheck_iter = iter; + else + get_buffer()->remove_tag(spellcheck_error_tag, begin_no_spellcheck_iter, iter); } - } - auto suggestions=get_spellcheck_suggestions(word.first, word.second); - if(suggestions.size()==0) return false; - for(auto &suggestion: suggestions) - SelectionDialog::get()->add_row(suggestion); - SelectionDialog::get()->on_select=[this, word](unsigned int index, const std::string &text, bool hide_window) { - get_buffer()->begin_user_action(); - get_buffer()->erase(word.first, word.second); - get_buffer()->insert(get_buffer()->get_insert()->get_iter(), text); - get_buffer()->end_user_action(); - }; - hide_tooltips(); - SelectionDialog::get()->show(); - } + }, 1000); + }); + + // In case of for instance text paste or undo/redo + get_buffer()->signal_insert().connect( + [this](const Gtk::TextIter &start_iter, const Glib::ustring &inserted_string, int) { + if (spellcheck_checker == nullptr) + return; + + if (!disable_spellcheck) + return; + + auto iter = start_iter; + if (!is_word_iter(iter) && !iter.starts_line()) + iter.backward_char(); + if (is_word_iter(iter)) { + auto word = get_word(iter); + get_buffer()->remove_tag(spellcheck_error_tag, word.first, word.second); + } + }, false); + + get_buffer()->signal_mark_set().connect( + [this](const Gtk::TextBuffer::iterator &iter, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { + if (spellcheck_checker == nullptr) + return; + + if (mark->get_name() == "insert") { + if (SelectionDialog::get()) + SelectionDialog::get()->hide(); + delayed_spellcheck_suggestions_connection.disconnect(); + delayed_spellcheck_suggestions_connection = Glib::signal_timeout().connect([this]() { + if (get_buffer()->get_insert()->get_iter().has_tag(spellcheck_error_tag)) { + SelectionDialog::create(this, + get_buffer()->create_mark(get_buffer()->get_insert()->get_iter()), + false); + auto word = get_word(get_buffer()->get_insert()->get_iter()); + if (*word.first == '\'' && word.second.get_offset() - word.first.get_offset() >= 3) { + auto before_end = word.second; + if (before_end.backward_char() && *before_end == '\'') { + word.first.forward_char(); + word.second.backward_char(); + } + } + auto suggestions = get_spellcheck_suggestions(word.first, word.second); + if (suggestions.size() == 0) + return false; + for (auto &suggestion: suggestions) + SelectionDialog::get()->add_row(suggestion); + SelectionDialog::get()->on_select = [this, word](unsigned int index, + const std::string &text, + bool hide_window) { + get_buffer()->begin_user_action(); + get_buffer()->erase(word.first, word.second); + get_buffer()->insert(get_buffer()->get_insert()->get_iter(), text); + get_buffer()->end_user_action(); + }; + hide_tooltips(); + SelectionDialog::get()->show(); + } + return false; + }, 500); + } + }); + + get_buffer()->signal_mark_set().connect( + [](const Gtk::TextBuffer::iterator &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { + if (mark->get_name() == "insert") { + if (SelectionDialog::get()) + SelectionDialog::get()->hide(); + } + }); + + signal_focus_out_event().connect([this](GdkEventFocus *event) { + delayed_spellcheck_suggestions_connection.disconnect(); return false; - }, 500); - } - }); - - get_buffer()->signal_mark_set().connect([](const Gtk::TextBuffer::iterator& iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark>& mark) { - if(mark->get_name()=="insert") { - if(SelectionDialog::get()) - SelectionDialog::get()->hide(); - } - }); - - signal_focus_out_event().connect([this](GdkEventFocus* event) { - delayed_spellcheck_suggestions_connection.disconnect(); - return false; - }); - - signal_leave_notify_event().connect([this](GdkEventCrossing*) { - delayed_spellcheck_suggestions_connection.disconnect(); - return false; - }); - - signal_tag_added_connection=get_buffer()->get_tag_table()->signal_tag_added().connect([this](const Glib::RefPtr<Gtk::TextTag> &tag) { - if(tag->property_name()=="gtksourceview:context-classes:comment") - comment_tag=tag; - else if(tag->property_name()=="gtksourceview:context-classes:string") - string_tag=tag; - else if(tag->property_name()=="gtksourceview:context-classes:no-spell-check") - no_spell_check_tag=tag; - }); - signal_tag_removed_connection=get_buffer()->get_tag_table()->signal_tag_removed().connect([this](const Glib::RefPtr<Gtk::TextTag> &tag) { - if(tag->property_name()=="gtksourceview:context-classes:comment") - comment_tag.reset(); - else if(tag->property_name()=="gtksourceview:context-classes:string") - string_tag.reset(); - else if(tag->property_name()=="gtksourceview:context-classes:no-spell-check") - no_spell_check_tag.reset(); - }); + }); + + signal_leave_notify_event().connect([this](GdkEventCrossing *) { + delayed_spellcheck_suggestions_connection.disconnect(); + return false; + }); + + signal_tag_added_connection = get_buffer()->get_tag_table()->signal_tag_added().connect( + [this](const Glib::RefPtr<Gtk::TextTag> &tag) { + if (tag->property_name() == "gtksourceview:context-classes:comment") + comment_tag = tag; + else if (tag->property_name() == "gtksourceview:context-classes:string") + string_tag = tag; + else if (tag->property_name() == "gtksourceview:context-classes:no-spell-check") + no_spell_check_tag = tag; + }); + signal_tag_removed_connection = get_buffer()->get_tag_table()->signal_tag_removed().connect( + [this](const Glib::RefPtr<Gtk::TextTag> &tag) { + if (tag->property_name() == "gtksourceview:context-classes:comment") + comment_tag.reset(); + else if (tag->property_name() == "gtksourceview:context-classes:string") + string_tag.reset(); + else if (tag->property_name() == "gtksourceview:context-classes:no-spell-check") + no_spell_check_tag.reset(); + }); } Source::SpellCheckView::~SpellCheckView() { - delayed_spellcheck_suggestions_connection.disconnect(); - delayed_spellcheck_error_clear.disconnect(); - - if(spellcheck_checker!=nullptr) - delete_aspell_speller(spellcheck_checker); - - signal_tag_added_connection.disconnect(); - signal_tag_removed_connection.disconnect(); + delayed_spellcheck_suggestions_connection.disconnect(); + delayed_spellcheck_error_clear.disconnect(); + + if (spellcheck_checker != nullptr) + delete_aspell_speller(spellcheck_checker); + + signal_tag_added_connection.disconnect(); + signal_tag_removed_connection.disconnect(); } void Source::SpellCheckView::configure() { - if(Config::get().source.spellcheck_language.size()>0) { - aspell_config_replace(spellcheck_config, "lang", Config::get().source.spellcheck_language.c_str()); - aspell_config_replace(spellcheck_config, "encoding", "utf-8"); - } - spellcheck_possible_err=new_aspell_speller(spellcheck_config); - if(spellcheck_checker!=nullptr) - delete_aspell_speller(spellcheck_checker); - spellcheck_checker=nullptr; - if (aspell_error_number(spellcheck_possible_err) != 0) - std::cerr << "Spell check error: " << aspell_error_message(spellcheck_possible_err) << std::endl; - else - spellcheck_checker = to_aspell_speller(spellcheck_possible_err); - get_buffer()->remove_tag(spellcheck_error_tag, get_buffer()->begin(), get_buffer()->end()); + if (Config::get().source.spellcheck_language.size() > 0) { + aspell_config_replace(spellcheck_config, "lang", Config::get().source.spellcheck_language.c_str()); + aspell_config_replace(spellcheck_config, "encoding", "utf-8"); + } + spellcheck_possible_err = new_aspell_speller(spellcheck_config); + if (spellcheck_checker != nullptr) + delete_aspell_speller(spellcheck_checker); + spellcheck_checker = nullptr; + if (aspell_error_number(spellcheck_possible_err) != 0) + std::cerr << "Spell check error: " << aspell_error_message(spellcheck_possible_err) << std::endl; + else + spellcheck_checker = to_aspell_speller(spellcheck_possible_err); + get_buffer()->remove_tag(spellcheck_error_tag, get_buffer()->begin(), get_buffer()->end()); } void Source::SpellCheckView::hide_dialogs() { - delayed_spellcheck_suggestions_connection.disconnect(); - if(SelectionDialog::get()) - SelectionDialog::get()->hide(); + delayed_spellcheck_suggestions_connection.disconnect(); + if (SelectionDialog::get()) + SelectionDialog::get()->hide(); } -void Source::SpellCheckView::spellcheck(const Gtk::TextIter& start, const Gtk::TextIter& end) { - if(spellcheck_checker==nullptr) - return; - auto iter=start; - while(iter && iter<end) { - if(is_word_iter(iter)) { - auto word=get_word(iter); - spellcheck_word(word.first, word.second); - iter=word.second; +void Source::SpellCheckView::spellcheck(const Gtk::TextIter &start, const Gtk::TextIter &end) { + if (spellcheck_checker == nullptr) + return; + auto iter = start; + while (iter && iter < end) { + if (is_word_iter(iter)) { + auto word = get_word(iter); + spellcheck_word(word.first, word.second); + iter = word.second; + } + iter.forward_char(); } - iter.forward_char(); - } } void Source::SpellCheckView::spellcheck() { - auto iter=get_buffer()->begin(); - Gtk::TextIter begin_spellcheck_iter; - if(spellcheck_all) { - bool spell_check=!get_source_buffer()->iter_has_context_class(iter, "no-spell-check"); - if(spell_check) - begin_spellcheck_iter=iter; - while(iter!=get_buffer()->end()) { - if(!get_source_buffer()->iter_forward_to_context_class_toggle(iter, "no-spell-check")) - iter=get_buffer()->end(); - - spell_check=!spell_check; - if(spell_check) - begin_spellcheck_iter=iter; - else - spellcheck(begin_spellcheck_iter, iter); - } - } - else { - bool spell_check=!is_code_iter(iter); - if(spell_check) - begin_spellcheck_iter=iter; - while(iter!=get_buffer()->end()) { - auto iter1=iter; - auto iter2=iter; - if(!get_source_buffer()->iter_forward_to_context_class_toggle(iter1, "string")) - iter1=get_buffer()->end(); - if(!get_source_buffer()->iter_forward_to_context_class_toggle(iter2, "comment")) - iter2=get_buffer()->end(); - - if(iter2<iter1) - iter=iter2; - else - iter=iter1; - spell_check=!spell_check; - if(spell_check) - begin_spellcheck_iter=iter; - else - spellcheck(begin_spellcheck_iter, iter); + auto iter = get_buffer()->begin(); + Gtk::TextIter begin_spellcheck_iter; + if (spellcheck_all) { + bool spell_check = !get_source_buffer()->iter_has_context_class(iter, "no-spell-check"); + if (spell_check) + begin_spellcheck_iter = iter; + while (iter != get_buffer()->end()) { + if (!get_source_buffer()->iter_forward_to_context_class_toggle(iter, "no-spell-check")) + iter = get_buffer()->end(); + + spell_check = !spell_check; + if (spell_check) + begin_spellcheck_iter = iter; + else + spellcheck(begin_spellcheck_iter, iter); + } + } else { + bool spell_check = !is_code_iter(iter); + if (spell_check) + begin_spellcheck_iter = iter; + while (iter != get_buffer()->end()) { + auto iter1 = iter; + auto iter2 = iter; + if (!get_source_buffer()->iter_forward_to_context_class_toggle(iter1, "string")) + iter1 = get_buffer()->end(); + if (!get_source_buffer()->iter_forward_to_context_class_toggle(iter2, "comment")) + iter2 = get_buffer()->end(); + + if (iter2 < iter1) + iter = iter2; + else + iter = iter1; + spell_check = !spell_check; + if (spell_check) + begin_spellcheck_iter = iter; + else + spellcheck(begin_spellcheck_iter, iter); + } } - } } void Source::SpellCheckView::remove_spellcheck_errors() { - get_buffer()->remove_tag(spellcheck_error_tag, get_buffer()->begin(), get_buffer()->end()); + get_buffer()->remove_tag(spellcheck_error_tag, get_buffer()->begin(), get_buffer()->end()); } void Source::SpellCheckView::goto_next_spellcheck_error() { - auto iter=get_buffer()->get_insert()->get_iter(); - auto insert_iter=iter; - bool wrapped=false; - iter.forward_char(); - while(!wrapped || iter<insert_iter) { - auto toggled_tags=iter.get_toggled_tags(); - for(auto &toggled_tag: toggled_tags) { - if(toggled_tag==spellcheck_error_tag) { - get_buffer()->place_cursor(iter); - scroll_to(get_buffer()->get_insert(), 0.0, 1.0, 0.5); - return; - } - } + auto iter = get_buffer()->get_insert()->get_iter(); + auto insert_iter = iter; + bool wrapped = false; iter.forward_char(); - if(!wrapped && iter==get_buffer()->end()) { - iter=get_buffer()->begin(); - wrapped=true; + while (!wrapped || iter < insert_iter) { + auto toggled_tags = iter.get_toggled_tags(); + for (auto &toggled_tag: toggled_tags) { + if (toggled_tag == spellcheck_error_tag) { + get_buffer()->place_cursor(iter); + scroll_to(get_buffer()->get_insert(), 0.0, 1.0, 0.5); + return; + } + } + iter.forward_char(); + if (!wrapped && iter == get_buffer()->end()) { + iter = get_buffer()->begin(); + wrapped = true; + } } - } - Info::get().print("No spelling errors found in current buffer"); + Info::get().print("No spelling errors found in current buffer"); } bool Source::SpellCheckView::is_code_iter(const Gtk::TextIter &iter) { - if(*iter=='\'') { - auto previous_iter=iter; - if(!iter.starts_line() && previous_iter.backward_char() && *previous_iter=='\'') - return false; - } - if(spellcheck_all) { - if(no_spell_check_tag && (iter.has_tag(no_spell_check_tag) || iter.begins_tag(no_spell_check_tag) || iter.ends_tag(no_spell_check_tag))) - return true; - // workaround for gtksourceview bug - if(iter.ends_line()) { - auto previous_iter=iter; - if(previous_iter.backward_char()) { - if(*previous_iter=='\'' || *previous_iter=='"') { - auto next_iter=iter; - next_iter.forward_char(); - if(next_iter.begins_tag(no_spell_check_tag) || next_iter.is_end()) - return true; - } - } - } - // for example, mark first " as code iter in this case: r"" - if(*iter=='\'' || *iter=='"') { - auto previous_iter=iter; - if(previous_iter.backward_char() && *previous_iter!='\'' && *previous_iter!='\"' && previous_iter.ends_tag(no_spell_check_tag)) - return true; + if (*iter == '\'') { + auto previous_iter = iter; + if (!iter.starts_line() && previous_iter.backward_char() && *previous_iter == '\'') + return false; } - return false; - } - if(comment_tag) { - if(iter.has_tag(comment_tag) && !iter.begins_tag(comment_tag)) - return false; - //Exception at the end of /**/ - else if(iter.ends_tag(comment_tag)) { - auto previous_iter=iter; - if(previous_iter.backward_char() && *previous_iter=='/') { - auto previous_previous_iter=previous_iter; - if(previous_previous_iter.backward_char() && *previous_previous_iter=='*') { - auto it=previous_iter; - while(!it.begins_tag(comment_tag) && it.backward_to_tag_toggle(comment_tag)) {} - auto next_iter=it; - if(it.begins_tag(comment_tag) && next_iter.forward_char() && *it=='/' && *next_iter=='*' && previous_iter!=it) + if (spellcheck_all) { + if (no_spell_check_tag && (iter.has_tag(no_spell_check_tag) || iter.begins_tag(no_spell_check_tag) || + iter.ends_tag(no_spell_check_tag))) return true; + // workaround for gtksourceview bug + if (iter.ends_line()) { + auto previous_iter = iter; + if (previous_iter.backward_char()) { + if (*previous_iter == '\'' || *previous_iter == '"') { + auto next_iter = iter; + next_iter.forward_char(); + if (next_iter.begins_tag(no_spell_check_tag) || next_iter.is_end()) + return true; + } + } } - } - return false; - } - } - if(string_tag) { - if(iter.has_tag(string_tag)) { - // When ending an open ''-string with ', the last '-iter is not correctly marked as end iter for string_tag - // For instance 'test, when inserting ' at end, would lead to spellcheck error of test' - if(*iter=='\'') { - long backslash_count=0; - auto it=iter; - while(it.backward_char() && *it=='\\') - ++backslash_count; - if(backslash_count%2==0) { - auto it=iter; - while(!it.begins_tag(string_tag) && it.backward_to_tag_toggle(string_tag)) {} - if(it.begins_tag(string_tag) && *it=='\'' && iter!=it) - return true; + // for example, mark first " as code iter in this case: r"" + if (*iter == '\'' || *iter == '"') { + auto previous_iter = iter; + if (previous_iter.backward_char() && *previous_iter != '\'' && *previous_iter != '\"' && + previous_iter.ends_tag(no_spell_check_tag)) + return true; } - } - if(!iter.begins_tag(string_tag)) return false; } - // If iter is at the end of string_tag, with exception of after " and ' - else if(iter.ends_tag(string_tag)) { - auto previous_iter=iter; - if(!iter.starts_line() && previous_iter.backward_char()) { - if((*previous_iter=='"' || *previous_iter=='\'')) { - long backslash_count=0; - auto it=previous_iter; - while(it.backward_char() && *it=='\\') - ++backslash_count; - if(backslash_count%2==0) { - auto it=previous_iter; - while(!it.begins_tag(string_tag) && it.backward_to_tag_toggle(string_tag)) {} - if(it.begins_tag(string_tag) && *previous_iter==*it && previous_iter!=it) - return true; - } + if (comment_tag) { + if (iter.has_tag(comment_tag) && !iter.begins_tag(comment_tag)) + return false; + //Exception at the end of /**/ + else if (iter.ends_tag(comment_tag)) { + auto previous_iter = iter; + if (previous_iter.backward_char() && *previous_iter == '/') { + auto previous_previous_iter = previous_iter; + if (previous_previous_iter.backward_char() && *previous_previous_iter == '*') { + auto it = previous_iter; + while (!it.begins_tag(comment_tag) && it.backward_to_tag_toggle(comment_tag)) {} + auto next_iter = it; + if (it.begins_tag(comment_tag) && next_iter.forward_char() && *it == '/' && *next_iter == '*' && + previous_iter != it) + return true; + } + } + return false; } - return false; - } } - } - return true; + if (string_tag) { + if (iter.has_tag(string_tag)) { + // When ending an open ''-string with ', the last '-iter is not correctly marked as end iter for string_tag + // For instance 'test, when inserting ' at end, would lead to spellcheck error of test' + if (*iter == '\'') { + long backslash_count = 0; + auto it = iter; + while (it.backward_char() && *it == '\\') + ++backslash_count; + if (backslash_count % 2 == 0) { + auto it = iter; + while (!it.begins_tag(string_tag) && it.backward_to_tag_toggle(string_tag)) {} + if (it.begins_tag(string_tag) && *it == '\'' && iter != it) + return true; + } + } + if (!iter.begins_tag(string_tag)) + return false; + } + // If iter is at the end of string_tag, with exception of after " and ' + else if (iter.ends_tag(string_tag)) { + auto previous_iter = iter; + if (!iter.starts_line() && previous_iter.backward_char()) { + if ((*previous_iter == '"' || *previous_iter == '\'')) { + long backslash_count = 0; + auto it = previous_iter; + while (it.backward_char() && *it == '\\') + ++backslash_count; + if (backslash_count % 2 == 0) { + auto it = previous_iter; + while (!it.begins_tag(string_tag) && it.backward_to_tag_toggle(string_tag)) {} + if (it.begins_tag(string_tag) && *previous_iter == *it && previous_iter != it) + return true; + } + } + return false; + } + } + } + return true; } -bool Source::SpellCheckView::is_word_iter(const Gtk::TextIter& iter) { - auto previous_iter=iter; - size_t backslash_count=0; - while(previous_iter.backward_char() && *previous_iter=='\\') - ++backslash_count; - if(backslash_count%2==1) +bool Source::SpellCheckView::is_word_iter(const Gtk::TextIter &iter) { + auto previous_iter = iter; + size_t backslash_count = 0; + while (previous_iter.backward_char() && *previous_iter == '\\') + ++backslash_count; + if (backslash_count % 2 == 1) + return false; + if (((*iter >= 'A' && *iter <= 'Z') || (*iter >= 'a' && *iter <= 'z') || *iter >= 128)) + return true; + if (*iter == '\'') { + if (is_code_iter(iter)) + return false; + auto next_iter = iter; + if (next_iter.forward_char() && is_code_iter(next_iter) && + !(comment_tag && iter.ends_tag(comment_tag))) // additional check for end of line comment + return false; + return true; + } return false; - if(((*iter>='A' && *iter<='Z') || (*iter>='a' && *iter<='z') || *iter>=128)) - return true; - if(*iter=='\'') { - if(is_code_iter(iter)) - return false; - auto next_iter=iter; - if(next_iter.forward_char() && is_code_iter(next_iter) && - !(comment_tag && iter.ends_tag(comment_tag))) // additional check for end of line comment - return false; - return true; - } - return false; } std::pair<Gtk::TextIter, Gtk::TextIter> Source::SpellCheckView::get_word(Gtk::TextIter iter) { - auto start=iter; - auto end=iter; - - while(is_word_iter(iter)) { - start=iter; - if(!iter.backward_char()) - break; - } - while(is_word_iter(end)) { - if(!end.forward_char()) - break; - } - - return {start, end}; + auto start = iter; + auto end = iter; + + while (is_word_iter(iter)) { + start = iter; + if (!iter.backward_char()) + break; + } + while (is_word_iter(end)) { + if (!end.forward_char()) + break; + } + + return {start, end}; } void Source::SpellCheckView::spellcheck_word(Gtk::TextIter start, Gtk::TextIter end) { - if(*start=='\'' && end.get_offset()-start.get_offset()>=3) { - auto before_end=end; - if(before_end.backward_char() && *before_end=='\'') { - get_buffer()->remove_tag(spellcheck_error_tag, start, end); - start.forward_char(); - end.backward_char(); + if (*start == '\'' && end.get_offset() - start.get_offset() >= 3) { + auto before_end = end; + if (before_end.backward_char() && *before_end == '\'') { + get_buffer()->remove_tag(spellcheck_error_tag, start, end); + start.forward_char(); + end.backward_char(); + } + } + + auto word = get_buffer()->get_text(start, end); + if (word.size() > 0) { + auto correct = aspell_speller_check(spellcheck_checker, word.data(), word.bytes()); + if (correct == 0) + get_buffer()->apply_tag(spellcheck_error_tag, start, end); + else + get_buffer()->remove_tag(spellcheck_error_tag, start, end); } - } - - auto word=get_buffer()->get_text(start, end); - if(word.size()>0) { - auto correct = aspell_speller_check(spellcheck_checker, word.data(), word.bytes()); - if(correct==0) - get_buffer()->apply_tag(spellcheck_error_tag, start, end); - else - get_buffer()->remove_tag(spellcheck_error_tag, start, end); - } } -std::vector<std::string> Source::SpellCheckView::get_spellcheck_suggestions(const Gtk::TextIter& start, const Gtk::TextIter& end) { - auto word_with_error=get_buffer()->get_text(start, end); - - const AspellWordList *suggestions = aspell_speller_suggest(spellcheck_checker, word_with_error.data(), word_with_error.bytes()); - AspellStringEnumeration *elements = aspell_word_list_elements(suggestions); - - std::vector<std::string> words; - const char *word; - while ((word = aspell_string_enumeration_next(elements))!= nullptr) { - words.emplace_back(word); - } - delete_aspell_string_enumeration(elements); - - return words; +std::vector<std::string> +Source::SpellCheckView::get_spellcheck_suggestions(const Gtk::TextIter &start, const Gtk::TextIter &end) { + auto word_with_error = get_buffer()->get_text(start, end); + + const AspellWordList *suggestions = aspell_speller_suggest(spellcheck_checker, word_with_error.data(), + word_with_error.bytes()); + AspellStringEnumeration *elements = aspell_word_list_elements(suggestions); + + std::vector<std::string> words; + const char *word; + while ((word = aspell_string_enumeration_next(elements)) != nullptr) { + words.emplace_back(word); + } + delete_aspell_string_enumeration(elements); + + return words; } diff --git a/src/source_spellcheck.h b/src/source_spellcheck.h index a320760d..1b6b476e 100644 --- a/src/source_spellcheck.h +++ b/src/source_spellcheck.h @@ -1,44 +1,55 @@ #pragma once + #include "source_base.h" #include <aspell.h> namespace Source { - class SpellCheckView : virtual public Source::BaseView { - public: - SpellCheckView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); - ~SpellCheckView(); - - void configure() override; - void hide_dialogs() override; - - void spellcheck(); - void remove_spellcheck_errors(); - void goto_next_spellcheck_error(); - - protected: - bool is_code_iter(const Gtk::TextIter &iter); - bool spellcheck_all=false; - guint last_keyval=0; - - Glib::RefPtr<Gtk::TextTag> comment_tag; - Glib::RefPtr<Gtk::TextTag> string_tag; - Glib::RefPtr<Gtk::TextTag> no_spell_check_tag; - private: - Glib::RefPtr<Gtk::TextTag> spellcheck_error_tag; - - sigc::connection signal_tag_added_connection; - sigc::connection signal_tag_removed_connection; - - static AspellConfig* spellcheck_config; - AspellCanHaveError *spellcheck_possible_err; - AspellSpeller *spellcheck_checker; - bool is_word_iter(const Gtk::TextIter& iter); - std::pair<Gtk::TextIter, Gtk::TextIter> get_word(Gtk::TextIter iter); - void spellcheck_word(Gtk::TextIter start, Gtk::TextIter end); - std::vector<std::string> get_spellcheck_suggestions(const Gtk::TextIter& start, const Gtk::TextIter& end); - sigc::connection delayed_spellcheck_suggestions_connection; - sigc::connection delayed_spellcheck_error_clear; - - void spellcheck(const Gtk::TextIter& start, const Gtk::TextIter& end); - }; + class SpellCheckView : virtual public Source::BaseView { + public: + SpellCheckView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); + + ~SpellCheckView(); + + void configure() override; + + void hide_dialogs() override; + + void spellcheck(); + + void remove_spellcheck_errors(); + + void goto_next_spellcheck_error(); + + protected: + bool is_code_iter(const Gtk::TextIter &iter); + + bool spellcheck_all = false; + guint last_keyval = 0; + + Glib::RefPtr<Gtk::TextTag> comment_tag; + Glib::RefPtr<Gtk::TextTag> string_tag; + Glib::RefPtr<Gtk::TextTag> no_spell_check_tag; + private: + Glib::RefPtr<Gtk::TextTag> spellcheck_error_tag; + + sigc::connection signal_tag_added_connection; + sigc::connection signal_tag_removed_connection; + + static AspellConfig *spellcheck_config; + AspellCanHaveError *spellcheck_possible_err; + AspellSpeller *spellcheck_checker; + + bool is_word_iter(const Gtk::TextIter &iter); + + std::pair<Gtk::TextIter, Gtk::TextIter> get_word(Gtk::TextIter iter); + + void spellcheck_word(Gtk::TextIter start, Gtk::TextIter end); + + std::vector<std::string> get_spellcheck_suggestions(const Gtk::TextIter &start, const Gtk::TextIter &end); + + sigc::connection delayed_spellcheck_suggestions_connection; + sigc::connection delayed_spellcheck_error_clear; + + void spellcheck(const Gtk::TextIter &start, const Gtk::TextIter &end); + }; } diff --git a/src/terminal.cc b/src/terminal.cc index 909c37dd..ab285590 100644 --- a/src/terminal.cc +++ b/src/terminal.cc @@ -9,427 +9,426 @@ #include <thread> Terminal::Terminal() { - set_editable(false); - - bold_tag=get_buffer()->create_tag(); - bold_tag->property_weight()=Pango::WEIGHT_ULTRAHEAVY; - - link_tag=get_buffer()->create_tag(); - link_tag->property_underline()=Pango::Underline::UNDERLINE_SINGLE; - - link_mouse_cursor=Gdk::Cursor::create(Gdk::CursorType::HAND1); - default_mouse_cursor=Gdk::Cursor::create(Gdk::CursorType::XTERM); -} + set_editable(false); -int Terminal::process(const std::string &command, const boost::filesystem::path &path, bool use_pipes) { - std::unique_ptr<TinyProcessLib::Process> process; - if(use_pipes) - process=std::make_unique<TinyProcessLib::Process>(command, path.string(), [this](const char* bytes, size_t n) { - async_print(std::string(bytes, n)); - }, [this](const char* bytes, size_t n) { - async_print(std::string(bytes, n), true); - }); - else - process=std::make_unique<TinyProcessLib::Process>(command, path.string()); - - if(process->get_id()<=0) { - async_print("Error: failed to run command: " + command + "\n", true); - return -1; - } - - return process->get_exit_status(); + bold_tag = get_buffer()->create_tag(); + bold_tag->property_weight() = Pango::WEIGHT_ULTRAHEAVY; + + link_tag = get_buffer()->create_tag(); + link_tag->property_underline() = Pango::Underline::UNDERLINE_SINGLE; + + link_mouse_cursor = Gdk::Cursor::create(Gdk::CursorType::HAND1); + default_mouse_cursor = Gdk::Cursor::create(Gdk::CursorType::XTERM); } -int Terminal::process(std::istream &stdin_stream, std::ostream &stdout_stream, const std::string &command, const boost::filesystem::path &path, std::ostream *stderr_stream) { - TinyProcessLib::Process process(command, path.string(), [&stdout_stream](const char* bytes, size_t n) { - Glib::ustring umessage(std::string(bytes, n)); - Glib::ustring::iterator iter; - while(!umessage.validate(iter)) { - auto next_char_iter=iter; - next_char_iter++; - umessage.replace(iter, next_char_iter, "?"); - } - stdout_stream.write(umessage.data(), n); - }, [this, stderr_stream](const char* bytes, size_t n) { - if(stderr_stream) - stderr_stream->write(bytes, n); +int Terminal::process(const std::string &command, const boost::filesystem::path &path, bool use_pipes) { + std::unique_ptr<TinyProcessLib::Process> process; + if (use_pipes) + process = std::make_unique<TinyProcessLib::Process>(command, path.string(), + [this](const char *bytes, size_t n) { + async_print(std::string(bytes, n)); + }, [this](const char *bytes, size_t n) { + async_print(std::string(bytes, n), true); + }); else - async_print(std::string(bytes, n), true); - }, true); - - if(process.get_id()<=0) { - async_print("Error: failed to run command: " + command + "\n", true); - return -1; - } - - char buffer[131072]; - for(;;) { - stdin_stream.readsome(buffer, 131072); - auto read_n=stdin_stream.gcount(); - if(read_n==0) - break; - if(!process.write(buffer, read_n)) { - break; + process = std::make_unique<TinyProcessLib::Process>(command, path.string()); + + if (process->get_id() <= 0) { + async_print("Error: failed to run command: " + command + "\n", true); + return -1; } - } - process.close_stdin(); - - return process.get_exit_status(); + + return process->get_exit_status(); } -void Terminal::async_process(const std::string &command, const boost::filesystem::path &path, std::function<void(int exit_status)> callback, bool quiet) { - std::thread async_execute_thread([this, command, path, callback, quiet]() { - std::unique_lock<std::mutex> processes_lock(processes_mutex); - stdin_buffer.clear(); - auto process=std::make_shared<TinyProcessLib::Process>(command, path.string(), [this, quiet](const char* bytes, size_t n) { - if(!quiet) - async_print(std::string(bytes, n)); - }, [this, quiet](const char* bytes, size_t n) { - if(!quiet) - async_print(std::string(bytes, n), true); +int Terminal::process(std::istream &stdin_stream, std::ostream &stdout_stream, const std::string &command, + const boost::filesystem::path &path, std::ostream *stderr_stream) { + TinyProcessLib::Process process(command, path.string(), [&stdout_stream](const char *bytes, size_t n) { + Glib::ustring umessage(std::string(bytes, n)); + Glib::ustring::iterator iter; + while (!umessage.validate(iter)) { + auto next_char_iter = iter; + next_char_iter++; + umessage.replace(iter, next_char_iter, "?"); + } + stdout_stream.write(umessage.data(), n); + }, [this, stderr_stream](const char *bytes, size_t n) { + if (stderr_stream) + stderr_stream->write(bytes, n); + else + async_print(std::string(bytes, n), true); }, true); - auto pid=process->get_id(); - if (pid<=0) { - processes_lock.unlock(); - async_print("Error: failed to run command: " + command + "\n", true); - if(callback) - callback(-1); - return; - } - else { - processes.emplace_back(process); - processes_lock.unlock(); + + if (process.get_id() <= 0) { + async_print("Error: failed to run command: " + command + "\n", true); + return -1; } - - auto exit_status=process->get_exit_status(); - - processes_lock.lock(); - for(auto it=processes.begin();it!=processes.end();it++) { - if((*it)->get_id()==pid) { - processes.erase(it); - break; - } + + char buffer[131072]; + for (;;) { + stdin_stream.readsome(buffer, 131072); + auto read_n = stdin_stream.gcount(); + if (read_n == 0) + break; + if (!process.write(buffer, read_n)) { + break; + } } - stdin_buffer.clear(); - processes_lock.unlock(); - - if(callback) - callback(exit_status); - }); - async_execute_thread.detach(); + process.close_stdin(); + + return process.get_exit_status(); +} + +void Terminal::async_process(const std::string &command, const boost::filesystem::path &path, + std::function<void(int exit_status)> callback, bool quiet) { + std::thread async_execute_thread([this, command, path, callback, quiet]() { + std::unique_lock<std::mutex> processes_lock(processes_mutex); + stdin_buffer.clear(); + auto process = std::make_shared<TinyProcessLib::Process>(command, path.string(), + [this, quiet](const char *bytes, size_t n) { + if (!quiet) + async_print(std::string(bytes, n)); + }, [this, quiet](const char *bytes, size_t n) { + if (!quiet) + async_print(std::string(bytes, n), true); + }, true); + auto pid = process->get_id(); + if (pid <= 0) { + processes_lock.unlock(); + async_print("Error: failed to run command: " + command + "\n", true); + if (callback) + callback(-1); + return; + } else { + processes.emplace_back(process); + processes_lock.unlock(); + } + + auto exit_status = process->get_exit_status(); + + processes_lock.lock(); + for (auto it = processes.begin(); it != processes.end(); it++) { + if ((*it)->get_id() == pid) { + processes.erase(it); + break; + } + } + stdin_buffer.clear(); + processes_lock.unlock(); + + if (callback) + callback(exit_status); + }); + async_execute_thread.detach(); } void Terminal::kill_last_async_process(bool force) { - std::unique_lock<std::mutex> lock(processes_mutex); - if(processes.empty()) - Info::get().print("No running processes"); - else - processes.back()->kill(force); + std::unique_lock<std::mutex> lock(processes_mutex); + if (processes.empty()) + Info::get().print("No running processes"); + else + processes.back()->kill(force); } void Terminal::kill_async_processes(bool force) { - std::unique_lock<std::mutex> lock(processes_mutex); - for(auto &process: processes) - process->kill(force); + std::unique_lock<std::mutex> lock(processes_mutex); + for (auto &process: processes) + process->kill(force); } bool Terminal::on_motion_notify_event(GdkEventMotion *event) { - Gtk::TextIter iter; - int location_x, location_y; - window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, event->x, event->y, location_x, location_y); - get_iter_at_location(iter, location_x, location_y); - if(iter.has_tag(link_tag)) - get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->set_cursor(link_mouse_cursor); - else - get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->set_cursor(default_mouse_cursor); - - // Workaround for drag-and-drop crash on MacOS - // TODO 2018: check if this bug has been fixed -#ifdef __APPLE__ - if((event->state & GDK_BUTTON1_MASK) == 0) - return Gtk::TextView::on_motion_notify_event(event); - else { - int x, y; - window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, event->x, event->y, x, y); Gtk::TextIter iter; - get_iter_at_location(iter, x, y); - get_buffer()->select_range(get_buffer()->get_insert()->get_iter(), iter); - return true; - } + int location_x, location_y; + window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, event->x, event->y, location_x, location_y); + get_iter_at_location(iter, location_x, location_y); + if (iter.has_tag(link_tag)) + get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->set_cursor(link_mouse_cursor); + else + get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->set_cursor(default_mouse_cursor); + + // Workaround for drag-and-drop crash on MacOS + // TODO 2018: check if this bug has been fixed +#ifdef __APPLE__ + if((event->state & GDK_BUTTON1_MASK) == 0) + return Gtk::TextView::on_motion_notify_event(event); + else { + int x, y; + window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, event->x, event->y, x, y); + Gtk::TextIter iter; + get_iter_at_location(iter, x, y); + get_buffer()->select_range(get_buffer()->get_insert()->get_iter(), iter); + return true; + } #else - return Gtk::TextView::on_motion_notify_event(event); + return Gtk::TextView::on_motion_notify_event(event); #endif - - return Gtk::TextView::on_motion_notify_event(event); + + return Gtk::TextView::on_motion_notify_event(event); } std::tuple<size_t, size_t, std::string, std::string, std::string> Terminal::find_link(const std::string &line) { - const static std::regex link_regex("^([A-Z]:)?([^:]+):([0-9]+):([0-9]+): .*$|" //compile warning/error/rename usages - "^Assertion failed: .*file ([A-Z]:)?([^:]+), line ([0-9]+)\\.$|" //clang assert() - "^[^:]*: ([A-Z]:)?([^:]+):([0-9]+): .* Assertion .* failed\\.$|" //gcc assert() - "^ERROR:([A-Z]:)?([^:]+):([0-9]+):.*$"); //g_assert (glib.h) - size_t start_position=-1, end_position=-1; - std::string path, line_number, line_offset; - std::smatch sm; - if(std::regex_match(line, sm, link_regex)) { - for(size_t sub=1;sub<link_regex.mark_count();) { - size_t subs=sub==1?4:3; - if(sm.length(sub+1)) { - start_position=sm.position(sub+1)-sm.length(sub); - end_position=sm.position(sub+subs-1)+sm.length(sub+subs-1); - if(sm.length(sub)) - path+=sm[sub].str(); - path+=sm[sub+1].str(); - line_number=sm[sub+2].str(); - line_offset=subs==4?sm[sub+3].str():"1"; - break; - } - sub+=subs; + const static std::regex link_regex("^([A-Z]:)?([^:]+):([0-9]+):([0-9]+): .*$|" //compile warning/error/rename usages + "^Assertion failed: .*file ([A-Z]:)?([^:]+), line ([0-9]+)\\.$|" //clang assert() + "^[^:]*: ([A-Z]:)?([^:]+):([0-9]+): .* Assertion .* failed\\.$|" //gcc assert() + "^ERROR:([A-Z]:)?([^:]+):([0-9]+):.*$"); //g_assert (glib.h) + size_t start_position = -1, end_position = -1; + std::string path, line_number, line_offset; + std::smatch sm; + if (std::regex_match(line, sm, link_regex)) { + for (size_t sub = 1; sub < link_regex.mark_count();) { + size_t subs = sub == 1 ? 4 : 3; + if (sm.length(sub + 1)) { + start_position = sm.position(sub + 1) - sm.length(sub); + end_position = sm.position(sub + subs - 1) + sm.length(sub + subs - 1); + if (sm.length(sub)) + path += sm[sub].str(); + path += sm[sub + 1].str(); + line_number = sm[sub + 2].str(); + line_offset = subs == 4 ? sm[sub + 3].str() : "1"; + break; + } + sub += subs; + } } - } - return std::make_tuple(start_position, end_position, path, line_number, line_offset); + return std::make_tuple(start_position, end_position, path, line_number, line_offset); } void Terminal::apply_link_tags(Gtk::TextIter start_iter, Gtk::TextIter end_iter) { - auto iter=start_iter; - Gtk::TextIter line_start; - bool line_start_set=false; - bool delimiter_found=false; - bool dot_found=false; - bool number_found=false; - do { - if(iter.starts_line()) { - line_start=iter; - line_start_set=true; - delimiter_found=false; - dot_found=false; - number_found=false; - } - if(line_start_set && (*iter=='\\' || *iter=='/')) - delimiter_found=true; - else if(line_start_set && *iter=='.') - dot_found=true; - else if(line_start_set && (*iter>='0' && *iter<='9')) - number_found=true; - else if(line_start_set && delimiter_found && dot_found && number_found && iter.ends_line()) { - auto line=get_buffer()->get_text(line_start, iter); - //Convert to ascii for std::regex and Gtk::Iter::forward_chars - for(size_t c=0;c<line.size();++c) { - if(line[c]>127) - line.replace(c, 1, "a"); - } - auto link=find_link(line.raw()); - if(std::get<0>(link)!=static_cast<size_t>(-1)) { - auto link_start=line_start; - auto link_end=line_start; - link_start.forward_chars(std::get<0>(link)); - link_end.forward_chars(std::get<1>(link)); - get_buffer()->apply_tag(link_tag, link_start, link_end); - } - line_start_set=false; - } - } while(iter.forward_char() && iter!=end_iter); + auto iter = start_iter; + Gtk::TextIter line_start; + bool line_start_set = false; + bool delimiter_found = false; + bool dot_found = false; + bool number_found = false; + do { + if (iter.starts_line()) { + line_start = iter; + line_start_set = true; + delimiter_found = false; + dot_found = false; + number_found = false; + } + if (line_start_set && (*iter == '\\' || *iter == '/')) + delimiter_found = true; + else if (line_start_set && *iter == '.') + dot_found = true; + else if (line_start_set && (*iter >= '0' && *iter <= '9')) + number_found = true; + else if (line_start_set && delimiter_found && dot_found && number_found && iter.ends_line()) { + auto line = get_buffer()->get_text(line_start, iter); + //Convert to ascii for std::regex and Gtk::Iter::forward_chars + for (size_t c = 0; c < line.size(); ++c) { + if (line[c] > 127) + line.replace(c, 1, "a"); + } + auto link = find_link(line.raw()); + if (std::get<0>(link) != static_cast<size_t>(-1)) { + auto link_start = line_start; + auto link_end = line_start; + link_start.forward_chars(std::get<0>(link)); + link_end.forward_chars(std::get<1>(link)); + get_buffer()->apply_tag(link_tag, link_start, link_end); + } + line_start_set = false; + } + } while (iter.forward_char() && iter != end_iter); } -size_t Terminal::print(const std::string &message, bool bold){ +size_t Terminal::print(const std::string &message, bool bold) { #ifdef _WIN32 - //Remove color codes - auto message_no_color=message; //copy here since operations on Glib::ustring is too slow - size_t pos=0; - while((pos=message_no_color.find('\e', pos))!=std::string::npos) { - if((pos+2)>=message_no_color.size()) - break; - if(message_no_color[pos+1]=='[') { - size_t end_pos=pos+2; - bool color_code_found=false; - while(end_pos<message_no_color.size()) { - if((message_no_color[end_pos]>='0' && message_no_color[end_pos]<='9') || message_no_color[end_pos]==';') - end_pos++; - else if(message_no_color[end_pos]=='m') { - color_code_found=true; - break; + //Remove color codes + auto message_no_color = message; //copy here since operations on Glib::ustring is too slow + size_t pos = 0; + while ((pos = message_no_color.find('\e', pos)) != std::string::npos) { + if ((pos + 2) >= message_no_color.size()) + break; + if (message_no_color[pos + 1] == '[') { + size_t end_pos = pos + 2; + bool color_code_found = false; + while (end_pos < message_no_color.size()) { + if ((message_no_color[end_pos] >= '0' && message_no_color[end_pos] <= '9') || + message_no_color[end_pos] == ';') + end_pos++; + else if (message_no_color[end_pos] == 'm') { + color_code_found = true; + break; + } else + break; + } + if (color_code_found) + message_no_color.erase(pos, end_pos - pos + 1); } - else - break; - } - if(color_code_found) - message_no_color.erase(pos, end_pos-pos+1); } - } - Glib::ustring umessage=message_no_color; + Glib::ustring umessage = message_no_color; #else - Glib::ustring umessage=message; + Glib::ustring umessage=message; #endif - - Glib::ustring::iterator iter; - while(!umessage.validate(iter)) { - auto next_char_iter=iter; - next_char_iter++; - umessage.replace(iter, next_char_iter, "?"); - } - - auto start_mark=get_buffer()->create_mark(get_buffer()->get_iter_at_line(get_buffer()->end().get_line())); - if(bold) - get_buffer()->insert_with_tag(get_buffer()->end(), umessage, bold_tag); - else - get_buffer()->insert(get_buffer()->end(), umessage); - auto start_iter=start_mark->get_iter(); - get_buffer()->delete_mark(start_mark); - auto end_iter=get_buffer()->get_insert()->get_iter(); - - apply_link_tags(start_iter, end_iter); - - if(get_buffer()->get_line_count()>Config::get().terminal.history_size) { - int lines=get_buffer()->get_line_count()-Config::get().terminal.history_size; - get_buffer()->erase(get_buffer()->begin(), get_buffer()->get_iter_at_line(lines)); - deleted_lines+=static_cast<size_t>(lines); - } - - return static_cast<size_t>(get_buffer()->end().get_line())+deleted_lines; + + Glib::ustring::iterator iter; + while (!umessage.validate(iter)) { + auto next_char_iter = iter; + next_char_iter++; + umessage.replace(iter, next_char_iter, "?"); + } + + auto start_mark = get_buffer()->create_mark(get_buffer()->get_iter_at_line(get_buffer()->end().get_line())); + if (bold) + get_buffer()->insert_with_tag(get_buffer()->end(), umessage, bold_tag); + else + get_buffer()->insert(get_buffer()->end(), umessage); + auto start_iter = start_mark->get_iter(); + get_buffer()->delete_mark(start_mark); + auto end_iter = get_buffer()->get_insert()->get_iter(); + + apply_link_tags(start_iter, end_iter); + + if (get_buffer()->get_line_count() > Config::get().terminal.history_size) { + int lines = get_buffer()->get_line_count() - Config::get().terminal.history_size; + get_buffer()->erase(get_buffer()->begin(), get_buffer()->get_iter_at_line(lines)); + deleted_lines += static_cast<size_t>(lines); + } + + return static_cast<size_t>(get_buffer()->end().get_line()) + deleted_lines; } void Terminal::async_print(const std::string &message, bool bold) { - dispatcher.post([message, bold] { - Terminal::get().print(message, bold); - }); + dispatcher.post([message, bold] { + Terminal::get().print(message, bold); + }); } void Terminal::async_print(size_t line_nr, const std::string &message) { - dispatcher.post([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); - }); + dispatcher.post([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); + }); } void Terminal::configure() { - link_tag->property_foreground_rgba()=get_style_context()->get_color(Gtk::StateFlags::STATE_FLAG_LINK); - - if(Config::get().terminal.font.size()>0) { - override_font(Pango::FontDescription(Config::get().terminal.font)); - } - else if(Config::get().source.font.size()>0) { - Pango::FontDescription font_description(Config::get().source.font); - auto font_description_size=font_description.get_size(); - if(font_description_size==0) { - Pango::FontDescription default_font_description(Gtk::Settings::get_default()->property_gtk_font_name()); - font_description_size=default_font_description.get_size(); + link_tag->property_foreground_rgba() = get_style_context()->get_color(Gtk::StateFlags::STATE_FLAG_LINK); + + if (Config::get().terminal.font.size() > 0) { + override_font(Pango::FontDescription(Config::get().terminal.font)); + } else if (Config::get().source.font.size() > 0) { + Pango::FontDescription font_description(Config::get().source.font); + auto font_description_size = font_description.get_size(); + if (font_description_size == 0) { + Pango::FontDescription default_font_description(Gtk::Settings::get_default()->property_gtk_font_name()); + font_description_size = default_font_description.get_size(); + } + if (font_description_size > 0) + font_description.set_size(font_description_size * 0.95); + override_font(font_description); } - if(font_description_size>0) - font_description.set_size(font_description_size*0.95); - override_font(font_description); - } } void Terminal::clear() { - get_buffer()->set_text(""); + get_buffer()->set_text(""); } -bool Terminal::on_button_press_event(GdkEventButton* button_event) { - //open clicked link in terminal - if(button_event->type==GDK_BUTTON_PRESS && button_event->button==GDK_BUTTON_PRIMARY) { - Gtk::TextIter iter; - int location_x, location_y; - window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, button_event->x, button_event->y, location_x, location_y); - get_iter_at_location(iter, location_x, location_y); - if(iter.has_tag(link_tag)) { - auto start_iter=get_buffer()->get_iter_at_line(iter.get_line()); - auto end_iter=start_iter; - while(!end_iter.ends_line() && end_iter.forward_char()) {} - auto link=find_link(get_buffer()->get_text(start_iter, end_iter).raw()); - if(std::get<0>(link)!=static_cast<size_t>(-1)) { - boost::filesystem::path path=std::get<2>(link); - std::string line=std::get<3>(link); - std::string index=std::get<4>(link); - - if(!path.empty() && *path.begin()=="~") { // boost::filesystem does not recognize ~ - boost::filesystem::path corrected_path; - corrected_path=filesystem::get_home_path(); - if(!corrected_path.empty()) { - auto it=path.begin(); - ++it; - for(;it!=path.end();++it) - corrected_path/=*it; - path=corrected_path; - } - } - - if(path.is_relative()) { - if(Project::current) { - auto absolute_path=Project::current->build->get_default_path()/path; - if(boost::filesystem::exists(absolute_path)) - path=absolute_path; - else - path=Project::current->build->get_debug_path()/path; - } - else - return Gtk::TextView::on_button_press_event(button_event); - } - if(boost::filesystem::is_regular_file(path)) { - Notebook::get().open(path); - if(auto view=Notebook::get().get_current_view()) { - try { - int line_int = std::stoi(line)-1; - int index_int = std::stoi(index)-1; - view->place_cursor_at_line_index(line_int, index_int); - view->scroll_to_cursor_delayed(view, true, true); - return true; +bool Terminal::on_button_press_event(GdkEventButton *button_event) { + //open clicked link in terminal + if (button_event->type == GDK_BUTTON_PRESS && button_event->button == GDK_BUTTON_PRIMARY) { + Gtk::TextIter iter; + int location_x, location_y; + window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, button_event->x, button_event->y, location_x, + location_y); + get_iter_at_location(iter, location_x, location_y); + if (iter.has_tag(link_tag)) { + auto start_iter = get_buffer()->get_iter_at_line(iter.get_line()); + auto end_iter = start_iter; + while (!end_iter.ends_line() && end_iter.forward_char()) {} + auto link = find_link(get_buffer()->get_text(start_iter, end_iter).raw()); + if (std::get<0>(link) != static_cast<size_t>(-1)) { + boost::filesystem::path path = std::get<2>(link); + std::string line = std::get<3>(link); + std::string index = std::get<4>(link); + + if (!path.empty() && *path.begin() == "~") { // boost::filesystem does not recognize ~ + boost::filesystem::path corrected_path; + corrected_path = filesystem::get_home_path(); + if (!corrected_path.empty()) { + auto it = path.begin(); + ++it; + for (; it != path.end(); ++it) + corrected_path /= *it; + path = corrected_path; + } + } + + if (path.is_relative()) { + if (Project::current) { + auto absolute_path = Project::current->build->get_default_path() / path; + if (boost::filesystem::exists(absolute_path)) + path = absolute_path; + else + path = Project::current->build->get_debug_path() / path; + } else + return Gtk::TextView::on_button_press_event(button_event); + } + if (boost::filesystem::is_regular_file(path)) { + Notebook::get().open(path); + if (auto view = Notebook::get().get_current_view()) { + try { + int line_int = std::stoi(line) - 1; + int index_int = std::stoi(index) - 1; + view->place_cursor_at_line_index(line_int, index_int); + view->scroll_to_cursor_delayed(view, true, true); + return true; + } + catch (...) {} + } + } } - catch(...) {} - } } - } } - } - return Gtk::TextView::on_button_press_event(button_event); + return Gtk::TextView::on_button_press_event(button_event); } bool Terminal::on_key_press_event(GdkEventKey *event) { - std::unique_lock<std::mutex> lock(processes_mutex); - bool debug_is_running=false; + std::unique_lock<std::mutex> lock(processes_mutex); + bool debug_is_running = false; #ifdef JUCI_ENABLE_DEBUG - debug_is_running=Project::current?Project::current->debug_is_running():false; + debug_is_running=Project::current?Project::current->debug_is_running():false; #endif - if(processes.size()>0 || debug_is_running) { - auto unicode=gdk_keyval_to_unicode(event->keyval); - if(unicode>=32 && unicode!=126 && unicode!=0) { - get_buffer()->place_cursor(get_buffer()->end()); - stdin_buffer+=unicode; - get_buffer()->insert_at_cursor(stdin_buffer.substr(stdin_buffer.size()-1)); - } - else if(event->keyval==GDK_KEY_BackSpace) { - get_buffer()->place_cursor(get_buffer()->end()); - if(stdin_buffer.size()>0 && get_buffer()->get_char_count()>0) { - auto iter=get_buffer()->end(); - iter--; - stdin_buffer.erase(stdin_buffer.size()-1); - get_buffer()->erase(iter, get_buffer()->end()); - } - } - else if(event->keyval==GDK_KEY_Return || event->keyval==GDK_KEY_KP_Enter) { - get_buffer()->place_cursor(get_buffer()->end()); - stdin_buffer+='\n'; - if(debug_is_running) { + if (processes.size() > 0 || debug_is_running) { + auto unicode = gdk_keyval_to_unicode(event->keyval); + if (unicode >= 32 && unicode != 126 && unicode != 0) { + get_buffer()->place_cursor(get_buffer()->end()); + stdin_buffer += unicode; + get_buffer()->insert_at_cursor(stdin_buffer.substr(stdin_buffer.size() - 1)); + } else if (event->keyval == GDK_KEY_BackSpace) { + get_buffer()->place_cursor(get_buffer()->end()); + if (stdin_buffer.size() > 0 && get_buffer()->get_char_count() > 0) { + auto iter = get_buffer()->end(); + iter--; + stdin_buffer.erase(stdin_buffer.size() - 1); + get_buffer()->erase(iter, get_buffer()->end()); + } + } else if (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) { + get_buffer()->place_cursor(get_buffer()->end()); + stdin_buffer += '\n'; + if (debug_is_running) { #ifdef JUCI_ENABLE_DEBUG - Project::current->debug_write(stdin_buffer); + Project::current->debug_write(stdin_buffer); #endif - } - else - processes.back()->write(stdin_buffer); - get_buffer()->insert_at_cursor(stdin_buffer.substr(stdin_buffer.size()-1)); - stdin_buffer.clear(); + } else + processes.back()->write(stdin_buffer); + get_buffer()->insert_at_cursor(stdin_buffer.substr(stdin_buffer.size() - 1)); + stdin_buffer.clear(); + } } - } - return true; + return true; } diff --git a/src/terminal.h b/src/terminal.h index 8499cbd1..88eb03df 100644 --- a/src/terminal.h +++ b/src/terminal.h @@ -1,4 +1,5 @@ #pragma once + #include <mutex> #include <functional> #include "gtkmm.h" @@ -9,42 +10,56 @@ #include <tuple> class Terminal : public Gtk::TextView { - Terminal(); + Terminal(); + public: - static Terminal &get() { - static Terminal singleton; - return singleton; - } - - int process(const std::string &command, const boost::filesystem::path &path="", bool use_pipes=true); - int process(std::istream &stdin_stream, std::ostream &stdout_stream, const std::string &command, const boost::filesystem::path &path="", std::ostream *stderr_stream=nullptr); - void async_process(const std::string &command, const boost::filesystem::path &path="", std::function<void(int exit_status)> callback=nullptr, bool quiet=false); - void kill_last_async_process(bool force=false); - void kill_async_processes(bool force=false); - - size_t print(const std::string &message, bool bold=false); - void async_print(const std::string &message, bool bold=false); - void async_print(size_t line_nr, const std::string &message); - - void configure(); - - void clear(); + static Terminal &get() { + static Terminal singleton; + return singleton; + } + + int process(const std::string &command, const boost::filesystem::path &path = "", bool use_pipes = true); + + int process(std::istream &stdin_stream, std::ostream &stdout_stream, const std::string &command, + const boost::filesystem::path &path = "", std::ostream *stderr_stream = nullptr); + + void async_process(const std::string &command, const boost::filesystem::path &path = "", + std::function<void(int exit_status)> callback = nullptr, bool quiet = false); + + void kill_last_async_process(bool force = false); + + void kill_async_processes(bool force = false); + + size_t print(const std::string &message, bool bold = false); + + void async_print(const std::string &message, bool bold = false); + + void async_print(size_t line_nr, const std::string &message); + + void configure(); + + void clear(); + protected: - bool on_motion_notify_event (GdkEventMotion* motion_event) override; - bool on_button_press_event(GdkEventButton* button_event) override; - bool on_key_press_event(GdkEventKey *event) override; + bool on_motion_notify_event(GdkEventMotion *motion_event) override; + + bool on_button_press_event(GdkEventButton *button_event) override; + + bool on_key_press_event(GdkEventKey *event) override; + private: - Dispatcher dispatcher; - Glib::RefPtr<Gtk::TextTag> bold_tag; - Glib::RefPtr<Gtk::TextTag> link_tag; - Glib::RefPtr<Gdk::Cursor> link_mouse_cursor; - Glib::RefPtr<Gdk::Cursor> default_mouse_cursor; - size_t deleted_lines=0; - - std::tuple<size_t, size_t, std::string, std::string, std::string> find_link(const std::string &line); - void apply_link_tags(Gtk::TextIter start_iter, Gtk::TextIter end_iter); - - std::vector<std::shared_ptr<TinyProcessLib::Process>> processes; - std::mutex processes_mutex; - Glib::ustring stdin_buffer; + Dispatcher dispatcher; + Glib::RefPtr<Gtk::TextTag> bold_tag; + Glib::RefPtr<Gtk::TextTag> link_tag; + Glib::RefPtr<Gdk::Cursor> link_mouse_cursor; + Glib::RefPtr<Gdk::Cursor> default_mouse_cursor; + size_t deleted_lines = 0; + + std::tuple<size_t, size_t, std::string, std::string, std::string> find_link(const std::string &line); + + void apply_link_tags(Gtk::TextIter start_iter, Gtk::TextIter end_iter); + + std::vector<std::shared_ptr<TinyProcessLib::Process>> processes; + std::mutex processes_mutex; + Glib::ustring stdin_buffer; }; diff --git a/src/tooltips.cc b/src/tooltips.cc index 7e612217..22437cba 100644 --- a/src/tooltips.cc +++ b/src/tooltips.cc @@ -1,245 +1,254 @@ #include "tooltips.h" #include "selection_dialog.h" -std::set<Tooltip*> Tooltips::shown_tooltips; -Gdk::Rectangle Tooltips::drawn_tooltips_rectangle=Gdk::Rectangle(); +std::set<Tooltip *> Tooltips::shown_tooltips; +Gdk::Rectangle Tooltips::drawn_tooltips_rectangle = Gdk::Rectangle(); -Tooltip::Tooltip(std::function<Glib::RefPtr<Gtk::TextBuffer>()> create_tooltip_buffer, Gtk::TextView *text_view, -Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, Glib::RefPtr<Gtk::TextBuffer::Mark> end_mark) - : start_mark(start_mark), end_mark(end_mark), create_tooltip_buffer(create_tooltip_buffer), text_view(text_view) {} +Tooltip::Tooltip(std::function<Glib::RefPtr<Gtk::TextBuffer>()> create_tooltip_buffer, Gtk::TextView *text_view, + Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, Glib::RefPtr<Gtk::TextBuffer::Mark> end_mark) + : start_mark(start_mark), end_mark(end_mark), create_tooltip_buffer(create_tooltip_buffer), + text_view(text_view) {} Tooltip::~Tooltip() { - Tooltips::shown_tooltips.erase(this); - if(text_view) { - text_view->get_buffer()->delete_mark(start_mark); - text_view->get_buffer()->delete_mark(end_mark); - } + Tooltips::shown_tooltips.erase(this); + if (text_view) { + text_view->get_buffer()->delete_mark(start_mark); + text_view->get_buffer()->delete_mark(end_mark); + } } void Tooltip::update() { - if(text_view) { - auto iter=start_mark->get_iter(); - auto end_iter=end_mark->get_iter(); - text_view->get_iter_location(iter, activation_rectangle); - if(iter.get_offset()<end_iter.get_offset()) { - while(iter.forward_char() && iter!=end_iter) { - Gdk::Rectangle rectangle; - text_view->get_iter_location(iter, rectangle); - activation_rectangle.join(rectangle); - } + if (text_view) { + auto iter = start_mark->get_iter(); + auto end_iter = end_mark->get_iter(); + text_view->get_iter_location(iter, activation_rectangle); + if (iter.get_offset() < end_iter.get_offset()) { + while (iter.forward_char() && iter != end_iter) { + Gdk::Rectangle rectangle; + text_view->get_iter_location(iter, rectangle); + activation_rectangle.join(rectangle); + } + } + int location_window_x, location_window_y; + text_view->buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, activation_rectangle.get_x(), + activation_rectangle.get_y(), location_window_x, location_window_y); + activation_rectangle.set_x(location_window_x); + activation_rectangle.set_y(location_window_y); } - int location_window_x, location_window_y; - text_view->buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, activation_rectangle.get_x(), activation_rectangle.get_y(), location_window_x, location_window_y); - activation_rectangle.set_x(location_window_x); - activation_rectangle.set_y(location_window_y); - } } void Tooltip::show(bool disregard_drawn, const std::function<void()> &on_motion) { - Tooltips::shown_tooltips.emplace(this); - - if(!window) { - //init window - window=std::make_unique<Gtk::Window>(Gtk::WindowType::WINDOW_POPUP); - - auto g_application=g_application_get_default(); - auto gio_application=Glib::wrap(g_application, true); - auto application=Glib::RefPtr<Gtk::Application>::cast_static(gio_application); - window->set_transient_for(*application->get_active_window()); - - window->set_type_hint(Gdk::WindowTypeHint::WINDOW_TYPE_HINT_TOOLTIP); - - window->set_events(Gdk::POINTER_MOTION_MASK); - window->property_decorated()=false; - window->set_accept_focus(false); - window->set_skip_taskbar_hint(true); - window->set_default_size(0, 0); - - window->signal_motion_notify_event().connect([on_motion](GdkEventMotion *event) { - if(on_motion) - on_motion(); - return false; - }); - - window->get_style_context()->add_class("juci_tooltip_window"); - auto visual = window->get_screen()->get_rgba_visual(); - if(visual) - gtk_widget_set_visual(reinterpret_cast<GtkWidget*>(window->gobj()), visual->gobj()); - - auto box=Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL)); - box->get_style_context()->add_class("juci_tooltip_box"); - window->add(*box); - - text_buffer=create_tooltip_buffer(); - wrap_lines(); - - auto tooltip_text_view=Gtk::manage(new Gtk::TextView(text_buffer)); - tooltip_text_view->get_style_context()->add_class("juci_tooltip_text_view"); - tooltip_text_view->set_editable(false); - + Tooltips::shown_tooltips.emplace(this); + + if (!window) { + //init window + window = std::make_unique<Gtk::Window>(Gtk::WindowType::WINDOW_POPUP); + + auto g_application = g_application_get_default(); + auto gio_application = Glib::wrap(g_application, true); + auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application); + window->set_transient_for(*application->get_active_window()); + + window->set_type_hint(Gdk::WindowTypeHint::WINDOW_TYPE_HINT_TOOLTIP); + + window->set_events(Gdk::POINTER_MOTION_MASK); + window->property_decorated() = false; + window->set_accept_focus(false); + window->set_skip_taskbar_hint(true); + window->set_default_size(0, 0); + + window->signal_motion_notify_event().connect([on_motion](GdkEventMotion *event) { + if (on_motion) + on_motion(); + return false; + }); + + window->get_style_context()->add_class("juci_tooltip_window"); + auto visual = window->get_screen()->get_rgba_visual(); + if (visual) + gtk_widget_set_visual(reinterpret_cast<GtkWidget *>(window->gobj()), visual->gobj()); + + auto box = Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL)); + box->get_style_context()->add_class("juci_tooltip_box"); + window->add(*box); + + text_buffer = create_tooltip_buffer(); + wrap_lines(); + + auto tooltip_text_view = Gtk::manage(new Gtk::TextView(text_buffer)); + tooltip_text_view->get_style_context()->add_class("juci_tooltip_text_view"); + tooltip_text_view->set_editable(false); + #if GTK_VERSION_GE(3, 20) - box->add(*tooltip_text_view); + box->add(*tooltip_text_view); #else - auto box2=Gtk::manage(new Gtk::Box()); - box2->pack_start(*tooltip_text_view, true, true, 3); - box->pack_start(*box2, true, true, 3); + auto box2=Gtk::manage(new Gtk::Box()); + box2->pack_start(*tooltip_text_view, true, true, 3); + box->pack_start(*box2, true, true, 3); #endif - auto layout=Pango::Layout::create(tooltip_text_view->get_pango_context()); - layout->set_text(text_buffer->get_text()); - layout->get_pixel_size(size.first, size.second); - size.first+=6; // 2xpadding - size.second+=8; // 2xpadding + 2 - - window->signal_realize().connect([this] { - if(!text_view) { - auto &dialog=SelectionDialog::get(); - if(dialog && dialog->is_visible()) { - int root_x, root_y; - dialog->get_position(root_x, root_y); - root_x-=3; // -1xpadding - rectangle.set_x(root_x); - rectangle.set_y(root_y-size.second); - if(rectangle.get_y()<0) - rectangle.set_y(0); - } - } - window->move(rectangle.get_x(), rectangle.get_y()); - }); - } - - int root_x=0, root_y=0; - if(text_view) { - //Adjust if tooltip is left of text_view - Gdk::Rectangle visible_rect; - text_view->get_visible_rect(visible_rect); - int visible_x, visible_y; - text_view->buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, visible_rect.get_x(), visible_rect.get_y(), visible_x, visible_y); - auto activation_rectangle_x=std::max(activation_rectangle.get_x(), visible_x); - - text_view->get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->get_root_coords(activation_rectangle_x, activation_rectangle.get_y(), root_x, root_y); - root_x-=3; // -1xpadding - if(root_y<size.second) - root_x+=visible_rect.get_width()*0.1; - } - rectangle.set_x(root_x); - rectangle.set_y(std::max(0, root_y-size.second)); - rectangle.set_width(size.first); - rectangle.set_height(size.second); - - if(!disregard_drawn) { - if(Tooltips::drawn_tooltips_rectangle.get_width()!=0) { - if(rectangle.intersects(Tooltips::drawn_tooltips_rectangle)) { - int new_y=Tooltips::drawn_tooltips_rectangle.get_y()-size.second; - if(new_y>=0) - rectangle.set_y(new_y); - else - rectangle.set_x(Tooltips::drawn_tooltips_rectangle.get_x()+Tooltips::drawn_tooltips_rectangle.get_width()+2); - } - Tooltips::drawn_tooltips_rectangle.join(rectangle); + auto layout = Pango::Layout::create(tooltip_text_view->get_pango_context()); + layout->set_text(text_buffer->get_text()); + layout->get_pixel_size(size.first, size.second); + size.first += 6; // 2xpadding + size.second += 8; // 2xpadding + 2 + + window->signal_realize().connect([this] { + if (!text_view) { + auto &dialog = SelectionDialog::get(); + if (dialog && dialog->is_visible()) { + int root_x, root_y; + dialog->get_position(root_x, root_y); + root_x -= 3; // -1xpadding + rectangle.set_x(root_x); + rectangle.set_y(root_y - size.second); + if (rectangle.get_y() < 0) + rectangle.set_y(0); + } + } + window->move(rectangle.get_x(), rectangle.get_y()); + }); + } + + int root_x = 0, root_y = 0; + if (text_view) { + //Adjust if tooltip is left of text_view + Gdk::Rectangle visible_rect; + text_view->get_visible_rect(visible_rect); + int visible_x, visible_y; + text_view->buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, visible_rect.get_x(), + visible_rect.get_y(), visible_x, visible_y); + auto activation_rectangle_x = std::max(activation_rectangle.get_x(), visible_x); + + text_view->get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->get_root_coords(activation_rectangle_x, + activation_rectangle.get_y(), + root_x, root_y); + root_x -= 3; // -1xpadding + if (root_y < size.second) + root_x += visible_rect.get_width() * 0.1; } - else - Tooltips::drawn_tooltips_rectangle=rectangle; - } - - if(window->get_realized()) - window->move(rectangle.get_x(), rectangle.get_y()); - window->show_all(); - shown=true; + rectangle.set_x(root_x); + rectangle.set_y(std::max(0, root_y - size.second)); + rectangle.set_width(size.first); + rectangle.set_height(size.second); + + if (!disregard_drawn) { + if (Tooltips::drawn_tooltips_rectangle.get_width() != 0) { + if (rectangle.intersects(Tooltips::drawn_tooltips_rectangle)) { + int new_y = Tooltips::drawn_tooltips_rectangle.get_y() - size.second; + if (new_y >= 0) + rectangle.set_y(new_y); + else + rectangle.set_x(Tooltips::drawn_tooltips_rectangle.get_x() + + Tooltips::drawn_tooltips_rectangle.get_width() + 2); + } + Tooltips::drawn_tooltips_rectangle.join(rectangle); + } else + Tooltips::drawn_tooltips_rectangle = rectangle; + } + + if (window->get_realized()) + window->move(rectangle.get_x(), rectangle.get_y()); + window->show_all(); + shown = true; } void Tooltip::hide(const std::pair<int, int> &last_mouse_pos, const std::pair<int, int> &mouse_pos) { - // Keep tooltip if mouse is moving towards it - // Calculated using dot product between the mouse_pos vector and the corners of the tooltip window - if(text_view && window && shown && last_mouse_pos.first!=-1 && last_mouse_pos.second!=-1 && mouse_pos.first!=-1 && mouse_pos.second!=-1) { - static int root_x, root_y; - text_view->get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->get_root_coords(last_mouse_pos.first, last_mouse_pos.second, root_x, root_y); - int diff_x=mouse_pos.first-last_mouse_pos.first; - int diff_y=mouse_pos.second-last_mouse_pos.second; - class Corner { - public: - Corner(int x, int y): x(x-root_x), y(y-root_y) {} - int x, y; - }; - std::vector<Corner> corners; - corners.emplace_back(rectangle.get_x(), rectangle.get_y()); - corners.emplace_back(rectangle.get_x()+rectangle.get_width(), rectangle.get_y()); - corners.emplace_back(rectangle.get_x(), rectangle.get_y()+rectangle.get_height()); - corners.emplace_back(rectangle.get_x()+rectangle.get_width(), rectangle.get_y()+rectangle.get_height()); - for(auto &corner: corners) { - if(diff_x*corner.x + diff_y*corner.y >= 0) - return; + // Keep tooltip if mouse is moving towards it + // Calculated using dot product between the mouse_pos vector and the corners of the tooltip window + if (text_view && window && shown && last_mouse_pos.first != -1 && last_mouse_pos.second != -1 && + mouse_pos.first != -1 && mouse_pos.second != -1) { + static int root_x, root_y; + text_view->get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->get_root_coords(last_mouse_pos.first, + last_mouse_pos.second, root_x, + root_y); + int diff_x = mouse_pos.first - last_mouse_pos.first; + int diff_y = mouse_pos.second - last_mouse_pos.second; + class Corner { + public: + Corner(int x, int y) : x(x - root_x), y(y - root_y) {} + + int x, y; + }; + std::vector<Corner> corners; + corners.emplace_back(rectangle.get_x(), rectangle.get_y()); + corners.emplace_back(rectangle.get_x() + rectangle.get_width(), rectangle.get_y()); + corners.emplace_back(rectangle.get_x(), rectangle.get_y() + rectangle.get_height()); + corners.emplace_back(rectangle.get_x() + rectangle.get_width(), rectangle.get_y() + rectangle.get_height()); + for (auto &corner: corners) { + if (diff_x * corner.x + diff_y * corner.y >= 0) + return; + } } - } - Tooltips::shown_tooltips.erase(this); - if(window) - window->hide(); - shown=false; + Tooltips::shown_tooltips.erase(this); + if (window) + window->hide(); + shown = false; } void Tooltip::wrap_lines() { - if(!text_buffer) - return; - - auto iter=text_buffer->begin(); - - while(iter) { - auto last_space=text_buffer->end(); - bool end=false; - for(unsigned c=0;c<=80;c++) { - if(!iter) { - end=true; - break; - } - if(*iter==' ') - last_space=iter; - if(*iter=='\n') { - end=true; - iter.forward_char(); - break; - } - iter.forward_char(); - } - if(!end) { - while(!last_space && iter) { //If no space (word longer than 80) - iter.forward_char(); - if(iter && *iter==' ') - last_space=iter; - } - if(iter && last_space) { - auto mark=text_buffer->create_mark(last_space); - auto last_space_p=last_space; - last_space.forward_char(); - text_buffer->erase(last_space_p, last_space); - text_buffer->insert(mark->get_iter(), "\n"); - - iter=mark->get_iter(); - iter.forward_char(); - - text_buffer->delete_mark(mark); - } + if (!text_buffer) + return; + + auto iter = text_buffer->begin(); + + while (iter) { + auto last_space = text_buffer->end(); + bool end = false; + for (unsigned c = 0; c <= 80; c++) { + if (!iter) { + end = true; + break; + } + if (*iter == ' ') + last_space = iter; + if (*iter == '\n') { + end = true; + iter.forward_char(); + break; + } + iter.forward_char(); + } + if (!end) { + while (!last_space && iter) { //If no space (word longer than 80) + iter.forward_char(); + if (iter && *iter == ' ') + last_space = iter; + } + if (iter && last_space) { + auto mark = text_buffer->create_mark(last_space); + auto last_space_p = last_space; + last_space.forward_char(); + text_buffer->erase(last_space_p, last_space); + text_buffer->insert(mark->get_iter(), "\n"); + + iter = mark->get_iter(); + iter.forward_char(); + + text_buffer->delete_mark(mark); + } + } } - } } -void Tooltips::show(const Gdk::Rectangle& rectangle, bool disregard_drawn) { - for(auto &tooltip : tooltip_list) { - tooltip.update(); - if(rectangle.intersects(tooltip.activation_rectangle)) - tooltip.show(disregard_drawn, on_motion); - else - tooltip.hide(); - } +void Tooltips::show(const Gdk::Rectangle &rectangle, bool disregard_drawn) { + for (auto &tooltip : tooltip_list) { + tooltip.update(); + if (rectangle.intersects(tooltip.activation_rectangle)) + tooltip.show(disregard_drawn, on_motion); + else + tooltip.hide(); + } } void Tooltips::show(bool disregard_drawn) { - for(auto &tooltip : tooltip_list) { - tooltip.update(); - tooltip.show(disregard_drawn, on_motion); - } + for (auto &tooltip : tooltip_list) { + tooltip.update(); + tooltip.show(disregard_drawn, on_motion); + } } void Tooltips::hide(const std::pair<int, int> &last_mouse_pos, const std::pair<int, int> &mouse_pos) { - for(auto &tooltip : tooltip_list) - tooltip.hide(last_mouse_pos, mouse_pos); + for (auto &tooltip : tooltip_list) + tooltip.hide(last_mouse_pos, mouse_pos); } diff --git a/src/tooltips.h b/src/tooltips.h index a032cb27..2cca4554 100644 --- a/src/tooltips.h +++ b/src/tooltips.h @@ -1,4 +1,5 @@ #pragma once + #include "gtkmm.h" #include <string> #include <list> @@ -7,49 +8,62 @@ class Tooltip { public: - Tooltip(std::function<Glib::RefPtr<Gtk::TextBuffer>()> create_tooltip_buffer, Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, Glib::RefPtr<Gtk::TextBuffer::Mark> end_mark); - Tooltip(std::function<Glib::RefPtr<Gtk::TextBuffer>()> create_tooltip_buffer) : Tooltip(create_tooltip_buffer, nullptr, Glib::RefPtr<Gtk::TextBuffer::Mark>(), Glib::RefPtr<Gtk::TextBuffer::Mark>()) {} - ~Tooltip(); - - void update(); - void show(bool disregard_drawn=false, const std::function<void()> &on_motion=nullptr); - void hide(const std::pair<int, int> &last_mouse_pos = {-1, -1}, const std::pair<int, int> &mouse_pos = {-1, -1}); - - Gdk::Rectangle activation_rectangle; - Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark; - Glib::RefPtr<Gtk::TextBuffer::Mark> end_mark; - - Glib::RefPtr<Gtk::TextBuffer> text_buffer; + Tooltip(std::function<Glib::RefPtr<Gtk::TextBuffer>()> create_tooltip_buffer, Gtk::TextView *text_view, + Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, Glib::RefPtr<Gtk::TextBuffer::Mark> end_mark); + + Tooltip(std::function<Glib::RefPtr<Gtk::TextBuffer>()> create_tooltip_buffer) : Tooltip(create_tooltip_buffer, + nullptr, + Glib::RefPtr<Gtk::TextBuffer::Mark>(), + Glib::RefPtr<Gtk::TextBuffer::Mark>()) {} + + ~Tooltip(); + + void update(); + + void show(bool disregard_drawn = false, const std::function<void()> &on_motion = nullptr); + + void hide(const std::pair<int, int> &last_mouse_pos = {-1, -1}, const std::pair<int, int> &mouse_pos = {-1, -1}); + + Gdk::Rectangle activation_rectangle; + Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark; + Glib::RefPtr<Gtk::TextBuffer::Mark> end_mark; + + Glib::RefPtr<Gtk::TextBuffer> text_buffer; private: - std::unique_ptr<Gtk::Window> window; - void wrap_lines(); - - std::function<Glib::RefPtr<Gtk::TextBuffer>()> create_tooltip_buffer; - Gtk::TextView *text_view; - std::pair<int, int> size; - Gdk::Rectangle rectangle; - - bool shown=false; + std::unique_ptr<Gtk::Window> window; + + void wrap_lines(); + + std::function<Glib::RefPtr<Gtk::TextBuffer>()> create_tooltip_buffer; + Gtk::TextView *text_view; + std::pair<int, int> size; + Gdk::Rectangle rectangle; + + bool shown = false; }; class Tooltips { public: - static std::set<Tooltip*> shown_tooltips; - static Gdk::Rectangle drawn_tooltips_rectangle; - static void init() {drawn_tooltips_rectangle=Gdk::Rectangle();} - - void show(const Gdk::Rectangle& rectangle, bool disregard_drawn=false); - void show(bool disregard_drawn=false); - void hide(const std::pair<int, int> &last_mouse_pos = {-1, -1}, const std::pair<int, int> &mouse_pos = {-1, -1}); - void clear() {tooltip_list.clear();}; - - template<typename... Ts> - void emplace_back(Ts&&... params) { - tooltip_list.emplace_back(std::forward<Ts>(params)...); - } - - std::function<void()> on_motion; - + static std::set<Tooltip *> shown_tooltips; + static Gdk::Rectangle drawn_tooltips_rectangle; + + static void init() { drawn_tooltips_rectangle = Gdk::Rectangle(); } + + void show(const Gdk::Rectangle &rectangle, bool disregard_drawn = false); + + void show(bool disregard_drawn = false); + + void hide(const std::pair<int, int> &last_mouse_pos = {-1, -1}, const std::pair<int, int> &mouse_pos = {-1, -1}); + + void clear() { tooltip_list.clear(); }; + + template<typename... Ts> + void emplace_back(Ts &&... params) { + tooltip_list.emplace_back(std::forward<Ts>(params)...); + } + + std::function<void()> on_motion; + private: - std::list<Tooltip> tooltip_list; + std::list<Tooltip> tooltip_list; }; diff --git a/src/usages_clang.cc b/src/usages_clang.cc index 6b861b14..71719539 100644 --- a/src/usages_clang.cc +++ b/src/usages_clang.cc @@ -9,10 +9,13 @@ #include <thread> #ifdef _WIN32 + #include <windows.h> + DWORD get_current_process_id() { - return GetCurrentProcessId(); + return GetCurrentProcessId(); } + #else #include <unistd.h> pid_t get_current_process_id() { @@ -26,726 +29,753 @@ std::mutex Usages::Clang::caches_mutex; std::atomic<size_t> Usages::Clang::cache_in_progress_count(0); bool Usages::Clang::Cache::Cursor::operator==(const Cursor &o) { - for(auto &usr : usrs) { - if(clangmm::Cursor::is_similar_kind(o.kind, kind) && o.usrs.count(usr)) - return true; - } - return false; + for (auto &usr : usrs) { + if (clangmm::Cursor::is_similar_kind(o.kind, kind) && o.usrs.count(usr)) + return true; + } + return false; } -Usages::Clang::Cache::Cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path, - std::time_t before_parse_time, clangmm::TranslationUnit *translation_unit, clangmm::Tokens *clang_tokens) - : project_path(project_path), build_path(build_path) { - for(auto &clang_token : *clang_tokens) { - tokens.emplace_back(Token{clang_token.get_spelling(), clang_token.get_source_range().get_offsets(), static_cast<size_t>(-1)}); - - if(clang_token.is_identifier()) { - auto clang_cursor = clang_token.get_cursor().get_referenced(); - if(clang_cursor) { - Cursor cursor{clang_cursor.get_kind(), clang_cursor.get_all_usr_extended()}; - for(size_t c = 0; c < cursors.size(); ++c) { - if(cursor == cursors[c]) { - tokens.back().cursor_id = c; - break; - } - } - if(tokens.back().cursor_id == static_cast<size_t>(-1)) { - cursors.emplace_back(cursor); - tokens.back().cursor_id = cursors.size() - 1; +Usages::Clang::Cache::Cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, + const boost::filesystem::path &path, + std::time_t before_parse_time, clangmm::TranslationUnit *translation_unit, + clangmm::Tokens *clang_tokens) + : project_path(project_path), build_path(build_path) { + for (auto &clang_token : *clang_tokens) { + tokens.emplace_back(Token{clang_token.get_spelling(), clang_token.get_source_range().get_offsets(), + static_cast<size_t>(-1)}); + + if (clang_token.is_identifier()) { + auto clang_cursor = clang_token.get_cursor().get_referenced(); + if (clang_cursor) { + Cursor cursor{clang_cursor.get_kind(), clang_cursor.get_all_usr_extended()}; + for (size_t c = 0; c < cursors.size(); ++c) { + if (cursor == cursors[c]) { + tokens.back().cursor_id = c; + break; + } + } + if (tokens.back().cursor_id == static_cast<size_t>(-1)) { + cursors.emplace_back(cursor); + tokens.back().cursor_id = cursors.size() - 1; + } + } } - } } - } - boost::system::error_code ec; - auto last_write_time = boost::filesystem::last_write_time(path, ec); - if(ec) - last_write_time = 0; - if(last_write_time > before_parse_time) - last_write_time = 0; - paths_and_last_write_times.emplace(path, last_write_time); - - class VisitorData { - public: - const boost::filesystem::path &project_path; - const boost::filesystem::path &path; - std::time_t before_parse_time; - std::map<boost::filesystem::path, std::time_t> &paths_and_last_write_times; - }; - VisitorData visitor_data{project_path, path, before_parse_time, paths_and_last_write_times}; - - clang_getInclusions(translation_unit->cx_tu, [](CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData data) { - auto visitor_data = static_cast<VisitorData *>(data); - auto path = filesystem::get_normal_path(clangmm::to_string(clang_getFileName(included_file))); - if(filesystem::file_in_path(path, visitor_data->project_path)) { - for(unsigned c = 0; c < include_len; ++c) { - auto from_path = filesystem::get_normal_path(clangmm::SourceLocation(inclusion_stack[c]).get_path()); - if(from_path == visitor_data->path) { - boost::system::error_code ec; - auto last_write_time = boost::filesystem::last_write_time(path, ec); - if(ec) - last_write_time = 0; - if(last_write_time > visitor_data->before_parse_time) - last_write_time = 0; - visitor_data->paths_and_last_write_times.emplace(path, last_write_time); - break; - } - } - } - }, - &visitor_data); + boost::system::error_code ec; + auto last_write_time = boost::filesystem::last_write_time(path, ec); + if (ec) + last_write_time = 0; + if (last_write_time > before_parse_time) + last_write_time = 0; + paths_and_last_write_times.emplace(path, last_write_time); + + class VisitorData { + public: + const boost::filesystem::path &project_path; + const boost::filesystem::path &path; + std::time_t before_parse_time; + std::map<boost::filesystem::path, std::time_t> &paths_and_last_write_times; + }; + VisitorData visitor_data{project_path, path, before_parse_time, paths_and_last_write_times}; + + clang_getInclusions(translation_unit->cx_tu, + [](CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, + CXClientData data) { + auto visitor_data = static_cast<VisitorData *>(data); + auto path = filesystem::get_normal_path( + clangmm::to_string(clang_getFileName(included_file))); + if (filesystem::file_in_path(path, visitor_data->project_path)) { + for (unsigned c = 0; c < include_len; ++c) { + auto from_path = filesystem::get_normal_path( + clangmm::SourceLocation(inclusion_stack[c]).get_path()); + if (from_path == visitor_data->path) { + boost::system::error_code ec; + auto last_write_time = boost::filesystem::last_write_time(path, ec); + if (ec) + last_write_time = 0; + if (last_write_time > visitor_data->before_parse_time) + last_write_time = 0; + visitor_data->paths_and_last_write_times.emplace(path, last_write_time); + break; + } + } + } + }, + &visitor_data); } -std::vector<std::pair<clangmm::Offset, clangmm::Offset>> Usages::Clang::Cache::get_similar_token_offsets(clangmm::Cursor::Kind kind, const std::string &spelling, - const std::unordered_set<std::string> &usrs) const { - std::vector<std::pair<clangmm::Offset, clangmm::Offset>> offsets; - for(auto &token : tokens) { - if(token.cursor_id != static_cast<size_t>(-1)) { - auto &cursor = cursors[token.cursor_id]; - if(clangmm::Cursor::is_similar_kind(cursor.kind, kind) && token.spelling == spelling) { - for(auto &usr : cursor.usrs) { - if(usrs.count(usr)) { - offsets.emplace_back(token.offsets); - break; - } +std::vector<std::pair<clangmm::Offset, clangmm::Offset>> +Usages::Clang::Cache::get_similar_token_offsets(clangmm::Cursor::Kind kind, const std::string &spelling, + const std::unordered_set<std::string> &usrs) const { + std::vector<std::pair<clangmm::Offset, clangmm::Offset>> offsets; + for (auto &token : tokens) { + if (token.cursor_id != static_cast<size_t>(-1)) { + auto &cursor = cursors[token.cursor_id]; + if (clangmm::Cursor::is_similar_kind(cursor.kind, kind) && token.spelling == spelling) { + for (auto &usr : cursor.usrs) { + if (usrs.count(usr)) { + offsets.emplace_back(token.offsets); + break; + } + } + } } - } } - } - return offsets; + return offsets; } -std::vector<Usages::Clang::Usages> Usages::Clang::get_usages(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &debug_path, - const std::string &spelling, const clangmm::Cursor &cursor, const std::vector<clangmm::TranslationUnit *> &translation_units) { - std::vector<Usages> usages; - - if(spelling.empty()) - return usages; - - PathSet visited; - - auto usr_extended = cursor.get_usr_extended(); - if(!usr_extended.empty() && usr_extended[0] >= '0' && usr_extended[0] <= '9') { //if declared within a function, return - if(!translation_units.empty()) - add_usages(project_path, build_path, boost::filesystem::path(), usages, visited, spelling, cursor, translation_units.front(), false); - return usages; - } - - for(auto &translation_unit : translation_units) - add_usages(project_path, build_path, boost::filesystem::path(), usages, visited, spelling, cursor, translation_unit, false); - - for(auto &translation_unit : translation_units) - add_usages_from_includes(project_path, build_path, usages, visited, spelling, cursor, translation_unit, false); - - if(project_path.empty()) - return usages; - - auto paths = find_paths(project_path, build_path, debug_path); - auto pair = parse_paths(spelling, paths); - PathSet all_cursors_paths; - auto canonical=cursor.get_canonical(); - all_cursors_paths.emplace(canonical.get_source_location().get_path()); - for(auto &cursor: canonical.get_all_overridden_cursors()) - all_cursors_paths.emplace(cursor.get_source_location().get_path()); - auto pair2 = find_potential_paths(all_cursors_paths, project_path, pair.first, pair.second); - auto &potential_paths = pair2.first; - auto &all_includes = pair2.second; - - // Remove visited paths - for(auto it = potential_paths.begin(); it != potential_paths.end();) { - if(visited.find(*it) != visited.end()) - it = potential_paths.erase(it); - else - ++it; - } - - // Wait for current caching to finish - std::unique_ptr<Dialog::Message> message; - const std::string message_string = "Please wait while finding usages"; - if(cache_in_progress_count != 0) { - message = std::make_unique<Dialog::Message>(message_string); - while(cache_in_progress_count != 0) { - while(Gtk::Main::events_pending()) - Gtk::Main::iteration(false); +std::vector<Usages::Clang::Usages> +Usages::Clang::get_usages(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, + const boost::filesystem::path &debug_path, + const std::string &spelling, const clangmm::Cursor &cursor, + const std::vector<clangmm::TranslationUnit *> &translation_units) { + std::vector<Usages> usages; + + if (spelling.empty()) + return usages; + + PathSet visited; + + auto usr_extended = cursor.get_usr_extended(); + if (!usr_extended.empty() && usr_extended[0] >= '0' && + usr_extended[0] <= '9') { //if declared within a function, return + if (!translation_units.empty()) + add_usages(project_path, build_path, boost::filesystem::path(), usages, visited, spelling, cursor, + translation_units.front(), false); + return usages; } - } - // Use cache - for(auto it = potential_paths.begin(); it != potential_paths.end();) { - std::unique_lock<std::mutex> lock(caches_mutex); - auto caches_it = caches.find(*it); - - // Load cache from file if not found in memory and if cache file exists - if(caches_it == caches.end()) { - auto cache = read_cache(project_path, build_path, *it); - if(cache) { - auto pair = caches.emplace(*it, std::move(cache)); - caches_it = pair.first; - } + for (auto &translation_unit : translation_units) + add_usages(project_path, build_path, boost::filesystem::path(), usages, visited, spelling, cursor, + translation_unit, false); + + for (auto &translation_unit : translation_units) + add_usages_from_includes(project_path, build_path, usages, visited, spelling, cursor, translation_unit, false); + + if (project_path.empty()) + return usages; + + auto paths = find_paths(project_path, build_path, debug_path); + auto pair = parse_paths(spelling, paths); + PathSet all_cursors_paths; + auto canonical = cursor.get_canonical(); + all_cursors_paths.emplace(canonical.get_source_location().get_path()); + for (auto &cursor: canonical.get_all_overridden_cursors()) + all_cursors_paths.emplace(cursor.get_source_location().get_path()); + auto pair2 = find_potential_paths(all_cursors_paths, project_path, pair.first, pair.second); + auto &potential_paths = pair2.first; + auto &all_includes = pair2.second; + + // Remove visited paths + for (auto it = potential_paths.begin(); it != potential_paths.end();) { + if (visited.find(*it) != visited.end()) + it = potential_paths.erase(it); + else + ++it; } - if(caches_it != caches.end()) { - if(add_usages_from_cache(caches_it->first, usages, visited, spelling, cursor, caches_it->second)) - it = potential_paths.erase(it); - else { - caches.erase(caches_it); - ++it; - } + // Wait for current caching to finish + std::unique_ptr<Dialog::Message> message; + const std::string message_string = "Please wait while finding usages"; + if (cache_in_progress_count != 0) { + message = std::make_unique<Dialog::Message>(message_string); + while (cache_in_progress_count != 0) { + while (Gtk::Main::events_pending()) + Gtk::Main::iteration(false); + } } - else - ++it; - } - // Remove paths that has been included - for(auto it = potential_paths.begin(); it != potential_paths.end();) { - if(all_includes.find(*it) != all_includes.end()) - it = potential_paths.erase(it); - else - ++it; - } - - // Parse potential paths - if(!potential_paths.empty()) { - if(!message) - message = std::make_unique<Dialog::Message>(message_string); - - std::vector<std::thread> threads; - auto it = potential_paths.begin(); - auto number_of_threads = Config::get().source.clang_usages_threads; - if(number_of_threads == static_cast<unsigned>(-1)) { - number_of_threads = std::thread::hardware_concurrency(); - if(number_of_threads == 0) - number_of_threads = 1; + // Use cache + for (auto it = potential_paths.begin(); it != potential_paths.end();) { + std::unique_lock<std::mutex> lock(caches_mutex); + auto caches_it = caches.find(*it); + + // Load cache from file if not found in memory and if cache file exists + if (caches_it == caches.end()) { + auto cache = read_cache(project_path, build_path, *it); + if (cache) { + auto pair = caches.emplace(*it, std::move(cache)); + caches_it = pair.first; + } + } + + if (caches_it != caches.end()) { + if (add_usages_from_cache(caches_it->first, usages, visited, spelling, cursor, caches_it->second)) + it = potential_paths.erase(it); + else { + caches.erase(caches_it); + ++it; + } + } else + ++it; } - for(unsigned thread_id = 0; thread_id < number_of_threads; ++thread_id) { - threads.emplace_back([&potential_paths, &it, &build_path, - &project_path, &usages, &visited, &spelling, &cursor] { - while(true) { - boost::filesystem::path path; - { - static std::mutex mutex; - std::unique_lock<std::mutex> lock(mutex); - if(it == potential_paths.end()) - return; - path = *it; + + // Remove paths that has been included + for (auto it = potential_paths.begin(); it != potential_paths.end();) { + if (all_includes.find(*it) != all_includes.end()) + it = potential_paths.erase(it); + else ++it; - } - clangmm::Index index(0, 0); - - { - static std::mutex mutex; - std::unique_lock<std::mutex> lock(mutex); - // std::cout << "parsing: " << path << std::endl; - } - // auto before_time = std::chrono::system_clock::now(); - - std::ifstream stream(path.string(), std::ifstream::binary); - std::string buffer; - buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>()); - - auto arguments = CompileCommands::get_arguments(build_path, path); - arguments.emplace_back("-w"); // Disable all warnings - for(auto it = arguments.begin(); it != arguments.end();) { // remove comments from system headers - if(*it == "-fretain-comments-from-system-headers") - it = arguments.erase(it); - else - ++it; - } - int flags = CXTranslationUnit_Incomplete; + } + + // Parse potential paths + if (!potential_paths.empty()) { + if (!message) + message = std::make_unique<Dialog::Message>(message_string); + + std::vector<std::thread> threads; + auto it = potential_paths.begin(); + auto number_of_threads = Config::get().source.clang_usages_threads; + if (number_of_threads == static_cast<unsigned>(-1)) { + number_of_threads = std::thread::hardware_concurrency(); + if (number_of_threads == 0) + number_of_threads = 1; + } + for (unsigned thread_id = 0; thread_id < number_of_threads; ++thread_id) { + threads.emplace_back([&potential_paths, &it, &build_path, + &project_path, &usages, &visited, &spelling, &cursor] { + while (true) { + boost::filesystem::path path; + { + static std::mutex mutex; + std::unique_lock<std::mutex> lock(mutex); + if (it == potential_paths.end()) + return; + path = *it; + ++it; + } + clangmm::Index index(0, 0); + + { + static std::mutex mutex; + std::unique_lock<std::mutex> lock(mutex); + // std::cout << "parsing: " << path << std::endl; + } + // auto before_time = std::chrono::system_clock::now(); + + std::ifstream stream(path.string(), std::ifstream::binary); + std::string buffer; + buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>()); + + auto arguments = CompileCommands::get_arguments(build_path, path); + arguments.emplace_back("-w"); // Disable all warnings + for (auto it = arguments.begin(); it != arguments.end();) { // remove comments from system headers + if (*it == "-fretain-comments-from-system-headers") + it = arguments.erase(it); + else + ++it; + } + int flags = CXTranslationUnit_Incomplete; #if CINDEX_VERSION_MAJOR > 0 || (CINDEX_VERSION_MAJOR == 0 && CINDEX_VERSION_MINOR >= 35) - flags |= CXTranslationUnit_KeepGoing; + flags |= CXTranslationUnit_KeepGoing; #endif - clangmm::TranslationUnit translation_unit(index, path.string(), arguments, buffer, flags); - - { - static std::mutex mutex; - std::unique_lock<std::mutex> lock(mutex); - add_usages(project_path, build_path, path, usages, visited, spelling, cursor, &translation_unit, true); - add_usages_from_includes(project_path, build_path, usages, visited, spelling, cursor, &translation_unit, true); - } - - // auto time = std::chrono::system_clock::now(); - // std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(time - before_time).count() << std::endl; + clangmm::TranslationUnit translation_unit(index, path.string(), arguments, buffer, flags); + + { + static std::mutex mutex; + std::unique_lock<std::mutex> lock(mutex); + add_usages(project_path, build_path, path, usages, visited, spelling, cursor, &translation_unit, + true); + add_usages_from_includes(project_path, build_path, usages, visited, spelling, cursor, + &translation_unit, true); + } + + // auto time = std::chrono::system_clock::now(); + // std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(time - before_time).count() << std::endl; + } + }); } - }); + for (auto &thread : threads) + thread.join(); } - for(auto &thread : threads) - thread.join(); - } - if(message) - message->hide(); + if (message) + message->hide(); - return usages; + return usages; } -void Usages::Clang::cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path, - std::time_t before_parse_time, const PathSet &project_paths_in_use, clangmm::TranslationUnit *translation_unit, clangmm::Tokens *tokens) { - class ScopeExit { - public: - std::function<void()> f; - ~ScopeExit() { - f(); - } - }; - ScopeExit scope_exit{[] { - --cache_in_progress_count; - }}; - - if(project_path.empty()) - return; +void Usages::Clang::cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, + const boost::filesystem::path &path, + std::time_t before_parse_time, const PathSet &project_paths_in_use, + clangmm::TranslationUnit *translation_unit, clangmm::Tokens *tokens) { + class ScopeExit { + public: + std::function<void()> f; - { - std::unique_lock<std::mutex> lock(caches_mutex); - if(project_paths_in_use.count(project_path)) { - caches.erase(path); - caches.emplace(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens)); + ~ScopeExit() { + f(); + } + }; + ScopeExit scope_exit{[] { + --cache_in_progress_count; + }}; + + if (project_path.empty()) + return; + + { + std::unique_lock<std::mutex> lock(caches_mutex); + if (project_paths_in_use.count(project_path)) { + caches.erase(path); + caches.emplace(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens)); + } else + write_cache(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens)); } - else - write_cache(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens)); - } - class VisitorData { - public: - const boost::filesystem::path &project_path; - PathSet paths; - }; - VisitorData visitor_data{project_path, {}}; + class VisitorData { + public: + const boost::filesystem::path &project_path; + PathSet paths; + }; + VisitorData visitor_data{project_path, {}}; - auto translation_unit_cursor = clang_getTranslationUnitCursor(translation_unit->cx_tu); - clang_visitChildren(translation_unit_cursor, [](CXCursor cx_cursor, CXCursor cx_parent, CXClientData data) { - auto visitor_data = static_cast<VisitorData *>(data); + auto translation_unit_cursor = clang_getTranslationUnitCursor(translation_unit->cx_tu); + clang_visitChildren(translation_unit_cursor, [](CXCursor cx_cursor, CXCursor cx_parent, CXClientData data) { + auto visitor_data = static_cast<VisitorData *>(data); - auto path = filesystem::get_normal_path(clangmm::Cursor(cx_cursor).get_source_location().get_path()); - if(filesystem::file_in_path(path, visitor_data->project_path)) - visitor_data->paths.emplace(path); + auto path = filesystem::get_normal_path(clangmm::Cursor(cx_cursor).get_source_location().get_path()); + if (filesystem::file_in_path(path, visitor_data->project_path)) + visitor_data->paths.emplace(path); - return CXChildVisit_Continue; - }, - &visitor_data); + return CXChildVisit_Continue; + }, + &visitor_data); - visitor_data.paths.erase(path); + visitor_data.paths.erase(path); - for(auto &path : visitor_data.paths) { - boost::system::error_code ec; - auto file_size = boost::filesystem::file_size(path, ec); - if(file_size == static_cast<boost::uintmax_t>(-1) || ec) - continue; - auto tokens = translation_unit->get_tokens(path.string(), 0, file_size - 1); - std::unique_lock<std::mutex> lock(caches_mutex); - if(project_paths_in_use.count(project_path)) { - caches.erase(path); - caches.emplace(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens.get())); + for (auto &path : visitor_data.paths) { + boost::system::error_code ec; + auto file_size = boost::filesystem::file_size(path, ec); + if (file_size == static_cast<boost::uintmax_t>(-1) || ec) + continue; + auto tokens = translation_unit->get_tokens(path.string(), 0, file_size - 1); + std::unique_lock<std::mutex> lock(caches_mutex); + if (project_paths_in_use.count(project_path)) { + caches.erase(path); + caches.emplace(path, + Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens.get())); + } else + write_cache(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens.get())); } - else - write_cache(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens.get())); - } } void Usages::Clang::erase_unused_caches(const PathSet &project_paths_in_use) { - std::unique_lock<std::mutex> lock(caches_mutex); - for(auto it = caches.begin(); it != caches.end();) { - bool found = false; - for(auto &project_path : project_paths_in_use) { - if(filesystem::file_in_path(it->first, project_path)) { - found = true; - break; - } - } - if(!found) { - write_cache(it->first, it->second); - it = caches.erase(it); + std::unique_lock<std::mutex> lock(caches_mutex); + for (auto it = caches.begin(); it != caches.end();) { + bool found = false; + for (auto &project_path : project_paths_in_use) { + if (filesystem::file_in_path(it->first, project_path)) { + found = true; + break; + } + } + if (!found) { + write_cache(it->first, it->second); + it = caches.erase(it); + } else + ++it; } - else - ++it; - } } void Usages::Clang::erase_cache(const boost::filesystem::path &path) { - std::unique_lock<std::mutex> lock(caches_mutex); + std::unique_lock<std::mutex> lock(caches_mutex); - auto it = caches.find(path); - if(it == caches.end()) - return; + auto it = caches.find(path); + if (it == caches.end()) + return; - auto paths_and_last_write_times = std::move(it->second.paths_and_last_write_times); - for(auto &path_and_last_write_time : paths_and_last_write_times) - caches.erase(path_and_last_write_time.first); + auto paths_and_last_write_times = std::move(it->second.paths_and_last_write_times); + for (auto &path_and_last_write_time : paths_and_last_write_times) + caches.erase(path_and_last_write_time.first); } -void Usages::Clang::erase_all_caches_for_project(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path) { - if(project_path.empty()) - return; +void Usages::Clang::erase_all_caches_for_project(const boost::filesystem::path &project_path, + const boost::filesystem::path &build_path) { + if (project_path.empty()) + return; - if(cache_in_progress_count != 0) - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + if (cache_in_progress_count != 0) + std::this_thread::sleep_for(std::chrono::milliseconds(10)); - std::unique_lock<std::mutex> lock(caches_mutex); - boost::system::error_code ec; - auto usages_clang_path = build_path / cache_folder; - if(boost::filesystem::exists(usages_clang_path, ec) && boost::filesystem::is_directory(usages_clang_path, ec)) { - for(boost::filesystem::directory_iterator it(usages_clang_path), end; it != end; ++it) { - if(it->path().extension() == ".usages") - boost::filesystem::remove(it->path(), ec); + std::unique_lock<std::mutex> lock(caches_mutex); + boost::system::error_code ec; + auto usages_clang_path = build_path / cache_folder; + if (boost::filesystem::exists(usages_clang_path, ec) && boost::filesystem::is_directory(usages_clang_path, ec)) { + for (boost::filesystem::directory_iterator it(usages_clang_path), end; it != end; ++it) { + if (it->path().extension() == ".usages") + boost::filesystem::remove(it->path(), ec); + } } - } - for(auto it = caches.begin(); it != caches.end();) { - if(filesystem::file_in_path(it->first, project_path)) - it = caches.erase(it); - else - ++it; - } + for (auto it = caches.begin(); it != caches.end();) { + if (filesystem::file_in_path(it->first, project_path)) + it = caches.erase(it); + else + ++it; + } } void Usages::Clang::cache_in_progress() { - ++cache_in_progress_count; + ++cache_in_progress_count; } -void Usages::Clang::add_usages(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path_, - std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, clangmm::Cursor cursor, +void Usages::Clang::add_usages(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, + const boost::filesystem::path &path_, + std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, + clangmm::Cursor cursor, clangmm::TranslationUnit *translation_unit, bool store_in_cache) { - std::unique_ptr<clangmm::Tokens> tokens; - boost::filesystem::path path; - auto before_parse_time = std::time(nullptr); - auto all_usr_extended = cursor.get_all_usr_extended(); - if(path_.empty()) { - path = clangmm::to_string(clang_getTranslationUnitSpelling(translation_unit->cx_tu)); - if(visited.find(path) != visited.end() || !filesystem::file_in_path(path, project_path)) - return; - tokens = translation_unit->get_tokens(); - } - else { - path = path_; - if(visited.find(path) != visited.end() || !filesystem::file_in_path(path, project_path)) - return; - boost::system::error_code ec; - auto file_size = boost::filesystem::file_size(path, ec); - if(file_size == static_cast<boost::uintmax_t>(-1) || ec) - return; - tokens = translation_unit->get_tokens(path.string(), 0, file_size - 1); - } - - auto offsets = tokens->get_similar_token_offsets(cursor.get_kind(), spelling, all_usr_extended); - std::vector<std::string> lines; - for(auto &offset : offsets) { - std::string line; - auto line_nr = offset.second.line; - for(auto &token : *tokens) { - auto offset = token.get_source_location().get_offset(); - if(offset.line == line_nr) { - while(line.size() < offset.index - 1) - line += ' '; - line += token.get_spelling(); - } + std::unique_ptr<clangmm::Tokens> tokens; + boost::filesystem::path path; + auto before_parse_time = std::time(nullptr); + auto all_usr_extended = cursor.get_all_usr_extended(); + if (path_.empty()) { + path = clangmm::to_string(clang_getTranslationUnitSpelling(translation_unit->cx_tu)); + if (visited.find(path) != visited.end() || !filesystem::file_in_path(path, project_path)) + return; + tokens = translation_unit->get_tokens(); + } else { + path = path_; + if (visited.find(path) != visited.end() || !filesystem::file_in_path(path, project_path)) + return; + boost::system::error_code ec; + auto file_size = boost::filesystem::file_size(path, ec); + if (file_size == static_cast<boost::uintmax_t>(-1) || ec) + return; + tokens = translation_unit->get_tokens(path.string(), 0, file_size - 1); } - lines.emplace_back(std::move(line)); - } - if(store_in_cache && filesystem::file_in_path(path, project_path)) { - std::unique_lock<std::mutex> lock(caches_mutex); - caches.erase(path); - caches.emplace(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens.get())); - } + auto offsets = tokens->get_similar_token_offsets(cursor.get_kind(), spelling, all_usr_extended); + std::vector<std::string> lines; + for (auto &offset : offsets) { + std::string line; + auto line_nr = offset.second.line; + for (auto &token : *tokens) { + auto offset = token.get_source_location().get_offset(); + if (offset.line == line_nr) { + while (line.size() < offset.index - 1) + line += ' '; + line += token.get_spelling(); + } + } + lines.emplace_back(std::move(line)); + } + + if (store_in_cache && filesystem::file_in_path(path, project_path)) { + std::unique_lock<std::mutex> lock(caches_mutex); + caches.erase(path); + caches.emplace(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens.get())); + } - visited.emplace(path); - if(!offsets.empty()) - usages.emplace_back(Usages{std::move(path), std::move(offsets), lines}); + visited.emplace(path); + if (!offsets.empty()) + usages.emplace_back(Usages{std::move(path), std::move(offsets), lines}); } -bool Usages::Clang::add_usages_from_cache(const boost::filesystem::path &path, std::vector<Usages> &usages, PathSet &visited, - const std::string &spelling, const clangmm::Cursor &cursor, const Cache &cache) { - for(auto &path_and_last_write_time : cache.paths_and_last_write_times) { - boost::system::error_code ec; - auto last_write_time = boost::filesystem::last_write_time(path_and_last_write_time.first, ec); - if(ec || last_write_time != path_and_last_write_time.second) { - // std::cout << "updated file: " << path_and_last_write_time.first << ", included from " << path << std::endl; - return false; +bool +Usages::Clang::add_usages_from_cache(const boost::filesystem::path &path, std::vector<Usages> &usages, PathSet &visited, + const std::string &spelling, const clangmm::Cursor &cursor, const Cache &cache) { + for (auto &path_and_last_write_time : cache.paths_and_last_write_times) { + boost::system::error_code ec; + auto last_write_time = boost::filesystem::last_write_time(path_and_last_write_time.first, ec); + if (ec || last_write_time != path_and_last_write_time.second) { + // std::cout << "updated file: " << path_and_last_write_time.first << ", included from " << path << std::endl; + return false; + } } - } - - auto offsets = cache.get_similar_token_offsets(cursor.get_kind(), spelling, cursor.get_all_usr_extended()); - - std::vector<std::string> lines; - for(auto &offset : offsets) { - std::string line; - auto line_nr = offset.second.line; - for(auto &token : cache.tokens) { - auto &offset = token.offsets.first; - if(offset.line == line_nr) { - while(line.size() < offset.index - 1) - line += ' '; - line += token.spelling; - } + + auto offsets = cache.get_similar_token_offsets(cursor.get_kind(), spelling, cursor.get_all_usr_extended()); + + std::vector<std::string> lines; + for (auto &offset : offsets) { + std::string line; + auto line_nr = offset.second.line; + for (auto &token : cache.tokens) { + auto &offset = token.offsets.first; + if (offset.line == line_nr) { + while (line.size() < offset.index - 1) + line += ' '; + line += token.spelling; + } + } + lines.emplace_back(std::move(line)); } - lines.emplace_back(std::move(line)); - } - visited.emplace(path); - if(!offsets.empty()) - usages.emplace_back(Usages{path, std::move(offsets), lines}); - return true; + visited.emplace(path); + if (!offsets.empty()) + usages.emplace_back(Usages{path, std::move(offsets), lines}); + return true; } -void Usages::Clang::add_usages_from_includes(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, - std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, const clangmm::Cursor &cursor, +void Usages::Clang::add_usages_from_includes(const boost::filesystem::path &project_path, + const boost::filesystem::path &build_path, + std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, + const clangmm::Cursor &cursor, clangmm::TranslationUnit *translation_unit, bool store_in_cache) { - if(project_path.empty()) - return; - - class VisitorData { - public: - const boost::filesystem::path &project_path; - const std::string &spelling; - PathSet &visited; - PathSet paths; - }; - VisitorData visitor_data{project_path, spelling, visited, {}}; - - auto translation_unit_cursor = clang_getTranslationUnitCursor(translation_unit->cx_tu); - clang_visitChildren(translation_unit_cursor, [](CXCursor cx_cursor, CXCursor cx_parent, CXClientData data) { - auto visitor_data = static_cast<VisitorData *>(data); - - auto path = filesystem::get_normal_path(clangmm::Cursor(cx_cursor).get_source_location().get_path()); - if(visitor_data->visited.find(path) == visitor_data->visited.end() && filesystem::file_in_path(path, visitor_data->project_path)) - visitor_data->paths.emplace(path); - - return CXChildVisit_Continue; - }, - &visitor_data); - - for(auto &path : visitor_data.paths) - add_usages(project_path, build_path, path, usages, visited, spelling, cursor, translation_unit, store_in_cache); + if (project_path.empty()) + return; + + class VisitorData { + public: + const boost::filesystem::path &project_path; + const std::string &spelling; + PathSet &visited; + PathSet paths; + }; + VisitorData visitor_data{project_path, spelling, visited, {}}; + + auto translation_unit_cursor = clang_getTranslationUnitCursor(translation_unit->cx_tu); + clang_visitChildren(translation_unit_cursor, [](CXCursor cx_cursor, CXCursor cx_parent, CXClientData data) { + auto visitor_data = static_cast<VisitorData *>(data); + + auto path = filesystem::get_normal_path(clangmm::Cursor(cx_cursor).get_source_location().get_path()); + if (visitor_data->visited.find(path) == visitor_data->visited.end() && + filesystem::file_in_path(path, visitor_data->project_path)) + visitor_data->paths.emplace(path); + + return CXChildVisit_Continue; + }, + &visitor_data); + + for (auto &path : visitor_data.paths) + add_usages(project_path, build_path, path, usages, visited, spelling, cursor, translation_unit, store_in_cache); } Usages::Clang::PathSet Usages::Clang::find_paths(const boost::filesystem::path &project_path, - const boost::filesystem::path &build_path, const boost::filesystem::path &debug_path) { - PathSet paths; + const boost::filesystem::path &build_path, + const boost::filesystem::path &debug_path) { + PathSet paths; - CompileCommands compile_commands(build_path); + CompileCommands compile_commands(build_path); - for(boost::filesystem::recursive_directory_iterator it(project_path), end; it != end; ++it) { - auto &path = it->path(); - if(!boost::filesystem::is_regular_file(path)) { - if(path == build_path || path == debug_path || path.filename() == ".git") - it.no_push(); - continue; - } + for (boost::filesystem::recursive_directory_iterator it(project_path), end; it != end; ++it) { + auto &path = it->path(); + if (!boost::filesystem::is_regular_file(path)) { + if (path == build_path || path == debug_path || path.filename() == ".git") + it.no_push(); + continue; + } - if(is_header(path)) - paths.emplace(path); - else if(is_source(path)) { - for(auto &command : compile_commands.commands) { - if(filesystem::get_normal_path(command.file) == path) { - paths.emplace(path); - break; + if (is_header(path)) + paths.emplace(path); + else if (is_source(path)) { + for (auto &command : compile_commands.commands) { + if (filesystem::get_normal_path(command.file) == path) { + paths.emplace(path); + break; + } + } } - } } - } - return paths; + return paths; } bool Usages::Clang::is_header(const boost::filesystem::path &path) { - auto ext = path.extension(); - if(ext == ".h" || // c headers - ext == ".hh" || ext == ".hp" || ext == ".hpp" || ext == ".h++" || ext == ".tcc" || // c++ headers - ext == ".cuh") // CUDA headers - return true; - else - return false; + auto ext = path.extension(); + if (ext == ".h" || // c headers + ext == ".hh" || ext == ".hp" || ext == ".hpp" || ext == ".h++" || ext == ".tcc" || // c++ headers + ext == ".cuh") // CUDA headers + return true; + else + return false; } bool Usages::Clang::is_source(const boost::filesystem::path &path) { - auto ext = path.extension(); - if(ext == ".c" || // c sources - ext == ".cpp" || ext == ".cxx" || ext == ".cc" || ext == ".C" || ext == ".c++" || // c++ sources - ext == ".cu" || // CUDA sources - ext == ".cl") // OpenCL sources - return true; - else - return false; + auto ext = path.extension(); + if (ext == ".c" || // c sources + ext == ".cpp" || ext == ".cxx" || ext == ".cc" || ext == ".C" || ext == ".c++" || // c++ sources + ext == ".cu" || // CUDA sources + ext == ".cl") // OpenCL sources + return true; + else + return false; } -std::pair<std::map<boost::filesystem::path, Usages::Clang::PathSet>, Usages::Clang::PathSet> Usages::Clang::parse_paths(const std::string &spelling, const PathSet &paths) { - std::map<boost::filesystem::path, PathSet> paths_includes; - PathSet paths_with_spelling; - - const static std::regex include_regex("^[ \t]*#[ \t]*include[ \t]*[\"]([^\"]+)[\"].*$"); - - auto is_spelling_char = [](char chr) { - return (chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z') || (chr >= '0' && chr <= '9') || chr == '_'; - }; - - for(auto &path : paths) { - auto paths_includes_it = paths_includes.emplace(path, PathSet()).first; - bool paths_with_spelling_emplaced = false; - - std::ifstream stream(path.string(), std::ifstream::binary); - if(!stream) - continue; - std::string line; - while(std::getline(stream, line)) { - std::smatch sm; - if(std::regex_match(line, sm, include_regex)) { - boost::filesystem::path path(sm[1].str()); - boost::filesystem::path include_path; - // remove .. and . - for(auto &part : path) { - if(part == "..") - include_path = include_path.parent_path(); - else if(part == ".") +std::pair<std::map<boost::filesystem::path, Usages::Clang::PathSet>, Usages::Clang::PathSet> +Usages::Clang::parse_paths(const std::string &spelling, const PathSet &paths) { + std::map<boost::filesystem::path, PathSet> paths_includes; + PathSet paths_with_spelling; + + const static std::regex include_regex("^[ \t]*#[ \t]*include[ \t]*[\"]([^\"]+)[\"].*$"); + + auto is_spelling_char = [](char chr) { + return (chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z') || (chr >= '0' && chr <= '9') || chr == '_'; + }; + + for (auto &path : paths) { + auto paths_includes_it = paths_includes.emplace(path, PathSet()).first; + bool paths_with_spelling_emplaced = false; + + std::ifstream stream(path.string(), std::ifstream::binary); + if (!stream) continue; - else - include_path /= part; + std::string line; + while (std::getline(stream, line)) { + std::smatch sm; + if (std::regex_match(line, sm, include_regex)) { + boost::filesystem::path path(sm[1].str()); + boost::filesystem::path include_path; + // remove .. and . + for (auto &part : path) { + if (part == "..") + include_path = include_path.parent_path(); + else if (part == ".") + continue; + else + include_path /= part; + } + auto distance = std::distance(include_path.begin(), include_path.end()); + for (auto &path : paths) { + auto path_distance = std::distance(path.begin(), path.end()); + if (path_distance >= distance) { + auto it = path.begin(); + std::advance(it, path_distance - distance); + if (std::equal(it, path.end(), include_path.begin(), include_path.end())) + paths_includes_it->second.emplace(path); + } + } + } else if (!paths_with_spelling_emplaced) { + auto pos = line.find(spelling); + if (pos != std::string::npos && + ((!spelling.empty() && !is_spelling_char(spelling[0])) || + ((pos == 0 || !is_spelling_char(line[pos - 1])) && + (pos + spelling.size() >= line.size() - 1 || !is_spelling_char(line[pos + spelling.size()]))))) { + paths_with_spelling.emplace(path); + paths_with_spelling_emplaced = true; + } + } } - auto distance = std::distance(include_path.begin(), include_path.end()); - for(auto &path : paths) { - auto path_distance = std::distance(path.begin(), path.end()); - if(path_distance >= distance) { - auto it = path.begin(); - std::advance(it, path_distance - distance); - if(std::equal(it, path.end(), include_path.begin(), include_path.end())) - paths_includes_it->second.emplace(path); - } - } - } - else if(!paths_with_spelling_emplaced) { - auto pos = line.find(spelling); - if(pos != std::string::npos && - ((!spelling.empty() && !is_spelling_char(spelling[0])) || - ((pos == 0 || !is_spelling_char(line[pos - 1])) && - (pos + spelling.size() >= line.size() - 1 || !is_spelling_char(line[pos + spelling.size()]))))) { - paths_with_spelling.emplace(path); - paths_with_spelling_emplaced = true; - } - } } - } - return {paths_includes, paths_with_spelling}; + return {paths_includes, paths_with_spelling}; } -Usages::Clang::PathSet Usages::Clang::get_all_includes(const boost::filesystem::path &path, const std::map<boost::filesystem::path, PathSet> &paths_includes) { - PathSet all_includes; - - class Recursive { - public: - static void f(PathSet &all_includes, const boost::filesystem::path &path, - const std::map<boost::filesystem::path, PathSet> &paths_includes) { - auto paths_includes_it = paths_includes.find(path); - if(paths_includes_it != paths_includes.end()) { - for(auto &include : paths_includes_it->second) { - auto pair = all_includes.emplace(include); - if(pair.second) - f(all_includes, include, paths_includes); +Usages::Clang::PathSet Usages::Clang::get_all_includes(const boost::filesystem::path &path, + const std::map<boost::filesystem::path, PathSet> &paths_includes) { + PathSet all_includes; + + class Recursive { + public: + static void f(PathSet &all_includes, const boost::filesystem::path &path, + const std::map<boost::filesystem::path, PathSet> &paths_includes) { + auto paths_includes_it = paths_includes.find(path); + if (paths_includes_it != paths_includes.end()) { + for (auto &include : paths_includes_it->second) { + auto pair = all_includes.emplace(include); + if (pair.second) + f(all_includes, include, paths_includes); + } + } } - } - } - }; - Recursive::f(all_includes, path, paths_includes); + }; + Recursive::f(all_includes, path, paths_includes); - return all_includes; + return all_includes; } -std::pair<Usages::Clang::PathSet, Usages::Clang::PathSet> Usages::Clang::find_potential_paths(const PathSet &paths, const boost::filesystem::path &project_path, - const std::map<boost::filesystem::path, PathSet> &paths_includes, const PathSet &paths_with_spelling) { - PathSet potential_paths; - PathSet all_includes; - - bool first=true; - for(auto &path: paths) { - if(filesystem::file_in_path(path, project_path)) { - for(auto &path_with_spelling : paths_with_spelling) { - auto path_all_includes = get_all_includes(path_with_spelling, paths_includes); - if((path_all_includes.find(path) != path_all_includes.end() || path_with_spelling == path)) { - potential_paths.emplace(path_with_spelling); - - for(auto &include : path_all_includes) - all_includes.emplace(include); +std::pair<Usages::Clang::PathSet, Usages::Clang::PathSet> +Usages::Clang::find_potential_paths(const PathSet &paths, const boost::filesystem::path &project_path, + const std::map<boost::filesystem::path, PathSet> &paths_includes, + const PathSet &paths_with_spelling) { + PathSet potential_paths; + PathSet all_includes; + + bool first = true; + for (auto &path: paths) { + if (filesystem::file_in_path(path, project_path)) { + for (auto &path_with_spelling : paths_with_spelling) { + auto path_all_includes = get_all_includes(path_with_spelling, paths_includes); + if ((path_all_includes.find(path) != path_all_includes.end() || path_with_spelling == path)) { + potential_paths.emplace(path_with_spelling); + + for (auto &include : path_all_includes) + all_includes.emplace(include); + } + } + } else { + if (first) { + for (auto &path_with_spelling : paths_with_spelling) { + potential_paths.emplace(path_with_spelling); + + auto path_all_includes = get_all_includes(path_with_spelling, paths_includes); + for (auto &include : path_all_includes) + all_includes.emplace(include); + } + first = false; + } } - } } - else { - if(first) { - for(auto &path_with_spelling : paths_with_spelling) { - potential_paths.emplace(path_with_spelling); - - auto path_all_includes = get_all_includes(path_with_spelling, paths_includes); - for(auto &include : path_all_includes) - all_includes.emplace(include); - } - first=false; - } - } - } - return {potential_paths, all_includes}; + return {potential_paths, all_includes}; } void Usages::Clang::write_cache(const boost::filesystem::path &path, const Clang::Cache &cache) { - auto cache_path = cache.build_path / cache_folder; - boost::system::error_code ec; - if(!boost::filesystem::exists(cache_path, ec)) { - boost::filesystem::create_directory(cache_path, ec); - if(ec) - return; - } - else if(!boost::filesystem::is_directory(cache_path, ec) || ec) - return; - - auto path_str = filesystem::get_relative_path(path, cache.project_path).string(); - for(auto &chr : path_str) { - if(chr == '/' || chr == '\\') - chr = '_'; - } - path_str += ".usages"; - - auto full_cache_path = cache_path / path_str; - auto tmp_file = boost::filesystem::temp_directory_path(ec); - if(ec) - return; - tmp_file /= ("jucipp" + std::to_string(get_current_process_id()) + path_str); - - std::ofstream stream(tmp_file.string()); - if(stream) { - try { - boost::archive::text_oarchive text_oarchive(stream); - text_oarchive << cache; - stream.close(); - boost::filesystem::rename(tmp_file, full_cache_path, ec); - if(ec) { - boost::filesystem::copy_file(tmp_file, full_cache_path, boost::filesystem::copy_option::overwrite_if_exists); - boost::filesystem::remove(tmp_file, ec); - } + auto cache_path = cache.build_path / cache_folder; + boost::system::error_code ec; + if (!boost::filesystem::exists(cache_path, ec)) { + boost::filesystem::create_directory(cache_path, ec); + if (ec) + return; + } else if (!boost::filesystem::is_directory(cache_path, ec) || ec) + return; + + auto path_str = filesystem::get_relative_path(path, cache.project_path).string(); + for (auto &chr : path_str) { + if (chr == '/' || chr == '\\') + chr = '_'; } - catch(...) { - boost::filesystem::remove(tmp_file, ec); + path_str += ".usages"; + + auto full_cache_path = cache_path / path_str; + auto tmp_file = boost::filesystem::temp_directory_path(ec); + if (ec) + return; + tmp_file /= ("jucipp" + std::to_string(get_current_process_id()) + path_str); + + std::ofstream stream(tmp_file.string()); + if (stream) { + try { + boost::archive::text_oarchive text_oarchive(stream); + text_oarchive << cache; + stream.close(); + boost::filesystem::rename(tmp_file, full_cache_path, ec); + if (ec) { + boost::filesystem::copy_file(tmp_file, full_cache_path, + boost::filesystem::copy_option::overwrite_if_exists); + boost::filesystem::remove(tmp_file, ec); + } + } + catch (...) { + boost::filesystem::remove(tmp_file, ec); + } } - } } -Usages::Clang::Cache Usages::Clang::read_cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path) { - auto path_str = filesystem::get_relative_path(path, project_path).string(); - for(auto &chr : path_str) { - if(chr == '/' || chr == '\\') - chr = '_'; - } - auto cache_path = build_path / cache_folder / (path_str + ".usages"); - - boost::system::error_code ec; - if(boost::filesystem::exists(cache_path, ec)) { - std::ifstream stream(cache_path.string()); - if(stream) { - Cache cache; - boost::archive::text_iarchive text_iarchive(stream); - try { - text_iarchive >> cache; - return cache; - } - catch(...) { - } +Usages::Clang::Cache +Usages::Clang::read_cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, + const boost::filesystem::path &path) { + auto path_str = filesystem::get_relative_path(path, project_path).string(); + for (auto &chr : path_str) { + if (chr == '/' || chr == '\\') + chr = '_'; + } + auto cache_path = build_path / cache_folder / (path_str + ".usages"); + + boost::system::error_code ec; + if (boost::filesystem::exists(cache_path, ec)) { + std::ifstream stream(cache_path.string()); + if (stream) { + Cache cache; + boost::archive::text_iarchive text_iarchive(stream); + try { + text_iarchive >> cache; + return cache; + } + catch (...) { + } + } } - } - return Cache(); + return Cache(); } diff --git a/src/usages_clang.h b/src/usages_clang.h index 492a21c7..61f127fa 100644 --- a/src/usages_clang.h +++ b/src/usages_clang.h @@ -1,4 +1,5 @@ #pragma once + #include "clangmm.h" #include <atomic> #include <boost/archive/text_iarchive.hpp> @@ -14,138 +15,167 @@ #include <unordered_set> namespace boost { - namespace serialization { - template <class Archive> - void serialize(Archive &ar, boost::filesystem::path &path, const unsigned int version) { - std::string path_str; - if(Archive::is_saving::value) - path_str = path.string(); - ar &path_str; - if(Archive::is_loading::value) - path = path_str; - } - } // namespace serialization + namespace serialization { + template<class Archive> + void serialize(Archive &ar, boost::filesystem::path &path, const unsigned int version) { + std::string path_str; + if (Archive::is_saving::value) + path_str = path.string(); + ar & path_str; + if (Archive::is_loading::value) + path = path_str; + } + } // namespace serialization } // namespace boost namespace Usages { - class Clang { - public: - typedef std::set<boost::filesystem::path> PathSet; - - class Usages { + class Clang { public: - boost::filesystem::path path; - std::vector<std::pair<clangmm::Offset, clangmm::Offset>> offsets; - std::vector<std::string> lines; - }; - - class Cache { - friend class boost::serialization::access; - template <class Archive> - void serialize(Archive &ar, const unsigned int version) { - ar &project_path; - ar &build_path; - ar &tokens; - ar &cursors; - ar &paths_and_last_write_times; - } + typedef std::set<boost::filesystem::path> PathSet; + + class Usages { + public: + boost::filesystem::path path; + std::vector<std::pair<clangmm::Offset, clangmm::Offset>> offsets; + std::vector<std::string> lines; + }; + + class Cache { + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive &ar, const unsigned int version) { + ar & project_path; + ar & build_path; + ar & tokens; + ar & cursors; + ar & paths_and_last_write_times; + } + + public: + class Cursor { + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive &ar, const unsigned int version) { + ar & kind; + ar & usrs; + } + + public: + clangmm::Cursor::Kind kind; + std::unordered_set<std::string> usrs; + + bool operator==(const Cursor &o); + }; + + class Token { + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive &ar, const unsigned int version) { + ar & spelling; + ar & offsets.first.line & offsets.first.index; + ar & offsets.second.line & offsets.second.index; + ar & cursor_id; + } + + public: + std::string spelling; + std::pair<clangmm::Offset, clangmm::Offset> offsets; + size_t cursor_id; + }; + + boost::filesystem::path project_path; + boost::filesystem::path build_path; + + std::vector<Token> tokens; + std::vector<Cursor> cursors; + std::map<boost::filesystem::path, std::time_t> paths_and_last_write_times; + + Cache() {} + + Cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, + const boost::filesystem::path &path, + std::time_t before_parse_time, clangmm::TranslationUnit *translation_unit, + clangmm::Tokens *clang_tokens); + + operator bool() const { return !paths_and_last_write_times.empty(); } + + std::vector<std::pair<clangmm::Offset, clangmm::Offset>> + get_similar_token_offsets(clangmm::Cursor::Kind kind, const std::string &spelling, + const std::unordered_set<std::string> &usrs) const; + }; + + private: + const static boost::filesystem::path cache_folder; + + static std::map<boost::filesystem::path, Cache> caches; + static std::mutex caches_mutex; + + static std::atomic<size_t> cache_in_progress_count; public: - class Cursor { - friend class boost::serialization::access; - template <class Archive> - void serialize(Archive &ar, const unsigned int version) { - ar &kind; - ar &usrs; - } - - public: - clangmm::Cursor::Kind kind; - std::unordered_set<std::string> usrs; - - bool operator==(const Cursor &o); - }; - - class Token { - friend class boost::serialization::access; - template <class Archive> - void serialize(Archive &ar, const unsigned int version) { - ar &spelling; - ar &offsets.first.line &offsets.first.index; - ar &offsets.second.line &offsets.second.index; - ar &cursor_id; - } - - public: - std::string spelling; - std::pair<clangmm::Offset, clangmm::Offset> offsets; - size_t cursor_id; - }; + static std::vector<Usages> + get_usages(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, + const boost::filesystem::path &debug_path, + const std::string &spelling, const clangmm::Cursor &cursor, + const std::vector<clangmm::TranslationUnit *> &translation_units); - boost::filesystem::path project_path; - boost::filesystem::path build_path; + static void cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, + const boost::filesystem::path &path, + std::time_t before_parse_time, const PathSet &project_paths_in_use, + clangmm::TranslationUnit *translation_unit, clangmm::Tokens *tokens); - std::vector<Token> tokens; - std::vector<Cursor> cursors; - std::map<boost::filesystem::path, std::time_t> paths_and_last_write_times; + static void erase_unused_caches(const PathSet &project_paths_in_use); - Cache() {} - Cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path, - std::time_t before_parse_time, clangmm::TranslationUnit *translation_unit, clangmm::Tokens *clang_tokens); + static void erase_cache(const boost::filesystem::path &path); - operator bool() const { return !paths_and_last_write_times.empty(); } - - std::vector<std::pair<clangmm::Offset, clangmm::Offset>> get_similar_token_offsets(clangmm::Cursor::Kind kind, const std::string &spelling, - const std::unordered_set<std::string> &usrs) const; - }; + static void erase_all_caches_for_project(const boost::filesystem::path &project_path, + const boost::filesystem::path &build_path); - private: - const static boost::filesystem::path cache_folder; + static void cache_in_progress(); - static std::map<boost::filesystem::path, Cache> caches; - static std::mutex caches_mutex; + private: + static void add_usages(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, + const boost::filesystem::path &path_, + std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, + clangmm::Cursor cursor, + clangmm::TranslationUnit *translation_unit, bool store_in_cache); - static std::atomic<size_t> cache_in_progress_count; + static bool + add_usages_from_cache(const boost::filesystem::path &path, std::vector<Usages> &usages, PathSet &visited, + const std::string &spelling, const clangmm::Cursor &cursor, const Cache &cache); - public: - static std::vector<Usages> get_usages(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &debug_path, - const std::string &spelling, const clangmm::Cursor &cursor, const std::vector<clangmm::TranslationUnit *> &translation_units); + static void + add_usages_from_includes(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, + std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, + const clangmm::Cursor &cursor, + clangmm::TranslationUnit *translation_unit, bool store_in_cache); - static void cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path, - std::time_t before_parse_time, const PathSet &project_paths_in_use, clangmm::TranslationUnit *translation_unit, clangmm::Tokens *tokens); - static void erase_unused_caches(const PathSet &project_paths_in_use); - static void erase_cache(const boost::filesystem::path &path); - static void erase_all_caches_for_project(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path); - static void cache_in_progress(); + static PathSet find_paths(const boost::filesystem::path &project_path, + const boost::filesystem::path &build_path, const boost::filesystem::path &debug_path); - private: - static void add_usages(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path_, - std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, clangmm::Cursor cursor, - clangmm::TranslationUnit *translation_unit, bool store_in_cache); + static bool is_header(const boost::filesystem::path &path); - static bool add_usages_from_cache(const boost::filesystem::path &path, std::vector<Usages> &usages, PathSet &visited, - const std::string &spelling, const clangmm::Cursor &cursor, const Cache &cache); + static bool is_source(const boost::filesystem::path &path); - static void add_usages_from_includes(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, - std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, const clangmm::Cursor &cursor, - clangmm::TranslationUnit *translation_unit, bool store_in_cache); + static std::pair<std::map<boost::filesystem::path, PathSet>, PathSet> + parse_paths(const std::string &spelling, const PathSet &paths); - static PathSet find_paths(const boost::filesystem::path &project_path, - const boost::filesystem::path &build_path, const boost::filesystem::path &debug_path); + /// Recursively find and return all the include paths of path + static PathSet get_all_includes(const boost::filesystem::path &path, + const std::map<boost::filesystem::path, PathSet> &paths_includes); - static bool is_header(const boost::filesystem::path &path); - static bool is_source(const boost::filesystem::path &path); + /// Based on cursor paths, paths_includes and paths_with_spelling return potential paths that might contain the sought after symbol + static std::pair<Clang::PathSet, Clang::PathSet> + find_potential_paths(const PathSet &paths, const boost::filesystem::path &project_path, + const std::map<boost::filesystem::path, PathSet> &paths_includes, + const PathSet &paths_with_spelling); - static std::pair<std::map<boost::filesystem::path, PathSet>, PathSet> parse_paths(const std::string &spelling, const PathSet &paths); + static void write_cache(const boost::filesystem::path &path, const Cache &cache); - /// Recursively find and return all the include paths of path - static PathSet get_all_includes(const boost::filesystem::path &path, const std::map<boost::filesystem::path, PathSet> &paths_includes); - - /// Based on cursor paths, paths_includes and paths_with_spelling return potential paths that might contain the sought after symbol - static std::pair<Clang::PathSet, Clang::PathSet> find_potential_paths(const PathSet &paths, const boost::filesystem::path &project_path, - const std::map<boost::filesystem::path, PathSet> &paths_includes, const PathSet &paths_with_spelling); - - static void write_cache(const boost::filesystem::path &path, const Cache &cache); - static Cache read_cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path); - }; + static Cache read_cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, + const boost::filesystem::path &path); + }; } // namespace Usages diff --git a/src/window.cc b/src/window.cc index e9f3311c..9371207a 100644 --- a/src/window.cc +++ b/src/window.cc @@ -12,1752 +12,1776 @@ #include "terminal.h" Window::Window() { - Gsv::init(); - - set_title("juCi++"); - set_events(Gdk::POINTER_MOTION_MASK|Gdk::FOCUS_CHANGE_MASK|Gdk::SCROLL_MASK|Gdk::LEAVE_NOTIFY_MASK); - - auto provider = Gtk::CssProvider::create(); - auto screen = get_screen(); - std::string border_radius_style; - if(screen->get_rgba_visual()) - border_radius_style="border-radius: 5px; "; + Gsv::init(); + + set_title("juCi++"); + set_events(Gdk::POINTER_MOTION_MASK | Gdk::FOCUS_CHANGE_MASK | Gdk::SCROLL_MASK | Gdk::LEAVE_NOTIFY_MASK); + + auto provider = Gtk::CssProvider::create(); + auto screen = get_screen(); + std::string border_radius_style; + if (screen->get_rgba_visual()) + border_radius_style = "border-radius: 5px; "; #if GTK_VERSION_GE(3, 20) - std::string notebook_style(".juci_notebook tab {border-radius: 5px 5px 0 0; padding: 0 4px; margin: 0;}"); + std::string notebook_style(".juci_notebook tab {border-radius: 5px 5px 0 0; padding: 0 4px; margin: 0;}"); #else - std::string notebook_style(".juci_notebook {-GtkNotebook-tab-overlap: 0px;} .juci_notebook tab {border-radius: 5px 5px 0 0; padding: 4px 4px;}"); + std::string notebook_style(".juci_notebook {-GtkNotebook-tab-overlap: 0px;} .juci_notebook tab {border-radius: 5px 5px 0 0; padding: 4px 4px;}"); #endif - provider->load_from_data(R"( + provider->load_from_data(R"( .juci_directories *:selected {border-left-color: inherit; color: inherit; background-color: rgba(128, 128, 128 , 0.2); background-image: inherit;} - )"+notebook_style+R"( + )" + notebook_style + R"( .juci_info {border-radius: 5px;} .juci_tooltip_window {background-color: transparent;} - .juci_tooltip_box {)"+border_radius_style+R"(padding: 3px;} + .juci_tooltip_box {)" + border_radius_style + R"(padding: 3px;} )"); - get_style_context()->add_provider_for_screen(screen, provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); - - set_menu_actions(); - configure(); - Menu::get().toggle_menu_items(); - - Menu::get().right_click_line_menu->attach_to_widget(*this); - Menu::get().right_click_selected_menu->attach_to_widget(*this); - - EntryBox::get().signal_hide().connect([]() { - if(auto view=Notebook::get().get_current_view()) - view->grab_focus(); - }); - - Notebook::get().on_change_page=[this](Source::View *view) { - if(search_entry_shown && EntryBox::get().labels.size()>0) { - view->update_search_occurrences=[](int number){ - EntryBox::get().labels.begin()->update(0, std::to_string(number)); - }; - view->search_highlight(last_search, case_sensitive_search, regex_search); - } + get_style_context()->add_provider_for_screen(screen, provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + set_menu_actions(); + configure(); Menu::get().toggle_menu_items(); - - Directories::get().select(view->file_path); - - if(view->full_reparse_needed) - view->full_reparse(); - else if(view->soft_reparse_needed) - view->soft_reparse(); - - Notebook::get().update_status(view); - + + Menu::get().right_click_line_menu->attach_to_widget(*this); + Menu::get().right_click_selected_menu->attach_to_widget(*this); + + EntryBox::get().signal_hide().connect([]() { + if (auto view = Notebook::get().get_current_view()) + view->grab_focus(); + }); + + Notebook::get().on_change_page = [this](Source::View *view) { + if (search_entry_shown && EntryBox::get().labels.size() > 0) { + view->update_search_occurrences = [](int number) { + EntryBox::get().labels.begin()->update(0, std::to_string(number)); + }; + view->search_highlight(last_search, case_sensitive_search, regex_search); + } + + Menu::get().toggle_menu_items(); + + Directories::get().select(view->file_path); + + if (view->full_reparse_needed) + view->full_reparse(); + else if (view->soft_reparse_needed) + view->soft_reparse(); + + Notebook::get().update_status(view); + #ifdef JUCI_ENABLE_DEBUG - if(Project::debugging) - Project::debug_update_stop(); + if(Project::debugging) + Project::debug_update_stop(); #endif - }; - Notebook::get().on_close_page=[](Source::View *view) { + }; + Notebook::get().on_close_page = [](Source::View *view) { #ifdef JUCI_ENABLE_DEBUG - if(Project::current && Project::debugging) { - auto iter=view->get_buffer()->begin(); - while(view->get_source_buffer()->forward_iter_to_source_mark(iter, "debug_breakpoint") || - view->get_source_buffer()->get_source_marks_at_iter(iter, "debug_breakpoint").size()) { - auto end_iter=view->get_iter_at_line_end(iter.get_line()); - view->get_source_buffer()->remove_source_marks(iter, end_iter, "debug_breakpoint"); - Project::current->debug_remove_breakpoint(view->file_path, iter.get_line()+1, view->get_buffer()->get_line_count()+1); - } - } + if(Project::current && Project::debugging) { + auto iter=view->get_buffer()->begin(); + while(view->get_source_buffer()->forward_iter_to_source_mark(iter, "debug_breakpoint") || + view->get_source_buffer()->get_source_marks_at_iter(iter, "debug_breakpoint").size()) { + auto end_iter=view->get_iter_at_line_end(iter.get_line()); + view->get_source_buffer()->remove_source_marks(iter, end_iter, "debug_breakpoint"); + Project::current->debug_remove_breakpoint(view->file_path, iter.get_line()+1, view->get_buffer()->get_line_count()+1); + } + } #endif - EntryBox::get().hide(); - if(auto view=Notebook::get().get_current_view()) - Notebook::get().update_status(view); - else { - Notebook::get().clear_status(); - - Menu::get().toggle_menu_items(); - } - }; - - signal_focus_out_event().connect([](GdkEventFocus *event) { - if(auto view=Notebook::get().get_current_view()) { - view->hide_tooltips(); - view->hide_dialogs(); - } - return false; - }); - - signal_hide().connect([]{ - while(!Source::View::non_deleted_views.empty()) { - while(Gtk::Main::events_pending()) - Gtk::Main::iteration(false); - } - // TODO 2022 (after Debian Stretch LTS has ended, see issue #354): remove: - Project::current=nullptr; - }); - - Gtk::Settings::get_default()->connect_property_changed("gtk-theme-name", [] { - Directories::get().update(); - if(auto view=Notebook::get().get_current_view()) - Notebook::get().update_status(view); - }); - - about.signal_response().connect([this](int d){ - about.hide(); - }); - - about.set_logo_icon_name("juci"); - about.set_version(Config::get().window.version); - about.set_authors({"(in order of appearance)", - "Ted Johan Kristoffersen", - "Jørgen Lien Sellæg", - "Geir Morten Larsen", - "Ole Christian Eidheim"}); - about.set_name("About juCi++"); - about.set_program_name("juCi++"); - about.set_comments("This is an open source IDE with high-end features to make your programming experience juicy"); - about.set_license_type(Gtk::License::LICENSE_MIT_X11); - about.set_transient_for(*this); + EntryBox::get().hide(); + if (auto view = Notebook::get().get_current_view()) + Notebook::get().update_status(view); + else { + Notebook::get().clear_status(); + + Menu::get().toggle_menu_items(); + } + }; + + signal_focus_out_event().connect([](GdkEventFocus *event) { + if (auto view = Notebook::get().get_current_view()) { + view->hide_tooltips(); + view->hide_dialogs(); + } + return false; + }); + + signal_hide().connect([] { + while (!Source::View::non_deleted_views.empty()) { + while (Gtk::Main::events_pending()) + Gtk::Main::iteration(false); + } + // TODO 2022 (after Debian Stretch LTS has ended, see issue #354): remove: + Project::current = nullptr; + }); + + Gtk::Settings::get_default()->connect_property_changed("gtk-theme-name", [] { + Directories::get().update(); + if (auto view = Notebook::get().get_current_view()) + Notebook::get().update_status(view); + }); + + about.signal_response().connect([this](int d) { + about.hide(); + }); + + about.set_logo_icon_name("juci"); + about.set_version(Config::get().window.version); + about.set_authors({"(in order of appearance)", + "Ted Johan Kristoffersen", + "Jørgen Lien Sellæg", + "Geir Morten Larsen", + "Ole Christian Eidheim"}); + about.set_name("About juCi++"); + about.set_program_name("juCi++"); + about.set_comments("This is an open source IDE with high-end features to make your programming experience juicy"); + about.set_license_type(Gtk::License::LICENSE_MIT_X11); + about.set_transient_for(*this); } // Window constructor void Window::configure() { - Config::get().load(); - auto screen = get_screen(); - if(css_provider_theme) - Gtk::StyleContext::remove_provider_for_screen(screen, css_provider_theme); - if(Config::get().window.theme_name.empty()) { - css_provider_theme=Gtk::CssProvider::create(); - Gtk::Settings::get_default()->property_gtk_application_prefer_dark_theme()=(Config::get().window.theme_variant=="dark"); - } - else - css_provider_theme=Gtk::CssProvider::get_named(Config::get().window.theme_name, Config::get().window.theme_variant); - //TODO: add check if theme exists, or else write error to terminal - Gtk::StyleContext::add_provider_for_screen(screen, css_provider_theme, GTK_STYLE_PROVIDER_PRIORITY_SETTINGS); - - auto style_scheme_manager=Source::StyleSchemeManager::get_default(); - if(css_provider_tooltips) - Gtk::StyleContext::remove_provider_for_screen(screen, css_provider_tooltips); - else - css_provider_tooltips=Gtk::CssProvider::create(); - Glib::RefPtr<Gsv::Style> style; - if(Config::get().source.style.size()>0) { - auto scheme = style_scheme_manager->get_scheme(Config::get().source.style); - if(scheme) - style = scheme->get_style("def:note"); - else { - Terminal::get().print("Error: Could not find gtksourceview style: "+Config::get().source.style+'\n', true); + Config::get().load(); + auto screen = get_screen(); + if (css_provider_theme) + Gtk::StyleContext::remove_provider_for_screen(screen, css_provider_theme); + if (Config::get().window.theme_name.empty()) { + css_provider_theme = Gtk::CssProvider::create(); + Gtk::Settings::get_default()->property_gtk_application_prefer_dark_theme() = ( + Config::get().window.theme_variant == "dark"); + } else + css_provider_theme = Gtk::CssProvider::get_named(Config::get().window.theme_name, + Config::get().window.theme_variant); + //TODO: add check if theme exists, or else write error to terminal + Gtk::StyleContext::add_provider_for_screen(screen, css_provider_theme, GTK_STYLE_PROVIDER_PRIORITY_SETTINGS); + + auto style_scheme_manager = Source::StyleSchemeManager::get_default(); + if (css_provider_tooltips) + Gtk::StyleContext::remove_provider_for_screen(screen, css_provider_tooltips); + else + css_provider_tooltips = Gtk::CssProvider::create(); + Glib::RefPtr<Gsv::Style> style; + if (Config::get().source.style.size() > 0) { + auto scheme = style_scheme_manager->get_scheme(Config::get().source.style); + if (scheme) + style = scheme->get_style("def:note"); + else { + Terminal::get().print("Error: Could not find gtksourceview style: " + Config::get().source.style + '\n', + true); + } } - } - auto foreground_value = style && style->property_foreground_set() ? style->property_foreground().get_value() : get_style_context()->get_color().to_string(); - auto background_value = style && style->property_background_set() ? style->property_background().get_value() : get_style_context()->get_background_color().to_string(); + auto foreground_value = style && style->property_foreground_set() ? style->property_foreground().get_value() + : get_style_context()->get_color().to_string(); + auto background_value = style && style->property_background_set() ? style->property_background().get_value() + : get_style_context()->get_background_color().to_string(); #if GTK_VERSION_GE(3, 20) - css_provider_tooltips->load_from_data(".juci_tooltip_box {background-color: "+background_value+";}" - ".juci_tooltip_text_view text {color: "+foreground_value+";background-color: "+background_value+";}"); + css_provider_tooltips->load_from_data(".juci_tooltip_box {background-color: " + background_value + ";}" + ".juci_tooltip_text_view text {color: " + + foreground_value + ";background-color: " + background_value + ";}"); #else - css_provider_tooltips->load_from_data(".juci_tooltip_box {background-color: "+background_value+";}" - ".juci_tooltip_text_view *:not(:selected) {color: "+foreground_value+";background-color: "+background_value+";}"); + css_provider_tooltips->load_from_data(".juci_tooltip_box {background-color: "+background_value+";}" + ".juci_tooltip_text_view *:not(:selected) {color: "+foreground_value+";background-color: "+background_value+";}"); #endif - get_style_context()->add_provider_for_screen(screen, css_provider_tooltips, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); - - Menu::get().set_keys(); - Terminal::get().configure(); - Directories::get().update(); - if(auto view=Notebook::get().get_current_view()) - Notebook::get().update_status(view); + get_style_context()->add_provider_for_screen(screen, css_provider_tooltips, + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + Menu::get().set_keys(); + Terminal::get().configure(); + Directories::get().update(); + if (auto view = Notebook::get().get_current_view()) + Notebook::get().update_status(view); } void Window::set_menu_actions() { - auto &menu = Menu::get(); - - menu.add_action("about", [this]() { - about.show(); - about.present(); - }); - menu.add_action("preferences", []() { - Notebook::get().open(Config::get().home_juci_path/"config"/"config.json"); - }); - menu.add_action("quit", [this]() { - close(); - }); - - menu.add_action("file_new_file", []() { - boost::filesystem::path path = Dialog::new_file(Notebook::get().get_current_folder()); - if(path!="") { - if(boost::filesystem::exists(path)) { - Terminal::get().print("Error: "+path.string()+" already exists.\n", true); - } - else { - if(filesystem::write(path)) { - if(Directories::get().path!="") - Directories::get().update(); - Notebook::get().open(path); - if(Directories::get().path!="") - Directories::get().on_save_file(path); - Terminal::get().print("New file "+path.string()+" created.\n"); + auto &menu = Menu::get(); + + menu.add_action("about", [this]() { + about.show(); + about.present(); + }); + menu.add_action("preferences", []() { + Notebook::get().open(Config::get().home_juci_path / "config" / "config.json"); + }); + menu.add_action("quit", [this]() { + close(); + }); + + menu.add_action("file_new_file", []() { + boost::filesystem::path path = Dialog::new_file(Notebook::get().get_current_folder()); + if (path != "") { + if (boost::filesystem::exists(path)) { + Terminal::get().print("Error: " + path.string() + " already exists.\n", true); + } else { + if (filesystem::write(path)) { + if (Directories::get().path != "") + Directories::get().update(); + Notebook::get().open(path); + if (Directories::get().path != "") + Directories::get().on_save_file(path); + Terminal::get().print("New file " + path.string() + " created.\n"); + } else + Terminal::get().print("Error: could not create new file " + path.string() + ".\n", true); + } } - else - Terminal::get().print("Error: could not create new file "+path.string()+".\n", true); - } - } - }); - menu.add_action("file_new_folder", []() { - auto time_now=std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); - boost::filesystem::path path = Dialog::new_folder(Notebook::get().get_current_folder()); - if(path!="" && boost::filesystem::exists(path)) { - boost::system::error_code ec; - auto last_write_time=boost::filesystem::last_write_time(path, ec); - if(!ec && last_write_time>=time_now) { - if(Directories::get().path!="") - Directories::get().update(); - Terminal::get().print("New folder "+path.string()+" created.\n"); - } - else - Terminal::get().print("Error: "+path.string()+" already exists.\n", true); - Directories::get().select(path); - } - }); - menu.add_action("file_new_project_c", []() { - boost::filesystem::path project_path = Dialog::new_folder(Notebook::get().get_current_folder()); - if(project_path!="") { - auto project_name=project_path.filename().string(); - for(size_t c=0;c<project_name.size();c++) { - if(project_name[c]==' ') - project_name[c]='_'; - } - auto cmakelists_path=project_path; - cmakelists_path/="CMakeLists.txt"; - auto c_main_path=project_path; - c_main_path/="main.c"; - if(boost::filesystem::exists(cmakelists_path)) { - Terminal::get().print("Error: "+cmakelists_path.string()+" already exists.\n", true); - return; - } - if(boost::filesystem::exists(c_main_path)) { - Terminal::get().print("Error: "+c_main_path.string()+" already exists.\n", true); - return; - } - std::string cmakelists="cmake_minimum_required(VERSION 2.8)\n\nproject("+project_name+")\n\nset(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -std=c11 -Wall -Wextra\")\n\nadd_executable("+project_name+" main.c)\n"; - std::string c_main="#include <stdio.h>\n\nint main() {\n printf(\"Hello World!\\n\");\n}\n"; - if(filesystem::write(cmakelists_path, cmakelists) && filesystem::write(c_main_path, c_main)) { - Directories::get().open(project_path); - Notebook::get().open(c_main_path); - Directories::get().update(); - Terminal::get().print("C project "+project_name+" created.\n"); - } - else - Terminal::get().print("Error: Could not create project "+project_path.string()+"\n", true); - } - }); - menu.add_action("file_new_project_cpp", []() { - boost::filesystem::path project_path = Dialog::new_folder(Notebook::get().get_current_folder()); - if(project_path!="") { - auto project_name=project_path.filename().string(); - for(size_t c=0;c<project_name.size();c++) { - if(project_name[c]==' ') - project_name[c]='_'; - } - auto cmakelists_path=project_path; - cmakelists_path/="CMakeLists.txt"; - auto cpp_main_path=project_path; - cpp_main_path/="main.cpp"; - if(boost::filesystem::exists(cmakelists_path)) { - Terminal::get().print("Error: "+cmakelists_path.string()+" already exists.\n", true); - return; - } - if(boost::filesystem::exists(cpp_main_path)) { - Terminal::get().print("Error: "+cpp_main_path.string()+" already exists.\n", true); - return; - } - std::string cmakelists="cmake_minimum_required(VERSION 2.8)\n\nproject("+project_name+")\n\nset(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -std=c++1y -Wall -Wextra\")\n\nadd_executable("+project_name+" main.cpp)\n"; - std::string cpp_main="#include <iostream>\n\nint main() {\n std::cout << \"Hello World!\\n\";\n}\n"; - if(filesystem::write(cmakelists_path, cmakelists) && filesystem::write(cpp_main_path, cpp_main)) { - Directories::get().open(project_path); - Notebook::get().open(cpp_main_path); - Directories::get().update(); - Terminal::get().print("C++ project "+project_name+" created.\n"); - } - else - Terminal::get().print("Error: Could not create project "+project_path.string()+"\n", true); - } - }); - - menu.add_action("file_open_file", []() { - auto folder_path=Notebook::get().get_current_folder(); - if(auto view=Notebook::get().get_current_view()) { - if(!Directories::get().path.empty() && !filesystem::file_in_path(view->file_path, Directories::get().path)) - folder_path=view->file_path.parent_path(); - } - auto path=Dialog::open_file(folder_path); - if(path!="") - Notebook::get().open(path); - }); - menu.add_action("file_open_folder", []() { - auto path = Dialog::open_folder(Notebook::get().get_current_folder()); - if (path!="" && boost::filesystem::exists(path)) - Directories::get().open(path); - }); - - menu.add_action("file_reload_file", []() { - if(auto view=Notebook::get().get_current_view()) { - if(boost::filesystem::exists(view->file_path)) { - std::ifstream can_read(view->file_path.string()); - if(!can_read) { - Terminal::get().print("Error: could not read "+view->file_path.string()+"\n", true); - return; + }); + menu.add_action("file_new_folder", []() { + auto time_now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + boost::filesystem::path path = Dialog::new_folder(Notebook::get().get_current_folder()); + if (path != "" && boost::filesystem::exists(path)) { + boost::system::error_code ec; + auto last_write_time = boost::filesystem::last_write_time(path, ec); + if (!ec && last_write_time >= time_now) { + if (Directories::get().path != "") + Directories::get().update(); + Terminal::get().print("New folder " + path.string() + " created.\n"); + } else + Terminal::get().print("Error: " + path.string() + " already exists.\n", true); + Directories::get().select(path); } - can_read.close(); - } - else { - Terminal::get().print("Error: "+view->file_path.string()+" does not exist\n", true); - return; - } - - if(view->load()) - view->full_reparse(); - } - }); - - menu.add_action("file_save", [this]() { - if(auto view=Notebook::get().get_current_view()) { - if(Notebook::get().save_current()) { - if(view->file_path==Config::get().home_juci_path/"config"/"config.json") { - configure(); - for(size_t c=0;c<Notebook::get().size();c++) { - Notebook::get().get_view(c)->configure(); - Notebook::get().configure(c); - } + }); + menu.add_action("file_new_project_c", []() { + boost::filesystem::path project_path = Dialog::new_folder(Notebook::get().get_current_folder()); + if (project_path != "") { + auto project_name = project_path.filename().string(); + for (size_t c = 0; c < project_name.size(); c++) { + if (project_name[c] == ' ') + project_name[c] = '_'; + } + auto cmakelists_path = project_path; + cmakelists_path /= "CMakeLists.txt"; + auto c_main_path = project_path; + c_main_path /= "main.c"; + if (boost::filesystem::exists(cmakelists_path)) { + Terminal::get().print("Error: " + cmakelists_path.string() + " already exists.\n", true); + return; + } + if (boost::filesystem::exists(c_main_path)) { + Terminal::get().print("Error: " + c_main_path.string() + " already exists.\n", true); + return; + } + std::string cmakelists = "cmake_minimum_required(VERSION 2.8)\n\nproject(" + project_name + + ")\n\nset(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -std=c11 -Wall -Wextra\")\n\nadd_executable(" + + project_name + " main.c)\n"; + std::string c_main = "#include <stdio.h>\n\nint main() {\n printf(\"Hello World!\\n\");\n}\n"; + if (filesystem::write(cmakelists_path, cmakelists) && filesystem::write(c_main_path, c_main)) { + Directories::get().open(project_path); + Notebook::get().open(c_main_path); + Directories::get().update(); + Terminal::get().print("C project " + project_name + " created.\n"); + } else + Terminal::get().print("Error: Could not create project " + project_path.string() + "\n", true); } - } - } - }); - menu.add_action("file_save_as", []() { - if(auto view=Notebook::get().get_current_view()) { - auto path = Dialog::save_file_as(view->file_path); - if(path!="") { - std::ofstream file(path, std::ofstream::binary); - if(file) { - file << view->get_buffer()->get_text().raw(); - file.close(); - if(Directories::get().path!="") - Directories::get().update(); - Notebook::get().open(path); - Terminal::get().print("File saved to: " + Notebook::get().get_current_view()->file_path.string()+"\n"); + }); + menu.add_action("file_new_project_cpp", []() { + boost::filesystem::path project_path = Dialog::new_folder(Notebook::get().get_current_folder()); + if (project_path != "") { + auto project_name = project_path.filename().string(); + for (size_t c = 0; c < project_name.size(); c++) { + if (project_name[c] == ' ') + project_name[c] = '_'; + } + auto cmakelists_path = project_path; + cmakelists_path /= "CMakeLists.txt"; + auto cpp_main_path = project_path; + cpp_main_path /= "main.cpp"; + if (boost::filesystem::exists(cmakelists_path)) { + Terminal::get().print("Error: " + cmakelists_path.string() + " already exists.\n", true); + return; + } + if (boost::filesystem::exists(cpp_main_path)) { + Terminal::get().print("Error: " + cpp_main_path.string() + " already exists.\n", true); + return; + } + std::string cmakelists = "cmake_minimum_required(VERSION 2.8)\n\nproject(" + project_name + + ")\n\nset(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -std=c++1y -Wall -Wextra\")\n\nadd_executable(" + + project_name + " main.cpp)\n"; + std::string cpp_main = "#include <iostream>\n\nint main() {\n std::cout << \"Hello World!\\n\";\n}\n"; + if (filesystem::write(cmakelists_path, cmakelists) && filesystem::write(cpp_main_path, cpp_main)) { + Directories::get().open(project_path); + Notebook::get().open(cpp_main_path); + Directories::get().update(); + Terminal::get().print("C++ project " + project_name + " created.\n"); + } else + Terminal::get().print("Error: Could not create project " + project_path.string() + "\n", true); } - else - Terminal::get().print("Error saving file\n", true); - } - } - }); - - menu.add_action("file_print", [this]() { - if(auto view=Notebook::get().get_current_view()) { - auto print_operation=Gtk::PrintOperation::create(); - auto print_compositor=Gsv::PrintCompositor::create(*view); - - print_operation->set_job_name(view->file_path.filename().string()); - print_compositor->set_wrap_mode(Gtk::WrapMode::WRAP_WORD_CHAR); - - print_operation->signal_begin_print().connect([print_operation, print_compositor](const Glib::RefPtr<Gtk::PrintContext>& print_context) { - while(!print_compositor->paginate(print_context)); - print_operation->set_n_pages(print_compositor->get_n_pages()); - }); - print_operation->signal_draw_page().connect([print_compositor](const Glib::RefPtr<Gtk::PrintContext>& print_context, int page_nr) { - print_compositor->draw_page(print_context, page_nr); - }); - - print_operation->run(Gtk::PRINT_OPERATION_ACTION_PRINT_DIALOG, *this); - } - }); - - menu.add_action("edit_undo", []() { - if(auto view=Notebook::get().get_current_view()) { - auto undo_manager = view->get_source_buffer()->get_undo_manager(); - if (undo_manager->can_undo()) { - view->disable_spellcheck=true; - undo_manager->undo(); - view->disable_spellcheck=false; - view->hide_tooltips(); - view->scroll_to(view->get_buffer()->get_insert()); - } - } - }); - menu.add_action("edit_redo", []() { - if(auto view=Notebook::get().get_current_view()) { - auto undo_manager = view->get_source_buffer()->get_undo_manager(); - if(undo_manager->can_redo()) { - view->disable_spellcheck=true; - undo_manager->redo(); - view->disable_spellcheck=false; - view->hide_tooltips(); - view->scroll_to(view->get_buffer()->get_insert()); - } - } - }); - - menu.add_action("edit_cut", [this]() { - // Return if a shown tooltip has selected text - for(auto tooltip: Tooltips::shown_tooltips) { - auto buffer=tooltip->text_buffer; - if(buffer && buffer->get_has_selection()) - return; - } - - auto widget=get_focus(); - if(auto entry=dynamic_cast<Gtk::Entry*>(widget)) - entry->cut_clipboard(); - else if(auto view=dynamic_cast<Gtk::TextView*>(widget)) { - if(!view->get_editable()) - return; - auto source_view=dynamic_cast<Source::View*>(view); - if(source_view) - source_view->disable_spellcheck=true; - if(!view->get_buffer()->get_has_selection()) { - auto start=view->get_buffer()->get_iter_at_line(view->get_buffer()->get_insert()->get_iter().get_line()); - auto end=start; - if(!end.ends_line()) - end.forward_to_line_end(); - end.forward_char(); - if(!end.starts_line()) // In case of \r\n - end.forward_char(); - Gtk::Clipboard::get()->set_text(view->get_buffer()->get_text(start, end)); - view->get_buffer()->erase(start, end); - } - else - view->get_buffer()->cut_clipboard(Gtk::Clipboard::get()); - if(source_view) - source_view->disable_spellcheck=false; - } - }); - menu.add_action("edit_copy", [this]() { - // Copy from a tooltip if it has selected text - for(auto tooltip: Tooltips::shown_tooltips) { - auto buffer=tooltip->text_buffer; - if(buffer && buffer->get_has_selection()) { - buffer->copy_clipboard(Gtk::Clipboard::get()); - return; - } - } - - auto widget=get_focus(); - if(auto entry=dynamic_cast<Gtk::Entry*>(widget)) - entry->copy_clipboard(); - else if(auto view=dynamic_cast<Gtk::TextView*>(widget)) { - if(!view->get_buffer()->get_has_selection()) { - auto start=view->get_buffer()->get_iter_at_line(view->get_buffer()->get_insert()->get_iter().get_line()); - auto end=start; - if(!end.ends_line()) - end.forward_to_line_end(); - end.forward_char(); - if(!end.starts_line()) // In case of \r\n - end.forward_char(); - Gtk::Clipboard::get()->set_text(view->get_buffer()->get_text(start, end)); - } - else - view->get_buffer()->copy_clipboard(Gtk::Clipboard::get()); - } - }); - menu.add_action("edit_paste", [this]() { - auto widget=get_focus(); - if(auto entry=dynamic_cast<Gtk::Entry*>(widget)) - entry->paste_clipboard(); - else if(auto view=dynamic_cast<Gtk::TextView*>(widget)) { - auto source_view=dynamic_cast<Source::View*>(view); - if(source_view) { - source_view->disable_spellcheck=true; - source_view->paste(); - source_view->disable_spellcheck=false; - source_view->hide_tooltips(); - } - else if(view->get_editable()) - view->get_buffer()->paste_clipboard(Gtk::Clipboard::get()); - } - }); - - menu.add_action("edit_find", [this]() { - search_and_replace_entry(); - }); - - menu.add_action("source_spellcheck", []() { - if(auto view=Notebook::get().get_current_view()) { - view->remove_spellcheck_errors(); - view->spellcheck(); - } - }); - menu.add_action("source_spellcheck_clear", []() { - if(auto view=Notebook::get().get_current_view()) - view->remove_spellcheck_errors(); - }); - menu.add_action("source_spellcheck_next_error", []() { - if(auto view=Notebook::get().get_current_view()) - view->goto_next_spellcheck_error(); - }); - - menu.add_action("source_git_next_diff", []() { - if(auto view=Notebook::get().get_current_view()) - view->git_goto_next_diff(); - }); - menu.add_action("source_git_show_diff", []() { - if(auto view=Notebook::get().get_current_view()) { - auto diff_details=view->git_get_diff_details(); - if(diff_details.empty()) - return; - std::string postfix; - if(diff_details.size()>2) { - size_t pos=diff_details.find("@@", 2); - if(pos!=std::string::npos) - postfix=diff_details.substr(0, pos+2); - } - Notebook::get().open(view->file_path.string()+postfix+".diff"); - if(auto new_view=Notebook::get().get_current_view()) { - if(new_view->get_buffer()->get_text().empty()) { - new_view->get_source_buffer()->begin_not_undoable_action(); - new_view->get_buffer()->set_text(diff_details); - new_view->get_source_buffer()->end_not_undoable_action(); - new_view->get_buffer()->set_modified(false); + }); + + menu.add_action("file_open_file", []() { + auto folder_path = Notebook::get().get_current_folder(); + if (auto view = Notebook::get().get_current_view()) { + if (!Directories::get().path.empty() && !filesystem::file_in_path(view->file_path, Directories::get().path)) + folder_path = view->file_path.parent_path(); } - } - } - }); - - menu.add_action("source_indentation_set_buffer_tab", [this]() { - set_tab_entry(); - }); - menu.add_action("source_indentation_auto_indent_buffer", []() { - auto view=Notebook::get().get_current_view(); - if(view && view->format_style) { - view->disable_spellcheck=true; - view->format_style(true); - view->disable_spellcheck=false; - view->hide_tooltips(); - } - }); - - menu.add_action("source_goto_line", [this]() { - goto_line_entry(); - }); - menu.add_action("source_center_cursor", []() { - if(auto view=Notebook::get().get_current_view()) - view->scroll_to_cursor_delayed(view, true, false); - }); - menu.add_action("source_cursor_history_back", []() { - if(Notebook::get().cursor_locations.size()==0) - return; - if(Notebook::get().current_cursor_location==static_cast<size_t>(-1)) - Notebook::get().current_cursor_location=Notebook::get().cursor_locations.size()-1; - - auto cursor_location=&Notebook::get().cursor_locations.at(Notebook::get().current_cursor_location); - // Move to current position if current position's view is not current view - // (in case one is looking at a new file but has not yet placed the cursor within the file) - if(cursor_location->view!=Notebook::get().get_current_view()) - Notebook::get().open(cursor_location->view->file_path); - else { - if(Notebook::get().cursor_locations.size()<=1) - return; - if(Notebook::get().current_cursor_location==0) - return; - - --Notebook::get().current_cursor_location; - cursor_location=&Notebook::get().cursor_locations.at(Notebook::get().current_cursor_location); - if(Notebook::get().get_current_view()!=cursor_location->view) - Notebook::get().open(cursor_location->view->file_path); - } - Notebook::get().disable_next_update_cursor_locations=true; - cursor_location->view->get_buffer()->place_cursor(cursor_location->mark->get_iter()); - cursor_location->view->scroll_to_cursor_delayed(cursor_location->view, true, false); - }); - menu.add_action("source_cursor_history_forward", []() { - if(Notebook::get().cursor_locations.size()<=1) - return; - if(Notebook::get().current_cursor_location==static_cast<size_t>(-1)) - Notebook::get().current_cursor_location=Notebook::get().cursor_locations.size()-1; - if(Notebook::get().current_cursor_location==Notebook::get().cursor_locations.size()-1) - return; - - ++Notebook::get().current_cursor_location; - auto &cursor_location=Notebook::get().cursor_locations.at(Notebook::get().current_cursor_location); - if(Notebook::get().get_current_view()!=cursor_location.view) - Notebook::get().open(cursor_location.view->file_path); - Notebook::get().disable_next_update_cursor_locations=true; - cursor_location.view->get_buffer()->place_cursor(cursor_location.mark->get_iter()); - cursor_location.view->scroll_to_cursor_delayed(cursor_location.view, true, false); - }); - - menu.add_action("source_show_completion", [] { - if(auto view=Notebook::get().get_current_view()) { - if(view->non_interactive_completion) - view->non_interactive_completion(); - else - g_signal_emit_by_name(view->gobj(), "show-completion"); - } - }); - - menu.add_action("source_find_symbol", []() { - auto project=Project::create(); - project->show_symbols(); - }); - - menu.add_action("source_find_file", []() { - auto view = Notebook::get().get_current_view(); - - boost::filesystem::path search_path; - if(view) - search_path=view->file_path.parent_path(); - else if(!Directories::get().path.empty()) - search_path=Directories::get().path; - else { - boost::system::error_code ec; - search_path=boost::filesystem::current_path(ec); - if(ec) { - Terminal::get().print("Error: could not find current path\n", true); - return; - } - } - auto build=Project::Build::create(search_path); - auto project_path=build->project_path; - boost::filesystem::path default_path, debug_path; - if(!project_path.empty()) { - search_path=project_path; - default_path = build->get_default_path(); - debug_path = build->get_debug_path(); - } - - if(view) { - auto dialog_iter=view->get_iter_for_dialog(); - SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); - } - else - SelectionDialog::create(true, true); - - std::unordered_set<std::string> buffer_paths; - for(auto view: Notebook::get().get_views()) - buffer_paths.emplace(view->file_path.string()); - - std::vector<boost::filesystem::path> paths; - // populate with all files in search_path - for (boost::filesystem::recursive_directory_iterator iter(search_path), end; iter != end; iter++) { - auto path = iter->path(); - // ignore folders - if (!boost::filesystem::is_regular_file(path)) { - if(path==default_path || path==debug_path || path.filename()==".git") - iter.no_push(); - continue; - } - - // remove project base path - auto row_str = filesystem::get_relative_path(path, search_path).string(); - if(buffer_paths.count(path.string())) - row_str="<b>"+row_str+"</b>"; - paths.emplace_back(path); - SelectionDialog::get()->add_row(row_str); - } - - if(paths.empty()) { - Info::get().print("No files found in current project"); - return; - } - - SelectionDialog::get()->on_select=[paths=std::move(paths)](unsigned int index, const std::string &text, bool hide_window) { - if(index>=paths.size()) - return; - Notebook::get().open(paths[index]); - if (auto view=Notebook::get().get_current_view()) - view->hide_tooltips(); - }; - - if(view) - view->hide_tooltips(); - SelectionDialog::get()->show(); - - }); - - menu.add_action("source_comments_toggle", []() { - if(auto view=Notebook::get().get_current_view()) { - if(view->toggle_comments) { - view->toggle_comments(); - } - } - }); - menu.add_action("source_comments_add_documentation", []() { - if(auto view=Notebook::get().get_current_view()) { - if(view->get_documentation_template) { - auto documentation_template=view->get_documentation_template(); - auto offset=std::get<0>(documentation_template); - if(offset) { - if(!boost::filesystem::is_regular_file(offset.file_path)) + auto path = Dialog::open_file(folder_path); + if (path != "") + Notebook::get().open(path); + }); + menu.add_action("file_open_folder", []() { + auto path = Dialog::open_folder(Notebook::get().get_current_folder()); + if (path != "" && boost::filesystem::exists(path)) + Directories::get().open(path); + }); + + menu.add_action("file_reload_file", []() { + if (auto view = Notebook::get().get_current_view()) { + if (boost::filesystem::exists(view->file_path)) { + std::ifstream can_read(view->file_path.string()); + if (!can_read) { + Terminal::get().print("Error: could not read " + view->file_path.string() + "\n", true); + return; + } + can_read.close(); + } else { + Terminal::get().print("Error: " + view->file_path.string() + " does not exist\n", true); + return; + } + + if (view->load()) + view->full_reparse(); + } + }); + + menu.add_action("file_save", [this]() { + if (auto view = Notebook::get().get_current_view()) { + if (Notebook::get().save_current()) { + if (view->file_path == Config::get().home_juci_path / "config" / "config.json") { + configure(); + for (size_t c = 0; c < Notebook::get().size(); c++) { + Notebook::get().get_view(c)->configure(); + Notebook::get().configure(c); + } + } + } + } + }); + menu.add_action("file_save_as", []() { + if (auto view = Notebook::get().get_current_view()) { + auto path = Dialog::save_file_as(view->file_path); + if (path != "") { + std::ofstream file(path, std::ofstream::binary); + if (file) { + file << view->get_buffer()->get_text().raw(); + file.close(); + if (Directories::get().path != "") + Directories::get().update(); + Notebook::get().open(path); + Terminal::get().print( + "File saved to: " + Notebook::get().get_current_view()->file_path.string() + "\n"); + } else + Terminal::get().print("Error saving file\n", true); + } + } + }); + + menu.add_action("file_print", [this]() { + if (auto view = Notebook::get().get_current_view()) { + auto print_operation = Gtk::PrintOperation::create(); + auto print_compositor = Gsv::PrintCompositor::create(*view); + + print_operation->set_job_name(view->file_path.filename().string()); + print_compositor->set_wrap_mode(Gtk::WrapMode::WRAP_WORD_CHAR); + + print_operation->signal_begin_print().connect( + [print_operation, print_compositor](const Glib::RefPtr<Gtk::PrintContext> &print_context) { + while (!print_compositor->paginate(print_context)); + print_operation->set_n_pages(print_compositor->get_n_pages()); + }); + print_operation->signal_draw_page().connect( + [print_compositor](const Glib::RefPtr<Gtk::PrintContext> &print_context, int page_nr) { + print_compositor->draw_page(print_context, page_nr); + }); + + print_operation->run(Gtk::PRINT_OPERATION_ACTION_PRINT_DIALOG, *this); + } + }); + + menu.add_action("edit_undo", []() { + if (auto view = Notebook::get().get_current_view()) { + auto undo_manager = view->get_source_buffer()->get_undo_manager(); + if (undo_manager->can_undo()) { + view->disable_spellcheck = true; + undo_manager->undo(); + view->disable_spellcheck = false; + view->scroll_to(view->get_buffer()->get_insert()); + } + } + }); + menu.add_action("edit_redo", []() { + if (auto view = Notebook::get().get_current_view()) { + auto undo_manager = view->get_source_buffer()->get_undo_manager(); + if (undo_manager->can_redo()) { + view->disable_spellcheck = true; + undo_manager->redo(); + view->disable_spellcheck = false; + view->scroll_to(view->get_buffer()->get_insert()); + } + } + }); + + menu.add_action("edit_cut", [this]() { + // Return if a shown tooltip has selected text + for (auto tooltip: Tooltips::shown_tooltips) { + auto buffer = tooltip->text_buffer; + if (buffer && buffer->get_has_selection()) + return; + } + + auto widget = get_focus(); + if (auto entry = dynamic_cast<Gtk::Entry *>(widget)) + entry->cut_clipboard(); + else if (auto view = dynamic_cast<Gtk::TextView *>(widget)) { + if (!view->get_editable()) + return; + auto source_view = dynamic_cast<Source::View *>(view); + if (source_view) + source_view->disable_spellcheck = true; + if (!view->get_buffer()->get_has_selection()) { + auto start = view->get_buffer()->get_iter_at_line( + view->get_buffer()->get_insert()->get_iter().get_line()); + auto end = start; + if (!end.ends_line()) + end.forward_to_line_end(); + end.forward_char(); + if (!end.starts_line()) // In case of \r\n + end.forward_char(); + Gtk::Clipboard::get()->set_text(view->get_buffer()->get_text(start, end)); + view->get_buffer()->erase(start, end); + } else + view->get_buffer()->cut_clipboard(Gtk::Clipboard::get()); + if (source_view) + source_view->disable_spellcheck = false; + } + }); + menu.add_action("edit_copy", [this]() { + // Copy from a tooltip if it has selected text + for (auto tooltip: Tooltips::shown_tooltips) { + auto buffer = tooltip->text_buffer; + if (buffer && buffer->get_has_selection()) { + buffer->copy_clipboard(Gtk::Clipboard::get()); + return; + } + } + + auto widget = get_focus(); + if (auto entry = dynamic_cast<Gtk::Entry *>(widget)) + entry->copy_clipboard(); + else if (auto view = dynamic_cast<Gtk::TextView *>(widget)) { + if (!view->get_buffer()->get_has_selection()) { + auto start = view->get_buffer()->get_iter_at_line( + view->get_buffer()->get_insert()->get_iter().get_line()); + auto end = start; + if (!end.ends_line()) + end.forward_to_line_end(); + end.forward_char(); + if (!end.starts_line()) // In case of \r\n + end.forward_char(); + Gtk::Clipboard::get()->set_text(view->get_buffer()->get_text(start, end)); + } else + view->get_buffer()->copy_clipboard(Gtk::Clipboard::get()); + } + }); + menu.add_action("edit_paste", [this]() { + auto widget = get_focus(); + if (auto entry = dynamic_cast<Gtk::Entry *>(widget)) + entry->paste_clipboard(); + else if (auto view = dynamic_cast<Gtk::TextView *>(widget)) { + auto source_view = dynamic_cast<Source::View *>(view); + if (source_view) { + source_view->disable_spellcheck = true; + source_view->paste(); + source_view->disable_spellcheck = false; + } else if (view->get_editable()) + view->get_buffer()->paste_clipboard(Gtk::Clipboard::get()); + } + }); + + menu.add_action("edit_find", [this]() { + search_and_replace_entry(); + }); + + menu.add_action("source_spellcheck", []() { + if (auto view = Notebook::get().get_current_view()) { + view->remove_spellcheck_errors(); + view->spellcheck(); + } + }); + menu.add_action("source_spellcheck_clear", []() { + if (auto view = Notebook::get().get_current_view()) + view->remove_spellcheck_errors(); + }); + menu.add_action("source_spellcheck_next_error", []() { + if (auto view = Notebook::get().get_current_view()) + view->goto_next_spellcheck_error(); + }); + + menu.add_action("source_git_next_diff", []() { + if (auto view = Notebook::get().get_current_view()) + view->git_goto_next_diff(); + }); + menu.add_action("source_git_show_diff", []() { + if (auto view = Notebook::get().get_current_view()) { + auto diff_details = view->git_get_diff_details(); + if (diff_details.empty()) + return; + std::string postfix; + if (diff_details.size() > 2) { + size_t pos = diff_details.find("@@", 2); + if (pos != std::string::npos) + postfix = diff_details.substr(0, pos + 2); + } + Notebook::get().open(view->file_path.string() + postfix + ".diff"); + if (auto new_view = Notebook::get().get_current_view()) { + if (new_view->get_buffer()->get_text().empty()) { + new_view->get_source_buffer()->begin_not_undoable_action(); + new_view->get_buffer()->set_text(diff_details); + new_view->get_source_buffer()->end_not_undoable_action(); + new_view->get_buffer()->set_modified(false); + } + } + } + }); + + menu.add_action("source_indentation_set_buffer_tab", [this]() { + set_tab_entry(); + }); + menu.add_action("source_indentation_auto_indent_buffer", []() { + auto view = Notebook::get().get_current_view(); + if (view && view->format_style) { + view->disable_spellcheck = true; + view->format_style(true); + view->disable_spellcheck = false; + } + }); + + menu.add_action("source_goto_line", [this]() { + goto_line_entry(); + }); + menu.add_action("source_center_cursor", []() { + if (auto view = Notebook::get().get_current_view()) + view->scroll_to_cursor_delayed(view, true, false); + }); + menu.add_action("source_cursor_history_back", []() { + if (Notebook::get().cursor_locations.size() == 0) return; - Notebook::get().open(offset.file_path); - auto view=Notebook::get().get_current_view(); - auto iter=view->get_iter_at_line_pos(offset.line, offset.index); - view->get_buffer()->insert(iter, std::get<1>(documentation_template)); - iter=view->get_iter_at_line_pos(offset.line, offset.index); - iter.forward_chars(std::get<2>(documentation_template)); - view->get_buffer()->place_cursor(iter); - view->scroll_to_cursor_delayed(view, true, false); + if (Notebook::get().current_cursor_location == static_cast<size_t>(-1)) + Notebook::get().current_cursor_location = Notebook::get().cursor_locations.size() - 1; + + auto cursor_location = &Notebook::get().cursor_locations.at(Notebook::get().current_cursor_location); + // Move to current position if current position's view is not current view + // (in case one is looking at a new file but has not yet placed the cursor within the file) + if (cursor_location->view != Notebook::get().get_current_view()) + Notebook::get().open(cursor_location->view->file_path); + else { + if (Notebook::get().cursor_locations.size() <= 1) + return; + if (Notebook::get().current_cursor_location == 0) + return; + + --Notebook::get().current_cursor_location; + cursor_location = &Notebook::get().cursor_locations.at(Notebook::get().current_cursor_location); + if (Notebook::get().get_current_view() != cursor_location->view) + Notebook::get().open(cursor_location->view->file_path); } - } - } - }); - menu.add_action("source_find_documentation", []() { - if(auto view=Notebook::get().get_current_view()) { - if(view->get_token_data) { - auto data=view->get_token_data(); - std::string url; - if(data.size()==1) - url=data[0]; - else if(data.size()>1) { - auto documentation_search=Config::get().source.documentation_searches.find(data[0]); - if(documentation_search!=Config::get().source.documentation_searches.end()) { - std::string token_query; - for(size_t c=1;c<data.size();c++) { - if(data[c].size()>0) { - if(token_query.size()>0) - token_query+=documentation_search->second.separator; - token_query+=data[c]; - } + Notebook::get().disable_next_update_cursor_locations = true; + cursor_location->view->get_buffer()->place_cursor(cursor_location->mark->get_iter()); + cursor_location->view->scroll_to_cursor_delayed(cursor_location->view, true, false); + }); + menu.add_action("source_cursor_history_forward", []() { + if (Notebook::get().cursor_locations.size() <= 1) + return; + if (Notebook::get().current_cursor_location == static_cast<size_t>(-1)) + Notebook::get().current_cursor_location = Notebook::get().cursor_locations.size() - 1; + if (Notebook::get().current_cursor_location == Notebook::get().cursor_locations.size() - 1) + return; + + ++Notebook::get().current_cursor_location; + auto &cursor_location = Notebook::get().cursor_locations.at(Notebook::get().current_cursor_location); + if (Notebook::get().get_current_view() != cursor_location.view) + Notebook::get().open(cursor_location.view->file_path); + Notebook::get().disable_next_update_cursor_locations = true; + cursor_location.view->get_buffer()->place_cursor(cursor_location.mark->get_iter()); + cursor_location.view->scroll_to_cursor_delayed(cursor_location.view, true, false); + }); + + menu.add_action("source_show_completion", [] { + if (auto view = Notebook::get().get_current_view()) { + if (view->non_interactive_completion) + view->non_interactive_completion(); + else + g_signal_emit_by_name(view->gobj(), "show-completion"); + } + }); + + menu.add_action("source_find_symbol", []() { + auto project = Project::create(); + project->show_symbols(); + }); + + menu.add_action("source_find_file", []() { + auto view = Notebook::get().get_current_view(); + + boost::filesystem::path search_path; + if (view) + search_path = view->file_path.parent_path(); + else if (!Directories::get().path.empty()) + search_path = Directories::get().path; + else { + boost::system::error_code ec; + search_path = boost::filesystem::current_path(ec); + if (ec) { + Terminal::get().print("Error: could not find current path\n", true); + return; } - if(token_query.size()>0) { - std::unordered_map<std::string, std::string>::iterator query; - if(data[1].size()>0) - query=documentation_search->second.queries.find(data[1]); - else - query=documentation_search->second.queries.find("@empty"); - if(query==documentation_search->second.queries.end()) - query=documentation_search->second.queries.find("@any"); - - if(query!=documentation_search->second.queries.end()) - url=query->second+token_query; + } + auto build = Project::Build::create(search_path); + auto project_path = build->project_path; + boost::filesystem::path default_path, debug_path; + if (!project_path.empty()) { + search_path = project_path; + default_path = build->get_default_path(); + debug_path = build->get_debug_path(); + } + + if (view) { + auto dialog_iter = view->get_iter_for_dialog(); + SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); + } else + SelectionDialog::create(true, true); + + std::unordered_set<std::string> buffer_paths; + for (auto view: Notebook::get().get_views()) + buffer_paths.emplace(view->file_path.string()); + + std::vector<boost::filesystem::path> paths; + // populate with all files in search_path + for (boost::filesystem::recursive_directory_iterator iter(search_path), end; iter != end; iter++) { + auto path = iter->path(); + // ignore folders + if (!boost::filesystem::is_regular_file(path)) { + if (path == default_path || path == debug_path || path.filename() == ".git") + iter.no_push(); + continue; + } + + // remove project base path + auto row_str = filesystem::get_relative_path(path, search_path).string(); + if (buffer_paths.count(path.string())) + row_str = "<b>" + row_str + "</b>"; + paths.emplace_back(path); + SelectionDialog::get()->add_row(row_str); + } + + if (paths.empty()) { + Info::get().print("No files found in current project"); + return; + } + + SelectionDialog::get()->on_select = [paths = std::move(paths)](unsigned int index, const std::string &text, + bool hide_window) { + if (index >= paths.size()) + return; + Notebook::get().open(paths[index]); + if (auto view = Notebook::get().get_current_view()) + view->hide_tooltips(); + }; + + if (view) + view->hide_tooltips(); + SelectionDialog::get()->show(); + + }); + + menu.add_action("source_comments_toggle", []() { + if (auto view = Notebook::get().get_current_view()) { + if (view->toggle_comments) { + view->toggle_comments(); } - } } - if(!url.empty()) { + }); + menu.add_action("source_comments_add_documentation", []() { + if (auto view = Notebook::get().get_current_view()) { + if (view->get_documentation_template) { + auto documentation_template = view->get_documentation_template(); + auto offset = std::get<0>(documentation_template); + if (offset) { + if (!boost::filesystem::is_regular_file(offset.file_path)) + return; + Notebook::get().open(offset.file_path); + auto view = Notebook::get().get_current_view(); + auto iter = view->get_iter_at_line_pos(offset.line, offset.index); + view->get_buffer()->insert(iter, std::get<1>(documentation_template)); + iter = view->get_iter_at_line_pos(offset.line, offset.index); + iter.forward_chars(std::get<2>(documentation_template)); + view->get_buffer()->place_cursor(iter); + view->scroll_to_cursor_delayed(view, true, false); + } + } + } + }); + menu.add_action("source_find_documentation", []() { + if (auto view = Notebook::get().get_current_view()) { + if (view->get_token_data) { + auto data = view->get_token_data(); + std::string url; + if (data.size() == 1) + url = data[0]; + else if (data.size() > 1) { + auto documentation_search = Config::get().source.documentation_searches.find(data[0]); + if (documentation_search != Config::get().source.documentation_searches.end()) { + std::string token_query; + for (size_t c = 1; c < data.size(); c++) { + if (data[c].size() > 0) { + if (token_query.size() > 0) + token_query += documentation_search->second.separator; + token_query += data[c]; + } + } + if (token_query.size() > 0) { + std::unordered_map<std::string, std::string>::iterator query; + if (data[1].size() > 0) + query = documentation_search->second.queries.find(data[1]); + else + query = documentation_search->second.queries.find("@empty"); + if (query == documentation_search->second.queries.end()) + query = documentation_search->second.queries.find("@any"); + + if (query != documentation_search->second.queries.end()) + url = query->second + token_query; + } + } + } + if (!url.empty()) { #ifdef __APPLE__ - Terminal::get().process("open \""+url+"\""); + Terminal::get().process("open \""+url+"\""); #else - GError* error=nullptr; + GError *error = nullptr; #if GTK_VERSION_GE(3, 22) - gtk_show_uri_on_window(nullptr, url.c_str(), GDK_CURRENT_TIME, &error); + gtk_show_uri_on_window(nullptr, url.c_str(), GDK_CURRENT_TIME, &error); #else - gtk_show_uri(nullptr, url.c_str(), GDK_CURRENT_TIME, &error); + gtk_show_uri(nullptr, url.c_str(), GDK_CURRENT_TIME, &error); #endif - g_clear_error(&error); + g_clear_error(&error); #endif + } + } } - } - } - }); - - menu.add_action("source_goto_declaration", []() { - if(auto view=Notebook::get().get_current_view()) { - if(view->get_declaration_location) { - auto location=view->get_declaration_location(); - if(location) { - if(!boost::filesystem::is_regular_file(location.file_path)) - return; - Notebook::get().open(location.file_path); - auto view=Notebook::get().get_current_view(); - auto line=static_cast<int>(location.line); - auto index=static_cast<int>(location.index); - view->place_cursor_at_line_pos(line, index); - view->scroll_to_cursor_delayed(view, true, false); + }); + + menu.add_action("source_goto_declaration", []() { + if (auto view = Notebook::get().get_current_view()) { + if (view->get_declaration_location) { + auto location = view->get_declaration_location(); + if (location) { + if (!boost::filesystem::is_regular_file(location.file_path)) + return; + Notebook::get().open(location.file_path); + auto view = Notebook::get().get_current_view(); + auto line = static_cast<int>(location.line); + auto index = static_cast<int>(location.index); + view->place_cursor_at_line_pos(line, index); + view->scroll_to_cursor_delayed(view, true, false); + } + } } - } - } - }); - menu.add_action("source_goto_type_declaration", []() { - if(auto view=Notebook::get().get_current_view()) { - if(view->get_type_declaration_location) { - auto location=view->get_type_declaration_location(); - if(location) { - if(!boost::filesystem::is_regular_file(location.file_path)) - return; - Notebook::get().open(location.file_path); - auto view=Notebook::get().get_current_view(); - auto line=static_cast<int>(location.line); - auto index=static_cast<int>(location.index); - view->place_cursor_at_line_pos(line, index); - view->scroll_to_cursor_delayed(view, true, false); + }); + menu.add_action("source_goto_type_declaration", []() { + if (auto view = Notebook::get().get_current_view()) { + if (view->get_type_declaration_location) { + auto location = view->get_type_declaration_location(); + if (location) { + if (!boost::filesystem::is_regular_file(location.file_path)) + return; + Notebook::get().open(location.file_path); + auto view = Notebook::get().get_current_view(); + auto line = static_cast<int>(location.line); + auto index = static_cast<int>(location.index); + view->place_cursor_at_line_pos(line, index); + view->scroll_to_cursor_delayed(view, true, false); + } + } } - } - } - }); - auto goto_selected_location=[](Source::View *view, const std::vector<Source::Offset> &locations) { - if(!locations.empty()) { - auto dialog_iter=view->get_iter_for_dialog(); - SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); - std::vector<Source::Offset> rows; - auto project_path=Project::Build::create(view->file_path)->project_path; - if(project_path.empty()) { - if(!Directories::get().path.empty()) - project_path=Directories::get().path; - else - project_path=view->file_path.parent_path(); - } - for(auto &location: locations) { - auto path=filesystem::get_relative_path(filesystem::get_normal_path(location.file_path), project_path); - if(path.empty()) - path=location.file_path.filename(); - auto row=path.string()+":"+std::to_string(location.line+1); - rows.emplace_back(location); - SelectionDialog::get()->add_row(row); - } - - if(rows.size()==0) - return; - else if(rows.size()==1) { - auto location=*rows.begin(); - if(!boost::filesystem::is_regular_file(location.file_path)) - return; - Notebook::get().open(location.file_path); - auto view=Notebook::get().get_current_view(); - auto line=static_cast<int>(location.line); - auto index=static_cast<int>(location.index); - view->place_cursor_at_line_pos(line, index); - view->scroll_to_cursor_delayed(view, true, false); - return; - } - SelectionDialog::get()->on_select=[rows=std::move(rows)](unsigned int index, const std::string &text, bool hide_window) { - if(index>=rows.size()) - return; - auto location=rows[index]; - if(!boost::filesystem::is_regular_file(location.file_path)) - return; - Notebook::get().open(location.file_path); - auto view=Notebook::get().get_current_view(); - view->place_cursor_at_line_pos(location.line, location.index); - view->scroll_to_cursor_delayed(view, true, false); - view->hide_tooltips(); - }; - view->hide_tooltips(); - SelectionDialog::get()->show(); - } - }; - menu.add_action("source_goto_implementation", [goto_selected_location]() { - if(auto view=Notebook::get().get_current_view()) { - if(view->get_implementation_locations) - goto_selected_location(view, view->get_implementation_locations()); - } - }); - menu.add_action("source_goto_declaration_or_implementation", [goto_selected_location]() { - if(auto view=Notebook::get().get_current_view()) { - if(view->get_declaration_or_implementation_locations) - goto_selected_location(view, view->get_declaration_or_implementation_locations()); - } - }); - - menu.add_action("source_goto_usage", []() { - if(auto view=Notebook::get().get_current_view()) { - if(view->get_usages) { - auto usages=view->get_usages(); - if(!usages.empty()) { - auto dialog_iter=view->get_iter_for_dialog(); - SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); - std::vector<Source::Offset> rows; - - auto iter=view->get_buffer()->get_insert()->get_iter(); - for(auto &usage: usages) { - std::string row; - bool current_page=true; - //add file name if usage is not in current page - if(view->file_path!=usage.first.file_path) { - row=usage.first.file_path.filename().string()+":"; - current_page=false; + }); + auto goto_selected_location = [](Source::View *view, const std::vector<Source::Offset> &locations) { + if (!locations.empty()) { + auto dialog_iter = view->get_iter_for_dialog(); + SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); + std::vector<Source::Offset> rows; + auto project_path = Project::Build::create(view->file_path)->project_path; + if (project_path.empty()) { + if (!Directories::get().path.empty()) + project_path = Directories::get().path; + else + project_path = view->file_path.parent_path(); } - row+=std::to_string(usage.first.line+1)+": "+usage.second; - rows.emplace_back(usage.first); - SelectionDialog::get()->add_row(row); - - //Set dialog cursor to the last row if the textview cursor is at the same line - if(current_page && - iter.get_line()==static_cast<int>(usage.first.line) && iter.get_line_index()>=static_cast<int>(usage.first.index)) { - SelectionDialog::get()->set_cursor_at_last_row(); + for (auto &location: locations) { + auto path = filesystem::get_relative_path(filesystem::get_normal_path(location.file_path), + project_path); + if (path.empty()) + path = location.file_path.filename(); + auto row = path.string() + ":" + std::to_string(location.line + 1); + rows.emplace_back(location); + SelectionDialog::get()->add_row(row); } - } - - if(rows.size()==0) - return; - SelectionDialog::get()->on_select=[rows=std::move(rows)](unsigned int index, const std::string &text, bool hide_window) { - if(index>=rows.size()) - return; - auto offset=rows[index]; - if(!boost::filesystem::is_regular_file(offset.file_path)) - return; - Notebook::get().open(offset.file_path); - auto view=Notebook::get().get_current_view(); - view->place_cursor_at_line_pos(offset.line, offset.index); - view->scroll_to_cursor_delayed(view, true, false); + + if (rows.size() == 0) + return; + else if (rows.size() == 1) { + auto location = *rows.begin(); + if (!boost::filesystem::is_regular_file(location.file_path)) + return; + Notebook::get().open(location.file_path); + auto view = Notebook::get().get_current_view(); + auto line = static_cast<int>(location.line); + auto index = static_cast<int>(location.index); + view->place_cursor_at_line_pos(line, index); + view->scroll_to_cursor_delayed(view, true, false); + return; + } + SelectionDialog::get()->on_select = [rows = std::move(rows)](unsigned int index, const std::string &text, + bool hide_window) { + if (index >= rows.size()) + return; + auto location = rows[index]; + if (!boost::filesystem::is_regular_file(location.file_path)) + return; + Notebook::get().open(location.file_path); + auto view = Notebook::get().get_current_view(); + view->place_cursor_at_line_pos(location.line, location.index); + view->scroll_to_cursor_delayed(view, true, false); + view->hide_tooltips(); + }; view->hide_tooltips(); - }; - view->hide_tooltips(); - SelectionDialog::get()->show(); + SelectionDialog::get()->show(); + } + }; + menu.add_action("source_goto_implementation", [goto_selected_location]() { + if (auto view = Notebook::get().get_current_view()) { + if (view->get_implementation_locations) + goto_selected_location(view, view->get_implementation_locations()); + } + }); + menu.add_action("source_goto_declaration_or_implementation", [goto_selected_location]() { + if (auto view = Notebook::get().get_current_view()) { + if (view->get_declaration_or_implementation_locations) + goto_selected_location(view, view->get_declaration_or_implementation_locations()); + } + }); + + menu.add_action("source_goto_usage", []() { + if (auto view = Notebook::get().get_current_view()) { + if (view->get_usages) { + auto usages = view->get_usages(); + if (!usages.empty()) { + auto dialog_iter = view->get_iter_for_dialog(); + SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); + std::vector<Source::Offset> rows; + + auto iter = view->get_buffer()->get_insert()->get_iter(); + for (auto &usage: usages) { + std::string row; + bool current_page = true; + //add file name if usage is not in current page + if (view->file_path != usage.first.file_path) { + row = usage.first.file_path.filename().string() + ":"; + current_page = false; + } + row += std::to_string(usage.first.line + 1) + ": " + usage.second; + rows.emplace_back(usage.first); + SelectionDialog::get()->add_row(row); + + //Set dialog cursor to the last row if the textview cursor is at the same line + if (current_page && + iter.get_line() == static_cast<int>(usage.first.line) && + iter.get_line_index() >= static_cast<int>(usage.first.index)) { + SelectionDialog::get()->set_cursor_at_last_row(); + } + } + + if (rows.size() == 0) + return; + SelectionDialog::get()->on_select = [rows = std::move(rows)](unsigned int index, + const std::string &text, + bool hide_window) { + if (index >= rows.size()) + return; + auto offset = rows[index]; + if (!boost::filesystem::is_regular_file(offset.file_path)) + return; + Notebook::get().open(offset.file_path); + auto view = Notebook::get().get_current_view(); + view->place_cursor_at_line_pos(offset.line, offset.index); + view->scroll_to_cursor_delayed(view, true, false); + view->hide_tooltips(); + }; + view->hide_tooltips(); + SelectionDialog::get()->show(); + } + } + } + }); + menu.add_action("source_goto_method", []() { + if (auto view = Notebook::get().get_current_view()) { + if (view->get_methods) { + auto methods = Notebook::get().get_current_view()->get_methods(); + if (!methods.empty()) { + auto dialog_iter = view->get_iter_for_dialog(); + SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); + std::vector<Source::Offset> rows; + auto iter = view->get_buffer()->get_insert()->get_iter(); + for (auto &method: methods) { + rows.emplace_back(method.first); + SelectionDialog::get()->add_row(method.second); + if (iter.get_line() >= static_cast<int>(method.first.line)) + SelectionDialog::get()->set_cursor_at_last_row(); + } + SelectionDialog::get()->on_select = [view, rows = std::move(rows)](unsigned int index, + const std::string &text, + bool hide_window) { + if (index >= rows.size()) + return; + auto offset = rows[index]; + view->get_buffer()->place_cursor(view->get_iter_at_line_pos(offset.line, offset.index)); + view->scroll_to(view->get_buffer()->get_insert(), 0.0, 1.0, 0.5); + view->hide_tooltips(); + }; + view->hide_tooltips(); + SelectionDialog::get()->show(); + } + } + } + }); + menu.add_action("source_rename", [this]() { + rename_token_entry(); + }); + menu.add_action("source_implement_method", []() { + const static std::string button_text = "Insert Method Implementation"; + + if (auto view = Notebook::get().get_current_view()) { + if (view->get_method) { + auto iter = view->get_buffer()->get_insert()->get_iter(); + if (!EntryBox::get().buttons.empty() && EntryBox::get().buttons.back().get_label() == button_text && + iter.ends_line() && iter.starts_line()) { + EntryBox::get().buttons.back().activate(); + return; + } + auto method = view->get_method(); + if (method.empty()) + return; + + EntryBox::get().clear(); + EntryBox::get().labels.emplace_back(); + EntryBox::get().labels.back().set_text(method); + EntryBox::get().buttons.emplace_back(button_text, [method = std::move(method)]() { + if (auto view = Notebook::get().get_current_view()) { + view->get_buffer()->insert_at_cursor(method); + EntryBox::get().clear(); + } + }); + EntryBox::get().show(); + } } - } - } - }); - menu.add_action("source_goto_method", []() { - if(auto view=Notebook::get().get_current_view()) { - if(view->get_methods) { - auto methods=Notebook::get().get_current_view()->get_methods(); - if(!methods.empty()) { - auto dialog_iter=view->get_iter_for_dialog(); - SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); - std::vector<Source::Offset> rows; - auto iter=view->get_buffer()->get_insert()->get_iter(); - for(auto &method: methods) { - rows.emplace_back(method.first); - SelectionDialog::get()->add_row(method.second); - if(iter.get_line()>=static_cast<int>(method.first.line)) - SelectionDialog::get()->set_cursor_at_last_row(); - } - SelectionDialog::get()->on_select=[view, rows=std::move(rows)](unsigned int index, const std::string &text, bool hide_window) { - if(index>=rows.size()) - return; - auto offset=rows[index]; - view->get_buffer()->place_cursor(view->get_iter_at_line_pos(offset.line, offset.index)); - view->scroll_to(view->get_buffer()->get_insert(), 0.0, 1.0, 0.5); - view->hide_tooltips(); - }; - view->hide_tooltips(); - SelectionDialog::get()->show(); + }); + + menu.add_action("source_goto_next_diagnostic", []() { + if (auto view = Notebook::get().get_current_view()) { + if (view->goto_next_diagnostic) { + view->goto_next_diagnostic(); + } } - } - } - }); - menu.add_action("source_rename", [this]() { - rename_token_entry(); - }); - menu.add_action("source_implement_method", []() { - const static std::string button_text="Insert Method Implementation"; - - if(auto view=Notebook::get().get_current_view()) { - if(view->get_method) { - auto iter=view->get_buffer()->get_insert()->get_iter(); - if(!EntryBox::get().buttons.empty() && EntryBox::get().buttons.back().get_label()==button_text && - iter.ends_line() && iter.starts_line()) { - EntryBox::get().buttons.back().activate(); - return; + }); + menu.add_action("source_apply_fix_its", []() { + if (auto view = Notebook::get().get_current_view()) { + if (view->get_fix_its) { + auto buffer = view->get_buffer(); + auto fix_its = view->get_fix_its(); + std::vector<std::pair<Glib::RefPtr<Gtk::TextMark>, Glib::RefPtr<Gtk::TextMark> > > fix_it_marks; + for (auto &fix_it: fix_its) { + auto start_iter = view->get_iter_at_line_pos(fix_it.offsets.first.line, fix_it.offsets.first.index); + auto end_iter = view->get_iter_at_line_pos(fix_it.offsets.second.line, fix_it.offsets.second.index); + fix_it_marks.emplace_back(buffer->create_mark(start_iter), buffer->create_mark(end_iter)); + } + size_t c = 0; + buffer->begin_user_action(); + for (auto &fix_it: fix_its) { + if (fix_it.type == Source::FixIt::Type::INSERT) { + buffer->insert(fix_it_marks[c].first->get_iter(), fix_it.source); + } + if (fix_it.type == Source::FixIt::Type::REPLACE) { + buffer->erase(fix_it_marks[c].first->get_iter(), fix_it_marks[c].second->get_iter()); + buffer->insert(fix_it_marks[c].first->get_iter(), fix_it.source); + } + if (fix_it.type == Source::FixIt::Type::ERASE) { + buffer->erase(fix_it_marks[c].first->get_iter(), fix_it_marks[c].second->get_iter()); + } + c++; + } + for (auto &mark_pair: fix_it_marks) { + buffer->delete_mark(mark_pair.first); + buffer->delete_mark(mark_pair.second); + } + buffer->end_user_action(); + } } - auto method=view->get_method(); - if(method.empty()) - return; - + }); + + menu.add_action("project_set_run_arguments", []() { + auto project = Project::create(); + auto run_arguments = project->get_run_arguments(); + if (run_arguments.second.empty()) + return; + EntryBox::get().clear(); EntryBox::get().labels.emplace_back(); - EntryBox::get().labels.back().set_text(method); - EntryBox::get().buttons.emplace_back(button_text, [method=std::move(method)](){ - if(auto view=Notebook::get().get_current_view()) { - view->get_buffer()->insert_at_cursor(method); - EntryBox::get().clear(); - } + auto label_it = EntryBox::get().labels.begin(); + label_it->update = [label_it](int state, const std::string &message) { + label_it->set_text( + "Synopsis: [environment_variable=value]... executable [argument]...\nSet empty to let juCi++ deduce executable."); + }; + label_it->update(0, ""); + EntryBox::get().entries.emplace_back(run_arguments.second, + [run_arguments_first = std::move(run_arguments.first)]( + const std::string &content) { + Project::run_arguments[run_arguments_first] = content; + EntryBox::get().hide(); + }, 50); + auto entry_it = EntryBox::get().entries.begin(); + entry_it->set_placeholder_text("Project: Set Run Arguments"); + EntryBox::get().buttons.emplace_back("Project: set run arguments", [entry_it]() { + entry_it->activate(); }); EntryBox::get().show(); - } - } - }); - - menu.add_action("source_goto_next_diagnostic", []() { - if(auto view=Notebook::get().get_current_view()) { - if(view->goto_next_diagnostic) { - view->goto_next_diagnostic(); - } - } - }); - menu.add_action("source_apply_fix_its", []() { - if(auto view=Notebook::get().get_current_view()) { - if(view->get_fix_its) { - auto buffer=view->get_buffer(); - auto fix_its=view->get_fix_its(); - std::vector<std::pair<Glib::RefPtr<Gtk::TextMark>, Glib::RefPtr<Gtk::TextMark> > > fix_it_marks; - for(auto &fix_it: fix_its) { - auto start_iter=view->get_iter_at_line_pos(fix_it.offsets.first.line, fix_it.offsets.first.index); - auto end_iter=view->get_iter_at_line_pos(fix_it.offsets.second.line, fix_it.offsets.second.index); - fix_it_marks.emplace_back(buffer->create_mark(start_iter), buffer->create_mark(end_iter)); + }); + menu.add_action("project_compile_and_run", []() { + if (Project::compiling || Project::debugging) { + Info::get().print("Compile or debug in progress"); + return; } - size_t c=0; - buffer->begin_user_action(); - for(auto &fix_it: fix_its) { - if(fix_it.type==Source::FixIt::Type::INSERT) { - buffer->insert(fix_it_marks[c].first->get_iter(), fix_it.source); - } - if(fix_it.type==Source::FixIt::Type::REPLACE) { - buffer->erase(fix_it_marks[c].first->get_iter(), fix_it_marks[c].second->get_iter()); - buffer->insert(fix_it_marks[c].first->get_iter(), fix_it.source); - } - if(fix_it.type==Source::FixIt::Type::ERASE) { - buffer->erase(fix_it_marks[c].first->get_iter(), fix_it_marks[c].second->get_iter()); - } - c++; + + Project::current = Project::create(); + + if (Config::get().project.save_on_compile_or_run) + Project::save_files(Project::current->build->project_path); + + Project::current->compile_and_run(); + }); + menu.add_action("project_compile", []() { + if (Project::compiling || Project::debugging) { + Info::get().print("Compile or debug in progress"); + return; } - for(auto &mark_pair: fix_it_marks) { - buffer->delete_mark(mark_pair.first); - buffer->delete_mark(mark_pair.second); + + Project::current = Project::create(); + + if (Config::get().project.save_on_compile_or_run) + Project::save_files(Project::current->build->project_path); + + Project::current->compile(); + }); + menu.add_action("project_recreate_build", []() { + if (Project::compiling || Project::debugging) { + Info::get().print("Compile or debug in progress"); + return; } - buffer->end_user_action(); - } - } - }); - - menu.add_action("project_set_run_arguments", []() { - auto project=Project::create(); - auto run_arguments=project->get_run_arguments(); - if(run_arguments.second.empty()) - return; - - EntryBox::get().clear(); - EntryBox::get().labels.emplace_back(); - auto label_it=EntryBox::get().labels.begin(); - label_it->update=[label_it](int state, const std::string& message){ - label_it->set_text("Synopsis: [environment_variable=value]... executable [argument]...\nSet empty to let juCi++ deduce executable."); - }; - label_it->update(0, ""); - EntryBox::get().entries.emplace_back(run_arguments.second, [run_arguments_first=std::move(run_arguments.first)](const std::string &content){ - Project::run_arguments[run_arguments_first]=content; - EntryBox::get().hide(); - }, 50); - auto entry_it=EntryBox::get().entries.begin(); - entry_it->set_placeholder_text("Project: Set Run Arguments"); - EntryBox::get().buttons.emplace_back("Project: set run arguments", [entry_it](){ - entry_it->activate(); + + Project::current = Project::create(); + + Project::current->recreate_build(); }); - EntryBox::get().show(); - }); - menu.add_action("project_compile_and_run", []() { - if(Project::compiling || Project::debugging) { - Info::get().print("Compile or debug in progress"); - return; - } - - Project::current=Project::create(); - - if(Config::get().project.save_on_compile_or_run) - Project::save_files(Project::current->build->project_path); - - Project::current->compile_and_run(); - }); - menu.add_action("project_compile", []() { - if(Project::compiling || Project::debugging) { - Info::get().print("Compile or debug in progress"); - return; - } - - Project::current=Project::create(); - - if(Config::get().project.save_on_compile_or_run) - Project::save_files(Project::current->build->project_path); - - Project::current->compile(); - }); - menu.add_action("project_recreate_build", []() { - if(Project::compiling || Project::debugging) { - Info::get().print("Compile or debug in progress"); - return; - } - - Project::current=Project::create(); - Project::current->recreate_build(); - }); - - menu.add_action("project_run_command", [this]() { - EntryBox::get().clear(); - EntryBox::get().labels.emplace_back(); - auto label_it=EntryBox::get().labels.begin(); - label_it->update=[label_it](int state, const std::string& message){ - label_it->set_text("Run Command directory order: opened directory, file path, current directory"); - }; - label_it->update(0, ""); - EntryBox::get().entries.emplace_back(last_run_command, [this](const std::string& content){ - if(content!="") { - last_run_command=content; - auto run_path=Notebook::get().get_current_folder(); - Terminal::get().async_print("Running: "+content+'\n'); - - Terminal::get().async_process(content, run_path, [content](int exit_status){ - Terminal::get().async_print(content+" returned: "+std::to_string(exit_status)+'\n'); + menu.add_action("project_run_command", [this]() { + EntryBox::get().clear(); + EntryBox::get().labels.emplace_back(); + auto label_it = EntryBox::get().labels.begin(); + label_it->update = [label_it](int state, const std::string &message) { + label_it->set_text("Run Command directory order: opened directory, file path, current directory"); + }; + label_it->update(0, ""); + EntryBox::get().entries.emplace_back(last_run_command, [this](const std::string &content) { + if (content != "") { + last_run_command = content; + auto run_path = Notebook::get().get_current_folder(); + Terminal::get().async_print("Running: " + content + '\n'); + + Terminal::get().async_process(content, run_path, [content](int exit_status) { + Terminal::get().async_print(content + " returned: " + std::to_string(exit_status) + '\n'); + }); + } + EntryBox::get().hide(); + }, 30); + auto entry_it = EntryBox::get().entries.begin(); + entry_it->set_placeholder_text("Command"); + EntryBox::get().buttons.emplace_back("Run command", [entry_it]() { + entry_it->activate(); }); - } - EntryBox::get().hide(); - }, 30); - auto entry_it=EntryBox::get().entries.begin(); - entry_it->set_placeholder_text("Command"); - EntryBox::get().buttons.emplace_back("Run command", [entry_it](){ - entry_it->activate(); + EntryBox::get().show(); }); - EntryBox::get().show(); - }); - - menu.add_action("project_kill_last_running", []() { - Terminal::get().kill_last_async_process(); - }); - menu.add_action("project_force_kill_last_running", []() { - Terminal::get().kill_last_async_process(true); - }); - + + menu.add_action("project_kill_last_running", []() { + Terminal::get().kill_last_async_process(); + }); + menu.add_action("project_force_kill_last_running", []() { + Terminal::get().kill_last_async_process(true); + }); + #ifdef JUCI_ENABLE_DEBUG - menu.add_action("debug_set_run_arguments", []() { - auto project=Project::create(); - auto run_arguments=project->debug_get_run_arguments(); - if(run_arguments.second.empty()) - return; - - EntryBox::get().clear(); - EntryBox::get().labels.emplace_back(); - auto label_it=EntryBox::get().labels.begin(); - label_it->update=[label_it](int state, const std::string& message){ - label_it->set_text("Synopsis: [environment_variable=value]... executable [argument]...\nSet empty to let juCi++ deduce executable."); - }; - label_it->update(0, ""); - EntryBox::get().entries.emplace_back(run_arguments.second, [run_arguments_first=std::move(run_arguments.first)](const std::string& content){ - Project::debug_run_arguments[run_arguments_first].arguments=content; - EntryBox::get().hide(); - }, 50); - auto entry_it=EntryBox::get().entries.begin(); - entry_it->set_placeholder_text("Debug: Set Run Arguments"); - - if(auto options=project->debug_get_options()) { - EntryBox::get().buttons.emplace_back("", [options]() { - options->show_all(); + menu.add_action("debug_set_run_arguments", []() { + auto project=Project::create(); + auto run_arguments=project->debug_get_run_arguments(); + if(run_arguments.second.empty()) + return; + + EntryBox::get().clear(); + EntryBox::get().labels.emplace_back(); + auto label_it=EntryBox::get().labels.begin(); + label_it->update=[label_it](int state, const std::string& message){ + label_it->set_text("Synopsis: [environment_variable=value]... executable [argument]...\nSet empty to let juCi++ deduce executable."); + }; + label_it->update(0, ""); + EntryBox::get().entries.emplace_back(run_arguments.second, [run_arguments_first=std::move(run_arguments.first)](const std::string& content){ + Project::debug_run_arguments[run_arguments_first].arguments=content; + EntryBox::get().hide(); + }, 50); + auto entry_it=EntryBox::get().entries.begin(); + entry_it->set_placeholder_text("Debug: Set Run Arguments"); + + if(auto options=project->debug_get_options()) { + EntryBox::get().buttons.emplace_back("", [options]() { + options->show_all(); + }); + EntryBox::get().buttons.back().set_image_from_icon_name("preferences-system"); + EntryBox::get().buttons.back().set_always_show_image(true); + EntryBox::get().buttons.back().set_tooltip_text("Additional Options"); + options->set_relative_to(EntryBox::get().buttons.back()); + } + + EntryBox::get().buttons.emplace_back("Debug: set run arguments", [entry_it](){ + entry_it->activate(); }); - EntryBox::get().buttons.back().set_image_from_icon_name("preferences-system"); - EntryBox::get().buttons.back().set_always_show_image(true); - EntryBox::get().buttons.back().set_tooltip_text("Additional Options"); - options->set_relative_to(EntryBox::get().buttons.back()); - } - - EntryBox::get().buttons.emplace_back("Debug: set run arguments", [entry_it](){ - entry_it->activate(); + EntryBox::get().show(); }); - EntryBox::get().show(); - }); - menu.add_action("debug_start_continue", [](){ - if(Project::compiling) { - Info::get().print("Compile in progress"); - return; - } - else if(Project::debugging) { - Project::current->debug_continue(); - return; - } - - Project::current=Project::create(); - - if(Config::get().project.save_on_compile_or_run) - Project::save_files(Project::current->build->project_path); - - Project::current->debug_start(); - }); - menu.add_action("debug_stop", []() { - if(Project::current) - Project::current->debug_stop(); - }); - menu.add_action("debug_kill", []() { - if(Project::current) - Project::current->debug_kill(); - }); - menu.add_action("debug_step_over", []() { - if(Project::current) - Project::current->debug_step_over(); - }); - menu.add_action("debug_step_into", []() { - if(Project::current) - Project::current->debug_step_into(); - }); - menu.add_action("debug_step_out", []() { - if(Project::current) - Project::current->debug_step_out(); - }); - menu.add_action("debug_backtrace", []() { - if(Project::current) - Project::current->debug_backtrace(); - }); - menu.add_action("debug_show_variables", []() { - if(Project::current) - Project::current->debug_show_variables(); - }); - menu.add_action("debug_run_command", [this]() { - EntryBox::get().clear(); - EntryBox::get().entries.emplace_back(last_run_debug_command, [this](const std::string& content){ - if(content!="") { - if(Project::current) - Project::current->debug_run_command(content); - last_run_debug_command=content; + menu.add_action("debug_start_continue", [](){ + if(Project::compiling) { + Info::get().print("Compile in progress"); + return; + } + else if(Project::debugging) { + Project::current->debug_continue(); + return; } - EntryBox::get().hide(); - }, 30); - auto entry_it=EntryBox::get().entries.begin(); - entry_it->set_placeholder_text("Debug Command"); - EntryBox::get().buttons.emplace_back("Run debug command", [entry_it](){ - entry_it->activate(); + + Project::current=Project::create(); + + if(Config::get().project.save_on_compile_or_run) + Project::save_files(Project::current->build->project_path); + + Project::current->debug_start(); }); - EntryBox::get().show(); - }); - menu.add_action("debug_toggle_breakpoint", [](){ - if(auto view=Notebook::get().get_current_view()) { - if(view->toggle_breakpoint) - view->toggle_breakpoint(view->get_buffer()->get_insert()->get_iter().get_line()); - } - }); - menu.add_action("debug_goto_stop", [](){ - if(Project::debugging) { - if(!Project::debug_stop.first.empty()) { - Notebook::get().open(Project::debug_stop.first); - if(auto view=Notebook::get().get_current_view()) { - int line=Project::debug_stop.second.first; - int index=Project::debug_stop.second.second; - view->place_cursor_at_line_index(line, index); - view->scroll_to_cursor_delayed(view, true, true); + menu.add_action("debug_stop", []() { + if(Project::current) + Project::current->debug_stop(); + }); + menu.add_action("debug_kill", []() { + if(Project::current) + Project::current->debug_kill(); + }); + menu.add_action("debug_step_over", []() { + if(Project::current) + Project::current->debug_step_over(); + }); + menu.add_action("debug_step_into", []() { + if(Project::current) + Project::current->debug_step_into(); + }); + menu.add_action("debug_step_out", []() { + if(Project::current) + Project::current->debug_step_out(); + }); + menu.add_action("debug_backtrace", []() { + if(Project::current) + Project::current->debug_backtrace(); + }); + menu.add_action("debug_show_variables", []() { + if(Project::current) + Project::current->debug_show_variables(); + }); + menu.add_action("debug_run_command", [this]() { + EntryBox::get().clear(); + EntryBox::get().entries.emplace_back(last_run_debug_command, [this](const std::string& content){ + if(content!="") { + if(Project::current) + Project::current->debug_run_command(content); + last_run_debug_command=content; + } + EntryBox::get().hide(); + }, 30); + auto entry_it=EntryBox::get().entries.begin(); + entry_it->set_placeholder_text("Debug Command"); + EntryBox::get().buttons.emplace_back("Run debug command", [entry_it](){ + entry_it->activate(); + }); + EntryBox::get().show(); + }); + menu.add_action("debug_toggle_breakpoint", [](){ + if(auto view=Notebook::get().get_current_view()) { + if(view->toggle_breakpoint) + view->toggle_breakpoint(view->get_buffer()->get_insert()->get_iter().get_line()); + } + }); + menu.add_action("debug_goto_stop", [](){ + if(Project::debugging) { + if(!Project::debug_stop.first.empty()) { + Notebook::get().open(Project::debug_stop.first); + if(auto view=Notebook::get().get_current_view()) { + int line=Project::debug_stop.second.first; + int index=Project::debug_stop.second.second; + view->place_cursor_at_line_index(line, index); + view->scroll_to_cursor_delayed(view, true, true); + } } } - } - }); - - Project::debug_update_status(""); + }); + + Project::debug_update_status(""); #endif - - menu.add_action("window_next_tab", []() { - Notebook::get().next(); - }); - menu.add_action("window_previous_tab", []() { - Notebook::get().previous(); - }); - menu.add_action("window_close_tab", []() { - if(Notebook::get().get_current_view()) - Notebook::get().close_current(); - }); - menu.add_action("window_toggle_split", [] { - Notebook::get().toggle_split(); - }); - menu.add_action("window_toggle_full_screen", [this] { - if(this->get_window()->get_state() & Gdk::WindowState::WINDOW_STATE_FULLSCREEN) - unfullscreen(); - else - fullscreen(); - }); - menu.add_action("window_toggle_tabs", [] { - Notebook::get().toggle_tabs(); - }); - menu.add_action("window_clear_terminal", [] { - Terminal::get().clear(); - }); - - menu.toggle_menu_items=[] { - auto &menu = Menu::get(); - auto view=Notebook::get().get_current_view(); - - menu.actions["file_reload_file"]->set_enabled(view); - menu.actions["source_spellcheck"]->set_enabled(view); - menu.actions["source_spellcheck_clear"]->set_enabled(view); - menu.actions["source_spellcheck_next_error"]->set_enabled(view); - menu.actions["source_git_next_diff"]->set_enabled(view); - menu.actions["source_git_show_diff"]->set_enabled(view); - menu.actions["source_indentation_set_buffer_tab"]->set_enabled(view); - menu.actions["source_goto_line"]->set_enabled(view); - menu.actions["source_center_cursor"]->set_enabled(view); - - menu.actions["source_indentation_auto_indent_buffer"]->set_enabled(view && view->format_style); - menu.actions["source_comments_toggle"]->set_enabled(view && view->toggle_comments); - menu.actions["source_comments_add_documentation"]->set_enabled(view && view->get_documentation_template); - menu.actions["source_find_documentation"]->set_enabled(view && view->get_token_data); - menu.actions["source_goto_declaration"]->set_enabled(view && view->get_declaration_location); - menu.actions["source_goto_type_declaration"]->set_enabled(view && view->get_type_declaration_location); - menu.actions["source_goto_implementation"]->set_enabled(view && view->get_implementation_locations); - menu.actions["source_goto_declaration_or_implementation"]->set_enabled(view && view->get_declaration_or_implementation_locations); - menu.actions["source_goto_usage"]->set_enabled(view && view->get_usages); - menu.actions["source_goto_method"]->set_enabled(view && view->get_methods); - menu.actions["source_rename"]->set_enabled(view && view->rename_similar_tokens); - menu.actions["source_implement_method"]->set_enabled(view && view->get_method); - menu.actions["source_goto_next_diagnostic"]->set_enabled(view && view->goto_next_diagnostic); - menu.actions["source_apply_fix_its"]->set_enabled(view && view->get_fix_its); + + menu.add_action("window_next_tab", []() { + Notebook::get().next(); + }); + menu.add_action("window_previous_tab", []() { + Notebook::get().previous(); + }); + menu.add_action("window_close_tab", []() { + if (Notebook::get().get_current_view()) + Notebook::get().close_current(); + }); + menu.add_action("window_toggle_split", [] { + Notebook::get().toggle_split(); + }); + menu.add_action("window_toggle_full_screen", [this] { + if (this->get_window()->get_state() & Gdk::WindowState::WINDOW_STATE_FULLSCREEN) + unfullscreen(); + else + fullscreen(); + }); + menu.add_action("window_toggle_tabs", [] { + Notebook::get().toggle_tabs(); + }); + menu.add_action("window_clear_terminal", [] { + Terminal::get().clear(); + }); + + menu.toggle_menu_items = [] { + auto &menu = Menu::get(); + auto view = Notebook::get().get_current_view(); + + menu.actions["file_reload_file"]->set_enabled(view); + menu.actions["source_spellcheck"]->set_enabled(view); + menu.actions["source_spellcheck_clear"]->set_enabled(view); + menu.actions["source_spellcheck_next_error"]->set_enabled(view); + menu.actions["source_git_next_diff"]->set_enabled(view); + menu.actions["source_git_show_diff"]->set_enabled(view); + menu.actions["source_indentation_set_buffer_tab"]->set_enabled(view); + menu.actions["source_goto_line"]->set_enabled(view); + menu.actions["source_center_cursor"]->set_enabled(view); + + menu.actions["source_indentation_auto_indent_buffer"]->set_enabled(view && view->format_style); + menu.actions["source_comments_toggle"]->set_enabled(view && view->toggle_comments); + menu.actions["source_comments_add_documentation"]->set_enabled(view && view->get_documentation_template); + menu.actions["source_find_documentation"]->set_enabled(view && view->get_token_data); + menu.actions["source_goto_declaration"]->set_enabled(view && view->get_declaration_location); + menu.actions["source_goto_type_declaration"]->set_enabled(view && view->get_type_declaration_location); + menu.actions["source_goto_implementation"]->set_enabled(view && view->get_implementation_locations); + menu.actions["source_goto_declaration_or_implementation"]->set_enabled( + view && view->get_declaration_or_implementation_locations); + menu.actions["source_goto_usage"]->set_enabled(view && view->get_usages); + menu.actions["source_goto_method"]->set_enabled(view && view->get_methods); + menu.actions["source_rename"]->set_enabled(view && view->rename_similar_tokens); + menu.actions["source_implement_method"]->set_enabled(view && view->get_method); + menu.actions["source_goto_next_diagnostic"]->set_enabled(view && view->goto_next_diagnostic); + menu.actions["source_apply_fix_its"]->set_enabled(view && view->get_fix_its); #ifdef JUCI_ENABLE_DEBUG - Project::debug_activate_menu_items(); + Project::debug_activate_menu_items(); #endif - }; + }; } void Window::add_widgets() { - auto directories_scrolled_window=Gtk::manage(new Gtk::ScrolledWindow()); - directories_scrolled_window->add(Directories::get()); - - auto notebook_vbox=Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL)); - notebook_vbox->pack_start(Notebook::get()); - notebook_vbox->pack_end(EntryBox::get(), Gtk::PACK_SHRINK); - - auto terminal_scrolled_window=Gtk::manage(new Gtk::ScrolledWindow()); - terminal_scrolled_window->add(Terminal::get()); - - int width, height; - get_default_size(width, height); - - auto notebook_and_terminal_vpaned=Gtk::manage(new Gtk::Paned(Gtk::Orientation::ORIENTATION_VERTICAL)); - notebook_and_terminal_vpaned->set_position(static_cast<int>(0.75*height)); - notebook_and_terminal_vpaned->pack1(*notebook_vbox, Gtk::SHRINK); - notebook_and_terminal_vpaned->pack2(*terminal_scrolled_window, Gtk::SHRINK); - - auto hpaned=Gtk::manage(new Gtk::Paned()); - hpaned->set_position(static_cast<int>(0.2*width)); - hpaned->pack1(*directories_scrolled_window, Gtk::SHRINK); - hpaned->pack2(*notebook_and_terminal_vpaned, Gtk::SHRINK); - - auto status_hbox=Gtk::manage(new Gtk::Box()); - status_hbox->set_homogeneous(true); - status_hbox->pack_start(*Gtk::manage(new Gtk::Box())); - auto status_right_hbox=Gtk::manage(new Gtk::Box()); - status_right_hbox->pack_end(Notebook::get().status_state, Gtk::PACK_SHRINK); - auto status_right_overlay=Gtk::manage(new Gtk::Overlay()); - status_right_overlay->add(*status_right_hbox); - status_right_overlay->add_overlay(Notebook::get().status_diagnostics); - status_hbox->pack_end(*status_right_overlay); - - auto status_overlay=Gtk::manage(new Gtk::Overlay()); - status_overlay->add(*status_hbox); - auto status_file_info_hbox=Gtk::manage(new Gtk::Box); - status_file_info_hbox->pack_start(Notebook::get().status_file_path, Gtk::PACK_SHRINK); - status_file_info_hbox->pack_start(Notebook::get().status_branch, Gtk::PACK_SHRINK); - status_file_info_hbox->pack_start(Notebook::get().status_location, Gtk::PACK_SHRINK); - status_overlay->add_overlay(*status_file_info_hbox); - status_overlay->add_overlay(Project::debug_status_label()); - - auto vbox=Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL)); - vbox->pack_start(*hpaned); - vbox->pack_start(*status_overlay, Gtk::PACK_SHRINK); - - auto overlay_vbox=Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL)); - auto overlay_hbox=Gtk::manage(new Gtk::Box()); - overlay_vbox->set_hexpand(false); - overlay_vbox->set_halign(Gtk::Align::ALIGN_START); - overlay_vbox->pack_start(Info::get(), Gtk::PACK_SHRINK, 20); - overlay_hbox->set_hexpand(false); - overlay_hbox->set_halign(Gtk::Align::ALIGN_END); - overlay_hbox->pack_end(*overlay_vbox, Gtk::PACK_SHRINK, 20); - - auto overlay=Gtk::manage(new Gtk::Overlay()); - overlay->add(*vbox); - overlay->add_overlay(*overlay_hbox); - overlay->set_overlay_pass_through(*overlay_hbox, true); - add(*overlay); - - show_all_children(); - Info::get().hide(); - - //Scroll to end of terminal whenever info is printed - Terminal::get().signal_size_allocate().connect([terminal_scrolled_window](Gtk::Allocation& allocation){ - auto adjustment=terminal_scrolled_window->get_vadjustment(); - adjustment->set_value(adjustment->get_upper()-adjustment->get_page_size()); - Terminal::get().queue_draw(); - }); - - EntryBox::get().signal_show().connect([hpaned, notebook_and_terminal_vpaned, notebook_vbox](){ - hpaned->set_focus_chain({notebook_and_terminal_vpaned}); - notebook_and_terminal_vpaned->set_focus_chain({notebook_vbox}); - notebook_vbox->set_focus_chain({&EntryBox::get()}); - }); - EntryBox::get().signal_hide().connect([hpaned, notebook_and_terminal_vpaned, notebook_vbox](){ - hpaned->unset_focus_chain(); - notebook_and_terminal_vpaned->unset_focus_chain(); - notebook_vbox->unset_focus_chain(); - }); + auto directories_scrolled_window = Gtk::manage(new Gtk::ScrolledWindow()); + directories_scrolled_window->add(Directories::get()); + + auto notebook_vbox = Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL)); + notebook_vbox->pack_start(Notebook::get()); + notebook_vbox->pack_end(EntryBox::get(), Gtk::PACK_SHRINK); + + auto terminal_scrolled_window = Gtk::manage(new Gtk::ScrolledWindow()); + terminal_scrolled_window->add(Terminal::get()); + + int width, height; + get_default_size(width, height); + + auto notebook_and_terminal_vpaned = Gtk::manage(new Gtk::Paned(Gtk::Orientation::ORIENTATION_VERTICAL)); + notebook_and_terminal_vpaned->set_position(static_cast<int>(0.75 * height)); + notebook_and_terminal_vpaned->pack1(*notebook_vbox, Gtk::SHRINK); + notebook_and_terminal_vpaned->pack2(*terminal_scrolled_window, Gtk::SHRINK); + + auto hpaned = Gtk::manage(new Gtk::Paned()); + hpaned->set_position(static_cast<int>(0.2 * width)); + hpaned->pack1(*directories_scrolled_window, Gtk::SHRINK); + hpaned->pack2(*notebook_and_terminal_vpaned, Gtk::SHRINK); + + auto status_hbox = Gtk::manage(new Gtk::Box()); + status_hbox->set_homogeneous(true); + status_hbox->pack_start(*Gtk::manage(new Gtk::Box())); + auto status_right_hbox = Gtk::manage(new Gtk::Box()); + status_right_hbox->pack_end(Notebook::get().status_state, Gtk::PACK_SHRINK); + auto status_right_overlay = Gtk::manage(new Gtk::Overlay()); + status_right_overlay->add(*status_right_hbox); + status_right_overlay->add_overlay(Notebook::get().status_diagnostics); + status_hbox->pack_end(*status_right_overlay); + + auto status_overlay = Gtk::manage(new Gtk::Overlay()); + status_overlay->add(*status_hbox); + auto status_file_info_hbox = Gtk::manage(new Gtk::Box); + status_file_info_hbox->pack_start(Notebook::get().status_file_path, Gtk::PACK_SHRINK); + status_file_info_hbox->pack_start(Notebook::get().status_branch, Gtk::PACK_SHRINK); + status_file_info_hbox->pack_start(Notebook::get().status_location, Gtk::PACK_SHRINK); + status_overlay->add_overlay(*status_file_info_hbox); + status_overlay->add_overlay(Project::debug_status_label()); + + auto vbox = Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL)); + vbox->pack_start(*hpaned); + vbox->pack_start(*status_overlay, Gtk::PACK_SHRINK); + + auto overlay_vbox = Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL)); + auto overlay_hbox = Gtk::manage(new Gtk::Box()); + overlay_vbox->set_hexpand(false); + overlay_vbox->set_halign(Gtk::Align::ALIGN_START); + overlay_vbox->pack_start(Info::get(), Gtk::PACK_SHRINK, 20); + overlay_hbox->set_hexpand(false); + overlay_hbox->set_halign(Gtk::Align::ALIGN_END); + overlay_hbox->pack_end(*overlay_vbox, Gtk::PACK_SHRINK, 20); + + auto overlay = Gtk::manage(new Gtk::Overlay()); + overlay->add(*vbox); + overlay->add_overlay(*overlay_hbox); + overlay->set_overlay_pass_through(*overlay_hbox, true); + add(*overlay); + + show_all_children(); + Info::get().hide(); + + //Scroll to end of terminal whenever info is printed + Terminal::get().signal_size_allocate().connect([terminal_scrolled_window](Gtk::Allocation &allocation) { + auto adjustment = terminal_scrolled_window->get_vadjustment(); + adjustment->set_value(adjustment->get_upper() - adjustment->get_page_size()); + Terminal::get().queue_draw(); + }); + + EntryBox::get().signal_show().connect([hpaned, notebook_and_terminal_vpaned, notebook_vbox]() { + hpaned->set_focus_chain({notebook_and_terminal_vpaned}); + notebook_and_terminal_vpaned->set_focus_chain({notebook_vbox}); + notebook_vbox->set_focus_chain({&EntryBox::get()}); + }); + EntryBox::get().signal_hide().connect([hpaned, notebook_and_terminal_vpaned, notebook_vbox]() { + hpaned->unset_focus_chain(); + notebook_and_terminal_vpaned->unset_focus_chain(); + notebook_vbox->unset_focus_chain(); + }); } bool Window::on_key_press_event(GdkEventKey *event) { - if(event->keyval==GDK_KEY_Escape) { - EntryBox::get().hide(); - } -#ifdef __APPLE__ //For Apple's Command-left, right, up, down keys - else if((event->state & GDK_META_MASK)>0 && (event->state & GDK_MOD1_MASK)==0) { - if(event->keyval==GDK_KEY_Left || event->keyval==GDK_KEY_KP_Left) { - event->keyval=GDK_KEY_Home; - event->state=event->state & GDK_SHIFT_MASK; - event->hardware_keycode=115; - } - else if(event->keyval==GDK_KEY_Right || event->keyval==GDK_KEY_KP_Right) { - event->keyval=GDK_KEY_End; - event->state=event->state & GDK_SHIFT_MASK; - event->hardware_keycode=119; - } - else if(event->keyval==GDK_KEY_Up || event->keyval==GDK_KEY_KP_Up) { - event->keyval=GDK_KEY_Home; - event->state=event->state & GDK_SHIFT_MASK; - event->state+=GDK_CONTROL_MASK; - event->hardware_keycode=115; + if (event->keyval == GDK_KEY_Escape) { + EntryBox::get().hide(); } - else if(event->keyval==GDK_KEY_Down || event->keyval==GDK_KEY_KP_Down) { - event->keyval=GDK_KEY_End; - event->state=event->state & GDK_SHIFT_MASK; - event->state+=GDK_CONTROL_MASK; - event->hardware_keycode=119; +#ifdef __APPLE__ //For Apple's Command-left, right, up, down keys + else if((event->state & GDK_META_MASK)>0 && (event->state & GDK_MOD1_MASK)==0) { + if(event->keyval==GDK_KEY_Left || event->keyval==GDK_KEY_KP_Left) { + event->keyval=GDK_KEY_Home; + event->state=event->state & GDK_SHIFT_MASK; + event->hardware_keycode=115; + } + else if(event->keyval==GDK_KEY_Right || event->keyval==GDK_KEY_KP_Right) { + event->keyval=GDK_KEY_End; + event->state=event->state & GDK_SHIFT_MASK; + event->hardware_keycode=119; + } + else if(event->keyval==GDK_KEY_Up || event->keyval==GDK_KEY_KP_Up) { + event->keyval=GDK_KEY_Home; + event->state=event->state & GDK_SHIFT_MASK; + event->state+=GDK_CONTROL_MASK; + event->hardware_keycode=115; + } + else if(event->keyval==GDK_KEY_Down || event->keyval==GDK_KEY_KP_Down) { + event->keyval=GDK_KEY_End; + event->state=event->state & GDK_SHIFT_MASK; + event->state+=GDK_CONTROL_MASK; + event->hardware_keycode=119; + } } - } #endif - if(SelectionDialog::get() && SelectionDialog::get()->is_visible()) { - if(SelectionDialog::get()->on_key_press(event)) - return true; - } + if (SelectionDialog::get() && SelectionDialog::get()->is_visible()) { + if (SelectionDialog::get()->on_key_press(event)) + return true; + } - return Gtk::ApplicationWindow::on_key_press_event(event); + return Gtk::ApplicationWindow::on_key_press_event(event); } bool Window::on_delete_event(GdkEventAny *event) { - save_session(); - - for(size_t c=Notebook::get().size()-1;c!=static_cast<size_t>(-1);--c) { - if(!Notebook::get().close(c)) - return true; - } - Terminal::get().kill_async_processes(); + save_session(); + + for (size_t c = Notebook::get().size() - 1; c != static_cast<size_t>(-1); --c) { + if (!Notebook::get().close(c)) + return true; + } + Terminal::get().kill_async_processes(); #ifdef JUCI_ENABLE_DEBUG - if(Project::current) - Project::current->debug_cancel(); + if(Project::current) + Project::current->debug_cancel(); #endif - return false; -} - -void Window::search_and_replace_entry() { - EntryBox::get().clear(); - EntryBox::get().labels.emplace_back(); - auto label_it=EntryBox::get().labels.begin(); - label_it->update=[label_it](int state, const std::string& message){ - if(state==0) { - try { - auto number = stoi(message); - if(number==0) - label_it->set_text(""); - else if(number==1) - label_it->set_text("1 result found"); - else if(number>1) - label_it->set_text(message+" results found"); - } - catch(const std::exception &e) {} - } - }; - EntryBox::get().entries.emplace_back(last_search, [](const std::string& content){ - if(auto view=Notebook::get().get_current_view()) - view->search_forward(); - }); - auto search_entry_it=EntryBox::get().entries.begin(); - search_entry_it->set_placeholder_text("Find"); - if(auto view=Notebook::get().get_current_view()) { - view->update_search_occurrences=[label_it](int number){ - label_it->update(0, std::to_string(number)); - }; - view->search_highlight(search_entry_it->get_text(), case_sensitive_search, regex_search); - } - search_entry_it->signal_key_press_event().connect([](GdkEventKey* event){ - if((event->keyval==GDK_KEY_Return || event->keyval==GDK_KEY_KP_Enter) && (event->state&GDK_SHIFT_MASK)>0) { - if(auto view=Notebook::get().get_current_view()) - view->search_backward(); - } return false; - }); - search_entry_it->signal_changed().connect([this, search_entry_it](){ - last_search=search_entry_it->get_text(); - if(auto view=Notebook::get().get_current_view()) - view->search_highlight(search_entry_it->get_text(), case_sensitive_search, regex_search); - }); - - EntryBox::get().entries.emplace_back(last_replace, [](const std::string &content){ - if(auto view=Notebook::get().get_current_view()) - view->replace_forward(content); - }); - auto replace_entry_it=EntryBox::get().entries.begin(); - replace_entry_it++; - replace_entry_it->set_placeholder_text("Replace"); - replace_entry_it->signal_key_press_event().connect([replace_entry_it](GdkEventKey* event){ - if((event->keyval==GDK_KEY_Return || event->keyval==GDK_KEY_KP_Enter) && (event->state&GDK_SHIFT_MASK)>0) { - if(auto view=Notebook::get().get_current_view()) - view->replace_backward(replace_entry_it->get_text()); - } - return false; - }); - replace_entry_it->signal_changed().connect([this, replace_entry_it](){ - last_replace=replace_entry_it->get_text(); - }); - - EntryBox::get().buttons.emplace_back("↑", [](){ - if(auto view=Notebook::get().get_current_view()) - view->search_backward(); - }); - EntryBox::get().buttons.back().set_tooltip_text("Find Previous\n\nShortcut: Shift+Enter in the Find entry field"); - EntryBox::get().buttons.emplace_back("⇄", [replace_entry_it](){ - if(auto view=Notebook::get().get_current_view()) { - view->replace_forward(replace_entry_it->get_text()); - } - }); - EntryBox::get().buttons.back().set_tooltip_text("Replace Next\n\nShortcut: Enter in the Replace entry field"); - EntryBox::get().buttons.emplace_back("↓", [](){ - if(auto view=Notebook::get().get_current_view()) - view->search_forward(); - }); - EntryBox::get().buttons.back().set_tooltip_text("Find Next\n\nShortcut: Enter in the Find entry field"); - EntryBox::get().buttons.emplace_back("Replace All", [replace_entry_it](){ - if(auto view=Notebook::get().get_current_view()) - view->replace_all(replace_entry_it->get_text()); - }); - EntryBox::get().buttons.back().set_tooltip_text("Replace All"); - - EntryBox::get().toggle_buttons.emplace_back("Aa"); - EntryBox::get().toggle_buttons.back().set_tooltip_text("Match Case"); - EntryBox::get().toggle_buttons.back().set_active(case_sensitive_search); - EntryBox::get().toggle_buttons.back().on_activate=[this, search_entry_it](){ - case_sensitive_search=!case_sensitive_search; - if(auto view=Notebook::get().get_current_view()) - view->search_highlight(search_entry_it->get_text(), case_sensitive_search, regex_search); - }; - EntryBox::get().toggle_buttons.emplace_back(".*"); - EntryBox::get().toggle_buttons.back().set_tooltip_text("Use Regex"); - EntryBox::get().toggle_buttons.back().set_active(regex_search); - EntryBox::get().toggle_buttons.back().on_activate=[this, search_entry_it](){ - regex_search=!regex_search; - if(auto view=Notebook::get().get_current_view()) - view->search_highlight(search_entry_it->get_text(), case_sensitive_search, regex_search); - }; - EntryBox::get().signal_hide().connect([this]() { - for(size_t c=0;c<Notebook::get().size();c++) { - Notebook::get().get_view(c)->update_search_occurrences=nullptr; - Notebook::get().get_view(c)->search_highlight("", case_sensitive_search, regex_search); - } - search_entry_shown=false; - }); - search_entry_shown=true; - EntryBox::get().show(); } -void Window::set_tab_entry() { - EntryBox::get().clear(); - if(auto view=Notebook::get().get_current_view()) { - auto tab_char_and_size=view->get_tab_char_and_size(); - +void Window::search_and_replace_entry() { + EntryBox::get().clear(); EntryBox::get().labels.emplace_back(); - auto label_it=EntryBox::get().labels.begin(); - - EntryBox::get().entries.emplace_back(std::to_string(tab_char_and_size.second)); - auto entry_tab_size_it=EntryBox::get().entries.begin(); - entry_tab_size_it->set_placeholder_text("Tab size"); - - char tab_char=tab_char_and_size.first; - std::string tab_char_string; - if(tab_char==' ') - tab_char_string="space"; - else if(tab_char=='\t') - tab_char_string="tab"; - - EntryBox::get().entries.emplace_back(tab_char_string); - auto entry_tab_char_it=EntryBox::get().entries.rbegin(); - entry_tab_char_it->set_placeholder_text("Tab char"); - - const auto activate_function=[entry_tab_char_it, entry_tab_size_it, label_it](const std::string& content){ - if(auto view=Notebook::get().get_current_view()) { - char tab_char=0; - unsigned tab_size=0; - try { - tab_size = static_cast<unsigned>(std::stoul(entry_tab_size_it->get_text())); - std::string tab_char_string=entry_tab_char_it->get_text(); - std::transform(tab_char_string.begin(), tab_char_string.end(), tab_char_string.begin(), ::tolower); - if(tab_char_string=="space") - tab_char=' '; - else if(tab_char_string=="tab") - tab_char='\t'; + auto label_it = EntryBox::get().labels.begin(); + label_it->update = [label_it](int state, const std::string &message) { + if (state == 0) { + try { + auto number = stoi(message); + if (number == 0) + label_it->set_text(""); + else if (number == 1) + label_it->set_text("1 result found"); + else if (number > 1) + label_it->set_text(message + " results found"); + } + catch (const std::exception &e) {} } - catch(const std::exception &e) {} - - if(tab_char!=0 && tab_size>0) { - view->set_tab_char_and_size(tab_char, tab_size); - EntryBox::get().hide(); + }; + EntryBox::get().entries.emplace_back(last_search, [](const std::string &content) { + if (auto view = Notebook::get().get_current_view()) + view->search_forward(); + }); + auto search_entry_it = EntryBox::get().entries.begin(); + search_entry_it->set_placeholder_text("Find"); + if (auto view = Notebook::get().get_current_view()) { + view->update_search_occurrences = [label_it](int number) { + label_it->update(0, std::to_string(number)); + }; + view->search_highlight(search_entry_it->get_text(), case_sensitive_search, regex_search); + } + search_entry_it->signal_key_press_event().connect([](GdkEventKey *event) { + if ((event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) && + (event->state & GDK_SHIFT_MASK) > 0) { + if (auto view = Notebook::get().get_current_view()) + view->search_backward(); } - else { - label_it->set_text("Tab size must be >0 and tab char set to either 'space' or 'tab'"); + return false; + }); + search_entry_it->signal_changed().connect([this, search_entry_it]() { + last_search = search_entry_it->get_text(); + if (auto view = Notebook::get().get_current_view()) + view->search_highlight(search_entry_it->get_text(), case_sensitive_search, regex_search); + }); + + EntryBox::get().entries.emplace_back(last_replace, [](const std::string &content) { + if (auto view = Notebook::get().get_current_view()) + view->replace_forward(content); + }); + auto replace_entry_it = EntryBox::get().entries.begin(); + replace_entry_it++; + replace_entry_it->set_placeholder_text("Replace"); + replace_entry_it->signal_key_press_event().connect([replace_entry_it](GdkEventKey *event) { + if ((event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) && + (event->state & GDK_SHIFT_MASK) > 0) { + if (auto view = Notebook::get().get_current_view()) + view->replace_backward(replace_entry_it->get_text()); } - } - }; - - entry_tab_char_it->on_activate=activate_function; - entry_tab_size_it->on_activate=activate_function; - - EntryBox::get().buttons.emplace_back("Set tab in current buffer", [entry_tab_char_it](){ - entry_tab_char_it->activate(); - }); - - EntryBox::get().show(); - } -} + return false; + }); + replace_entry_it->signal_changed().connect([this, replace_entry_it]() { + last_replace = replace_entry_it->get_text(); + }); -void Window::goto_line_entry() { - EntryBox::get().clear(); - if(Notebook::get().get_current_view()) { - EntryBox::get().entries.emplace_back("", [](const std::string& content){ - if(auto view=Notebook::get().get_current_view()) { - try { - view->place_cursor_at_line_index(stoi(content)-1, 0); - view->scroll_to_cursor_delayed(view, true, false); + EntryBox::get().buttons.emplace_back("↑", []() { + if (auto view = Notebook::get().get_current_view()) + view->search_backward(); + }); + EntryBox::get().buttons.back().set_tooltip_text("Find Previous\n\nShortcut: Shift+Enter in the Find entry field"); + EntryBox::get().buttons.emplace_back("⇄", [replace_entry_it]() { + if (auto view = Notebook::get().get_current_view()) { + view->replace_forward(replace_entry_it->get_text()); } - catch(const std::exception &e) {} - EntryBox::get().hide(); - } }); - auto entry_it=EntryBox::get().entries.begin(); - entry_it->set_placeholder_text("Line number"); - EntryBox::get().buttons.emplace_back("Go to line", [entry_it](){ - entry_it->activate(); + EntryBox::get().buttons.back().set_tooltip_text("Replace Next\n\nShortcut: Enter in the Replace entry field"); + EntryBox::get().buttons.emplace_back("↓", []() { + if (auto view = Notebook::get().get_current_view()) + view->search_forward(); }); + EntryBox::get().buttons.back().set_tooltip_text("Find Next\n\nShortcut: Enter in the Find entry field"); + EntryBox::get().buttons.emplace_back("Replace All", [replace_entry_it]() { + if (auto view = Notebook::get().get_current_view()) + view->replace_all(replace_entry_it->get_text()); + }); + EntryBox::get().buttons.back().set_tooltip_text("Replace All"); + + EntryBox::get().toggle_buttons.emplace_back("Aa"); + EntryBox::get().toggle_buttons.back().set_tooltip_text("Match Case"); + EntryBox::get().toggle_buttons.back().set_active(case_sensitive_search); + EntryBox::get().toggle_buttons.back().on_activate = [this, search_entry_it]() { + case_sensitive_search = !case_sensitive_search; + if (auto view = Notebook::get().get_current_view()) + view->search_highlight(search_entry_it->get_text(), case_sensitive_search, regex_search); + }; + EntryBox::get().toggle_buttons.emplace_back(".*"); + EntryBox::get().toggle_buttons.back().set_tooltip_text("Use Regex"); + EntryBox::get().toggle_buttons.back().set_active(regex_search); + EntryBox::get().toggle_buttons.back().on_activate = [this, search_entry_it]() { + regex_search = !regex_search; + if (auto view = Notebook::get().get_current_view()) + view->search_highlight(search_entry_it->get_text(), case_sensitive_search, regex_search); + }; + EntryBox::get().signal_hide().connect([this]() { + for (size_t c = 0; c < Notebook::get().size(); c++) { + Notebook::get().get_view(c)->update_search_occurrences = nullptr; + Notebook::get().get_view(c)->search_highlight("", case_sensitive_search, regex_search); + } + search_entry_shown = false; + }); + search_entry_shown = true; EntryBox::get().show(); - } } -void Window::rename_token_entry() { - EntryBox::get().clear(); - if(auto view=Notebook::get().get_current_view()) { - if(view->get_token_spelling && view->rename_similar_tokens) { - auto spelling=std::make_shared<std::string>(view->get_token_spelling()); - if(!spelling->empty()) { - EntryBox::get().entries.emplace_back(*spelling, [view, spelling, iter=view->get_buffer()->get_insert()->get_iter()](const std::string& content){ - //TODO: gtk needs a way to check if iter is valid without dumping g_error message - //iter->get_buffer() will print such a message, but no segfault will occur - if(Notebook::get().get_current_view()==view && content!=*spelling && iter.get_buffer() && view->get_buffer()->get_insert()->get_iter()==iter) - view->rename_similar_tokens(content); - else - Info::get().print("Operation canceled"); - EntryBox::get().hide(); - }); - auto entry_it=EntryBox::get().entries.begin(); - entry_it->set_placeholder_text("New name"); - EntryBox::get().buttons.emplace_back("Rename", [entry_it](){ - entry_it->activate(); +void Window::set_tab_entry() { + EntryBox::get().clear(); + if (auto view = Notebook::get().get_current_view()) { + auto tab_char_and_size = view->get_tab_char_and_size(); + + EntryBox::get().labels.emplace_back(); + auto label_it = EntryBox::get().labels.begin(); + + EntryBox::get().entries.emplace_back(std::to_string(tab_char_and_size.second)); + auto entry_tab_size_it = EntryBox::get().entries.begin(); + entry_tab_size_it->set_placeholder_text("Tab size"); + + char tab_char = tab_char_and_size.first; + std::string tab_char_string; + if (tab_char == ' ') + tab_char_string = "space"; + else if (tab_char == '\t') + tab_char_string = "tab"; + + EntryBox::get().entries.emplace_back(tab_char_string); + auto entry_tab_char_it = EntryBox::get().entries.rbegin(); + entry_tab_char_it->set_placeholder_text("Tab char"); + + const auto activate_function = [entry_tab_char_it, entry_tab_size_it, label_it](const std::string &content) { + if (auto view = Notebook::get().get_current_view()) { + char tab_char = 0; + unsigned tab_size = 0; + try { + tab_size = static_cast<unsigned>(std::stoul(entry_tab_size_it->get_text())); + std::string tab_char_string = entry_tab_char_it->get_text(); + std::transform(tab_char_string.begin(), tab_char_string.end(), tab_char_string.begin(), ::tolower); + if (tab_char_string == "space") + tab_char = ' '; + else if (tab_char_string == "tab") + tab_char = '\t'; + } + catch (const std::exception &e) {} + + if (tab_char != 0 && tab_size > 0) { + view->set_tab_char_and_size(tab_char, tab_size); + EntryBox::get().hide(); + } else { + label_it->set_text("Tab size must be >0 and tab char set to either 'space' or 'tab'"); + } + } + }; + + entry_tab_char_it->on_activate = activate_function; + entry_tab_size_it->on_activate = activate_function; + + EntryBox::get().buttons.emplace_back("Set tab in current buffer", [entry_tab_char_it]() { + entry_tab_char_it->activate(); }); + EntryBox::get().show(); - } } - } } -void Window::save_session() { - try { - boost::property_tree::ptree root_pt; - root_pt.put("folder", Directories::get().path.string()); - - boost::property_tree::ptree files_pt; - for(auto ¬ebook_view: Notebook::get().get_notebook_views()) { - boost::property_tree::ptree file_pt; - file_pt.put("path", notebook_view.second->file_path.string()); - file_pt.put("notebook", notebook_view.first); - auto iter=notebook_view.second->get_buffer()->get_insert()->get_iter(); - file_pt.put("line", iter.get_line()); - file_pt.put("line_offset", iter.get_line_offset()); - files_pt.push_back(std::make_pair("", file_pt)); - } - root_pt.add_child("files", files_pt); - - boost::property_tree::ptree current_file_pt; - if(auto view=Notebook::get().get_current_view()) { - current_file_pt.put("path", view->file_path.string()); - auto iter=view->get_buffer()->get_insert()->get_iter(); - current_file_pt.put("line", iter.get_line()); - current_file_pt.put("line_offset", iter.get_line_offset()); - } - std::string current_path; - if(auto view=Notebook::get().get_current_view()) - current_path=view->file_path.string(); - root_pt.put("current_file", current_path); - - boost::property_tree::ptree run_arguments_pt; - for(auto &run_argument: Project::run_arguments) { - if(run_argument.second.empty()) - continue; - boost::system::error_code ec; - if(boost::filesystem::exists(run_argument.first, ec) && boost::filesystem::is_directory(run_argument.first, ec)) { - boost::property_tree::ptree run_argument_pt; - run_argument_pt.put("path", run_argument.first); - run_argument_pt.put("arguments", run_argument.second); - run_arguments_pt.push_back(std::make_pair("", run_argument_pt)); - } +void Window::goto_line_entry() { + EntryBox::get().clear(); + if (Notebook::get().get_current_view()) { + EntryBox::get().entries.emplace_back("", [](const std::string &content) { + if (auto view = Notebook::get().get_current_view()) { + try { + view->place_cursor_at_line_index(stoi(content) - 1, 0); + view->scroll_to_cursor_delayed(view, true, false); + } + catch (const std::exception &e) {} + EntryBox::get().hide(); + } + }); + auto entry_it = EntryBox::get().entries.begin(); + entry_it->set_placeholder_text("Line number"); + EntryBox::get().buttons.emplace_back("Go to line", [entry_it]() { + entry_it->activate(); + }); + EntryBox::get().show(); } - root_pt.add_child("run_arguments", run_arguments_pt); - - boost::property_tree::ptree debug_run_arguments_pt; - for(auto &debug_run_argument: Project::debug_run_arguments) { - if(debug_run_argument.second.arguments.empty() && !debug_run_argument.second.remote_enabled && debug_run_argument.second.remote_host_port.empty()) - continue; - boost::system::error_code ec; - if(boost::filesystem::exists(debug_run_argument.first, ec) && boost::filesystem::is_directory(debug_run_argument.first, ec)) { - boost::property_tree::ptree debug_run_argument_pt; - debug_run_argument_pt.put("path", debug_run_argument.first); - debug_run_argument_pt.put("arguments", debug_run_argument.second.arguments); - debug_run_argument_pt.put("remote_enabled", debug_run_argument.second.remote_enabled); - debug_run_argument_pt.put("remote_host_port", debug_run_argument.second.remote_host_port); - debug_run_arguments_pt.push_back(std::make_pair("", debug_run_argument_pt)); - } +} + +void Window::rename_token_entry() { + EntryBox::get().clear(); + if (auto view = Notebook::get().get_current_view()) { + if (view->get_token_spelling && view->rename_similar_tokens) { + auto spelling = std::make_shared<std::string>(view->get_token_spelling()); + if (!spelling->empty()) { + EntryBox::get().entries.emplace_back(*spelling, + [view, spelling, iter = view->get_buffer()->get_insert()->get_iter()]( + const std::string &content) { + //TODO: gtk needs a way to check if iter is valid without dumping g_error message + //iter->get_buffer() will print such a message, but no segfault will occur + if (Notebook::get().get_current_view() == view && + content != *spelling && iter.get_buffer() && + view->get_buffer()->get_insert()->get_iter() == iter) + view->rename_similar_tokens(content); + else + Info::get().print("Operation canceled"); + EntryBox::get().hide(); + }); + auto entry_it = EntryBox::get().entries.begin(); + entry_it->set_placeholder_text("New name"); + EntryBox::get().buttons.emplace_back("Rename", [entry_it]() { + entry_it->activate(); + }); + EntryBox::get().show(); + } + } } - root_pt.add_child("debug_run_arguments", debug_run_arguments_pt); - - int width, height; - get_size(width, height); - boost::property_tree::ptree window_pt; - window_pt.put("width", width); - window_pt.put("height", height); - root_pt.add_child("window", window_pt); - - boost::property_tree::write_json((Config::get().home_juci_path/"last_session.json").string(), root_pt); - } - catch(...) {} } -void Window::load_session(std::vector<boost::filesystem::path> &directories, std::vector<std::pair<boost::filesystem::path, size_t> > &files, std::vector<std::pair<int, int> > &file_offsets, std::string ¤t_file, bool read_directories_and_files) { - try { - boost::property_tree::ptree root_pt; - boost::property_tree::read_json((Config::get().home_juci_path/"last_session.json").string(), root_pt); - if(read_directories_and_files) { - auto folder=root_pt.get<std::string>("folder"); - if(!folder.empty() && boost::filesystem::exists(folder) && boost::filesystem::is_directory(folder)) - directories.emplace_back(folder); - - for(auto &file_pt: root_pt.get_child("files")) { - auto file=file_pt.second.get<std::string>("path"); - auto notebook=file_pt.second.get<size_t>("notebook"); - auto line=file_pt.second.get<int>("line"); - auto line_offset=file_pt.second.get<int>("line_offset"); - if(!file.empty() && boost::filesystem::exists(file) && !boost::filesystem::is_directory(file)) { - files.emplace_back(file, notebook); - file_offsets.emplace_back(line, line_offset); +void Window::save_session() { + try { + boost::property_tree::ptree root_pt; + root_pt.put("folder", Directories::get().path.string()); + + boost::property_tree::ptree files_pt; + for (auto ¬ebook_view: Notebook::get().get_notebook_views()) { + boost::property_tree::ptree file_pt; + file_pt.put("path", notebook_view.second->file_path.string()); + file_pt.put("notebook", notebook_view.first); + auto iter = notebook_view.second->get_buffer()->get_insert()->get_iter(); + file_pt.put("line", iter.get_line()); + file_pt.put("line_offset", iter.get_line_offset()); + files_pt.push_back(std::make_pair("", file_pt)); } - } - - current_file=root_pt.get<std::string>("current_file"); + root_pt.add_child("files", files_pt); + + boost::property_tree::ptree current_file_pt; + if (auto view = Notebook::get().get_current_view()) { + current_file_pt.put("path", view->file_path.string()); + auto iter = view->get_buffer()->get_insert()->get_iter(); + current_file_pt.put("line", iter.get_line()); + current_file_pt.put("line_offset", iter.get_line_offset()); + } + std::string current_path; + if (auto view = Notebook::get().get_current_view()) + current_path = view->file_path.string(); + root_pt.put("current_file", current_path); + + boost::property_tree::ptree run_arguments_pt; + for (auto &run_argument: Project::run_arguments) { + if (run_argument.second.empty()) + continue; + boost::system::error_code ec; + if (boost::filesystem::exists(run_argument.first, ec) && + boost::filesystem::is_directory(run_argument.first, ec)) { + boost::property_tree::ptree run_argument_pt; + run_argument_pt.put("path", run_argument.first); + run_argument_pt.put("arguments", run_argument.second); + run_arguments_pt.push_back(std::make_pair("", run_argument_pt)); + } + } + root_pt.add_child("run_arguments", run_arguments_pt); + + boost::property_tree::ptree debug_run_arguments_pt; + for (auto &debug_run_argument: Project::debug_run_arguments) { + if (debug_run_argument.second.arguments.empty() && !debug_run_argument.second.remote_enabled && + debug_run_argument.second.remote_host_port.empty()) + continue; + boost::system::error_code ec; + if (boost::filesystem::exists(debug_run_argument.first, ec) && + boost::filesystem::is_directory(debug_run_argument.first, ec)) { + boost::property_tree::ptree debug_run_argument_pt; + debug_run_argument_pt.put("path", debug_run_argument.first); + debug_run_argument_pt.put("arguments", debug_run_argument.second.arguments); + debug_run_argument_pt.put("remote_enabled", debug_run_argument.second.remote_enabled); + debug_run_argument_pt.put("remote_host_port", debug_run_argument.second.remote_host_port); + debug_run_arguments_pt.push_back(std::make_pair("", debug_run_argument_pt)); + } + } + root_pt.add_child("debug_run_arguments", debug_run_arguments_pt); + + int width, height; + get_size(width, height); + boost::property_tree::ptree window_pt; + window_pt.put("width", width); + window_pt.put("height", height); + root_pt.add_child("window", window_pt); + + boost::property_tree::write_json((Config::get().home_juci_path / "last_session.json").string(), root_pt); } - - for(auto &run_argument: root_pt.get_child(("run_arguments"))) { - auto path=run_argument.second.get<std::string>("path"); - boost::system::error_code ec; - if(boost::filesystem::exists(path, ec) && boost::filesystem::is_directory(path, ec)) - Project::run_arguments.emplace(path, run_argument.second.get<std::string>("arguments")); + catch (...) {} +} + +void Window::load_session(std::vector<boost::filesystem::path> &directories, + std::vector<std::pair<boost::filesystem::path, size_t> > &files, + std::vector<std::pair<int, int> > &file_offsets, std::string ¤t_file, + bool read_directories_and_files) { + try { + boost::property_tree::ptree root_pt; + boost::property_tree::read_json((Config::get().home_juci_path / "last_session.json").string(), root_pt); + if (read_directories_and_files) { + auto folder = root_pt.get<std::string>("folder"); + if (!folder.empty() && boost::filesystem::exists(folder) && boost::filesystem::is_directory(folder)) + directories.emplace_back(folder); + + for (auto &file_pt: root_pt.get_child("files")) { + auto file = file_pt.second.get<std::string>("path"); + auto notebook = file_pt.second.get<size_t>("notebook"); + auto line = file_pt.second.get<int>("line"); + auto line_offset = file_pt.second.get<int>("line_offset"); + if (!file.empty() && boost::filesystem::exists(file) && !boost::filesystem::is_directory(file)) { + files.emplace_back(file, notebook); + file_offsets.emplace_back(line, line_offset); + } + } + + current_file = root_pt.get<std::string>("current_file"); + } + + for (auto &run_argument: root_pt.get_child(("run_arguments"))) { + auto path = run_argument.second.get<std::string>("path"); + boost::system::error_code ec; + if (boost::filesystem::exists(path, ec) && boost::filesystem::is_directory(path, ec)) + Project::run_arguments.emplace(path, run_argument.second.get<std::string>("arguments")); + } + + for (auto &debug_run_argument: root_pt.get_child(("debug_run_arguments"))) { + auto path = debug_run_argument.second.get<std::string>("path"); + boost::system::error_code ec; + if (boost::filesystem::exists(path, ec) && boost::filesystem::is_directory(path, ec)) + Project::debug_run_arguments.emplace(path, Project::DebugRunArguments{ + debug_run_argument.second.get<std::string>("arguments"), + debug_run_argument.second.get<bool>("remote_enabled"), + debug_run_argument.second.get<std::string>("remote_host_port")}); + } + + auto window_pt = root_pt.get_child("window"); + set_default_size(window_pt.get<int>("width"), window_pt.get<int>("height")); } - - for(auto &debug_run_argument: root_pt.get_child(("debug_run_arguments"))) { - auto path=debug_run_argument.second.get<std::string>("path"); - boost::system::error_code ec; - if(boost::filesystem::exists(path, ec) && boost::filesystem::is_directory(path, ec)) - Project::debug_run_arguments.emplace(path, Project::DebugRunArguments{debug_run_argument.second.get<std::string>("arguments"), - debug_run_argument.second.get<bool>("remote_enabled"), - debug_run_argument.second.get<std::string>("remote_host_port")}); + catch (...) { + set_default_size(800, 600); } - - auto window_pt=root_pt.get_child("window"); - set_default_size(window_pt.get<int>("width"), window_pt.get<int>("height")); - } - catch(...) { - set_default_size(800, 600); - } } diff --git a/src/window.h b/src/window.h index fa9b32d8..7f9b062e 100644 --- a/src/window.h +++ b/src/window.h @@ -1,40 +1,55 @@ #pragma once + #include <gtkmm.h> #include <atomic> #include <boost/filesystem.hpp> class Window : public Gtk::ApplicationWindow { - Window(); + Window(); + public: - static Window &get() { - static Window singleton; - return singleton; - } - void add_widgets(); - void save_session(); - void load_session(std::vector<boost::filesystem::path> &directories, std::vector<std::pair<boost::filesystem::path, size_t>> &files, std::vector<std::pair<int, int>> &file_offsets, std::string ¤t_file, bool read_directories_and_files); + static Window &get() { + static Window singleton; + return singleton; + } + + void add_widgets(); + + void save_session(); + + void load_session(std::vector<boost::filesystem::path> &directories, + std::vector<std::pair<boost::filesystem::path, size_t>> &files, + std::vector<std::pair<int, int>> &file_offsets, std::string ¤t_file, + bool read_directories_and_files); protected: - bool on_key_press_event(GdkEventKey *event) override; - bool on_delete_event(GdkEventAny *event) override; + bool on_key_press_event(GdkEventKey *event) override; + + bool on_delete_event(GdkEventAny *event) override; private: - Gtk::AboutDialog about; - - Glib::RefPtr<Gtk::CssProvider> css_provider_theme; - Glib::RefPtr<Gtk::CssProvider> css_provider_tooltips; - - void configure(); - void set_menu_actions(); - void search_and_replace_entry(); - void set_tab_entry(); - void goto_line_entry(); - void rename_token_entry(); - std::string last_search; - std::string last_replace; - std::string last_run_command; - std::string last_run_debug_command; - bool case_sensitive_search=true; - bool regex_search=false; - bool search_entry_shown=false; + Gtk::AboutDialog about; + + Glib::RefPtr<Gtk::CssProvider> css_provider_theme; + Glib::RefPtr<Gtk::CssProvider> css_provider_tooltips; + + void configure(); + + void set_menu_actions(); + + void search_and_replace_entry(); + + void set_tab_entry(); + + void goto_line_entry(); + + void rename_token_entry(); + + std::string last_search; + std::string last_replace; + std::string last_run_command; + std::string last_run_debug_command; + bool case_sensitive_search = true; + bool regex_search = false; + bool search_entry_shown = false; }; From 0892e40f74dead347589209403bef8bff4b18a3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=88=E9=A1=BA=20=E5=88=98?= <yshliu0321@icloud.com> Date: Fri, 18 May 2018 18:26:33 +0800 Subject: [PATCH 02/12] Make Things Compile --- src/buildsystem/buildsystem.h | 9 ++++++--- src/documentation_cppreference.cc | 6 ++---- src/project_build.cc | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/buildsystem/buildsystem.h b/src/buildsystem/buildsystem.h index d384bec2..764e5928 100644 --- a/src/buildsystem/buildsystem.h +++ b/src/buildsystem/buildsystem.h @@ -5,15 +5,18 @@ class BuildSystemBase { public: + virtual ~BuildSystemBase() = default; + auto get_project_path() const { return project_path; } + void set_project_path(const boost::filesystem::path& rhs) { project_path = rhs; } - virtual bool update_default_build(const boost::filesystem::path &default_build_path, bool force = false); + virtual bool update_default_build(const boost::filesystem::path &default_build_path, bool force = false) = 0; - virtual bool update_debug_build(const boost::filesystem::path &debug_build_path, bool force = false); + virtual bool update_debug_build(const boost::filesystem::path &debug_build_path, bool force = false) = 0; virtual boost::filesystem::path - get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path); + get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) = 0; private: boost::filesystem::path project_path; diff --git a/src/documentation_cppreference.cc b/src/documentation_cppreference.cc index ef7365bd..7dc5fbe0 100644 --- a/src/documentation_cppreference.cc +++ b/src/documentation_cppreference.cc @@ -1,7 +1,5 @@ -#include -"documentation_cppreference.h" -#include -<unordered_map> +#include "documentation_cppreference.h" +#include <unordered_map> std::string Documentation::CppReference::get_url(const std::string symbol) noexcept { // Copied from http://upload.cppreference.com/mwiki/images/d/df/html_book_20170409.zip/reference/cppreference-export-ns0,4,8,10.xml diff --git a/src/project_build.cc b/src/project_build.cc index b756f592..ca050a2c 100644 --- a/src/project_build.cc +++ b/src/project_build.cc @@ -98,7 +98,7 @@ boost::filesystem::path Project::Build::get_debug_path() { } Project::CMakeBuild::CMakeBuild(const boost::filesystem::path &path) : Project::Build(), cmake(path) { - project_path = cmake.project_path; + project_path = cmake.get_project_path(); } bool Project::CMakeBuild::update_default(bool force) { From d8ffec5e8cdd1331677ad281b547a0f7f272db10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=88=E9=A1=BA=20=E5=88=98?= <yshliu0321@icloud.com> Date: Fri, 18 May 2018 21:50:15 +0800 Subject: [PATCH 03/12] refactor --- src/buildsystem/buildsystem.cc | 49 ++++++++++++++++++++++++++++++ src/buildsystem/buildsystem.h | 14 +++++---- src/buildsystem/cmake.cc | 47 ++--------------------------- src/buildsystem/cmake.h | 4 +-- src/buildsystem/meson.cc | 55 +++++----------------------------- src/buildsystem/meson.h | 15 ++++------ src/project_build.cc | 2 +- 7 files changed, 76 insertions(+), 110 deletions(-) create mode 100644 src/buildsystem/buildsystem.cc diff --git a/src/buildsystem/buildsystem.cc b/src/buildsystem/buildsystem.cc new file mode 100644 index 00000000..0d528364 --- /dev/null +++ b/src/buildsystem/buildsystem.cc @@ -0,0 +1,49 @@ +#include <regex> +#include "meson.h" +#include "buildsystem.h" +#include "../terminal.h" +#include "../filesystem.h" + +bool BuildSystemBase::create_build_directory(const boost::filesystem::path &build_path) { + if (!boost::filesystem::exists(build_path)) { + boost::system::error_code ec; + boost::filesystem::create_directories(build_path, ec); + if (ec) { + Terminal::get().print("Error: could not create " + build_path.string() + ": " + ec.message() + "\n", + true); + return false; + } + } + return true; +} + +namespace { + bool find_project_impl(const boost::filesystem::path &file_path) { + for (auto &line: filesystem::read_lines(file_path)) { + const static std::regex project_regex("^ *project *\\(.*\\r?$", std::regex::icase); + std::smatch sm; + if (std::regex_match(line, sm, project_regex)) + return true; + } + return false; + } + + boost::filesystem::path find_project(const boost::filesystem::path &path, const std::string &filename) { + auto search_path = is_directory(path) ? path : path.parent_path(); + while (true) { + auto search_file = search_path / filename; + if (exists(search_file)) { + if (find_project_impl(search_file)) { + return search_path; + } + } + if (search_path == search_path.root_directory()) + break; + search_path = search_path.parent_path(); + } + return ""; + } +} + +BuildSystemBase::BuildSystemBase(const boost::filesystem::path &path, const std::string &filename) : project_path( + find_project(path, filename)) {} diff --git a/src/buildsystem/buildsystem.h b/src/buildsystem/buildsystem.h index 764e5928..962748ab 100644 --- a/src/buildsystem/buildsystem.h +++ b/src/buildsystem/buildsystem.h @@ -5,19 +5,21 @@ class BuildSystemBase { public: - virtual ~BuildSystemBase() = default; + BuildSystemBase(const boost::filesystem::path &path, const std::string &filename); - auto get_project_path() const { return project_path; } + virtual ~BuildSystemBase() = default; - void set_project_path(const boost::filesystem::path& rhs) { project_path = rhs; } + auto& get_project_path() const { return project_path; } - virtual bool update_default_build(const boost::filesystem::path &default_build_path, bool force = false) = 0; + virtual bool update_default_build(const boost::filesystem::path &default_build_path, bool force) = 0; - virtual bool update_debug_build(const boost::filesystem::path &debug_build_path, bool force = false) = 0; + virtual bool update_debug_build(const boost::filesystem::path &debug_build_path, bool force) = 0; virtual boost::filesystem::path get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) = 0; +protected: + static bool create_build_directory(const boost::filesystem::path &build_path); private: - boost::filesystem::path project_path; + const boost::filesystem::path project_path; }; diff --git a/src/buildsystem/cmake.cc b/src/buildsystem/cmake.cc index c656c536..c2a5d39b 100644 --- a/src/buildsystem/cmake.cc +++ b/src/buildsystem/cmake.cc @@ -6,47 +6,14 @@ #include <regex> #include "../compile_commands.h" -CMake::CMake(const boost::filesystem::path &path) { - const auto find_cmake_project = [](const boost::filesystem::path &cmake_path) { - for (auto &line: filesystem::read_lines(cmake_path)) { - const static std::regex project_regex("^ *project *\\(.*\\r?$", std::regex::icase); - std::smatch sm; - if (std::regex_match(line, sm, project_regex)) - return true; - } - return false; - }; - - auto search_path = boost::filesystem::is_directory(path) ? path : path.parent_path(); - while (true) { - auto search_cmake_path = search_path / "CMakeLists.txt"; - if (boost::filesystem::exists(search_cmake_path)) { - paths.emplace(paths.begin(), search_cmake_path); - if (find_cmake_project(search_cmake_path)) { - set_project_path(search_path); - break; - } - } - if (search_path == search_path.root_directory()) - break; - search_path = search_path.parent_path(); - } -} +CMake::CMake(const boost::filesystem::path &path): BuildSystemBase(path, "CMakeLists.txt") {} bool CMake::update_default_build(const boost::filesystem::path &default_build_path, bool force) { if (get_project_path().empty() || !boost::filesystem::exists(get_project_path() / "CMakeLists.txt") || default_build_path.empty()) return false; - if (!boost::filesystem::exists(default_build_path)) { - boost::system::error_code ec; - boost::filesystem::create_directories(default_build_path, ec); - if (ec) { - Terminal::get().print("Error: could not create " + default_build_path.string() + ": " + ec.message() + "\n", - true); - return false; - } - } + if (!create_build_directory(default_build_path)) return false; if (!force && boost::filesystem::exists(default_build_path / "compile_commands.json")) return true; @@ -84,15 +51,7 @@ bool CMake::update_debug_build(const boost::filesystem::path &debug_build_path, if (get_project_path().empty() || !boost::filesystem::exists(get_project_path() / "CMakeLists.txt") || debug_build_path.empty()) return false; - if (!boost::filesystem::exists(debug_build_path)) { - boost::system::error_code ec; - boost::filesystem::create_directories(debug_build_path, ec); - if (ec) { - Terminal::get().print("Error: could not create " + debug_build_path.string() + ": " + ec.message() + "\n", - true); - return false; - } - } + if (!create_build_directory(debug_build_path)) return false; if (!force && boost::filesystem::exists(debug_build_path / "CMakeCache.txt")) return true; diff --git a/src/buildsystem/cmake.h b/src/buildsystem/cmake.h index 9ea4c41b..0f44b7a0 100644 --- a/src/buildsystem/cmake.h +++ b/src/buildsystem/cmake.h @@ -8,9 +8,9 @@ class CMake : public BuildSystemBase { public: CMake(const boost::filesystem::path &path); - bool update_default_build(const boost::filesystem::path &default_build_path, bool force = false) override; + bool update_default_build(const boost::filesystem::path &default_build_path, bool force) override; - bool update_debug_build(const boost::filesystem::path &debug_build_path, bool force = false) override; + bool update_debug_build(const boost::filesystem::path &debug_build_path, bool force) override; boost::filesystem::path get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) override; diff --git a/src/buildsystem/meson.cc b/src/buildsystem/meson.cc index 9b2b994c..a81584a3 100644 --- a/src/buildsystem/meson.cc +++ b/src/buildsystem/meson.cc @@ -1,50 +1,17 @@ #include "meson.h" #include "../filesystem.h" #include "../compile_commands.h" -#include <regex> #include "../terminal.h" #include "../dialogs.h" #include "../config.h" -Meson::Meson(const boost::filesystem::path &path) { - const auto find_project = [](const boost::filesystem::path &file_path) { - for (auto &line: filesystem::read_lines(file_path)) { - const static std::regex project_regex("^ *project *\\(.*\\r?$", std::regex::icase); - std::smatch sm; - if (std::regex_match(line, sm, project_regex)) - return true; - } - return false; - }; - - auto search_path = boost::filesystem::is_directory(path) ? path : path.parent_path(); - while (true) { - auto search_file = search_path / "meson.build"; - if (boost::filesystem::exists(search_file)) { - if (find_project(search_file)) { - project_path = search_path; - break; - } - } - if (search_path == search_path.root_directory()) - break; - search_path = search_path.parent_path(); - } -} +Meson::Meson(const boost::filesystem::path &path): BuildSystemBase(path, "meson.build") {} bool Meson::update_default_build(const boost::filesystem::path &default_build_path, bool force) { - if (project_path.empty() || !boost::filesystem::exists(project_path / "meson.build") || default_build_path.empty()) + if (get_project_path().empty() || !boost::filesystem::exists(get_project_path() / "meson.build") || default_build_path.empty()) return false; - if (!boost::filesystem::exists(default_build_path)) { - boost::system::error_code ec; - boost::filesystem::create_directories(default_build_path, ec); - if (ec) { - Terminal::get().print("Error: could not create " + default_build_path.string() + ": " + ec.message() + "\n", - true); - return false; - } - } + if (!create_build_directory(default_build_path)) return false; auto compile_commands_path = default_build_path / "compile_commands.json"; bool compile_commands_exists = boost::filesystem::exists(compile_commands_path); @@ -54,7 +21,7 @@ bool Meson::update_default_build(const boost::filesystem::path &default_build_pa Dialog::Message message("Creating/updating default build"); auto exit_status = Terminal::get().process( Config::get().project.meson.command + ' ' + (compile_commands_exists ? "--internal regenerate " : "") + - filesystem::escape_argument(project_path.string()), default_build_path); + filesystem::escape_argument(get_project_path().string()), default_build_path); message.hide(); if (exit_status == EXIT_SUCCESS) return true; @@ -62,18 +29,10 @@ bool Meson::update_default_build(const boost::filesystem::path &default_build_pa } bool Meson::update_debug_build(const boost::filesystem::path &debug_build_path, bool force) { - if (project_path.empty() || !boost::filesystem::exists(project_path / "meson.build") || debug_build_path.empty()) + if (get_project_path().empty() || !boost::filesystem::exists(get_project_path() / "meson.build") || debug_build_path.empty()) return false; - if (!boost::filesystem::exists(debug_build_path)) { - boost::system::error_code ec; - boost::filesystem::create_directories(debug_build_path, ec); - if (ec) { - Terminal::get().print("Error: could not create " + debug_build_path.string() + ": " + ec.message() + "\n", - true); - return false; - } - } + if (!create_build_directory(debug_build_path)) return false; bool compile_commands_exists = boost::filesystem::exists(debug_build_path / "compile_commands.json"); if (!force && compile_commands_exists) @@ -82,7 +41,7 @@ bool Meson::update_debug_build(const boost::filesystem::path &debug_build_path, Dialog::Message message("Creating/updating debug build"); auto exit_status = Terminal::get().process( Config::get().project.meson.command + ' ' + (compile_commands_exists ? "--internal regenerate " : "") + - "--buildtype debug " + filesystem::escape_argument(project_path.string()), debug_build_path); + "--buildtype debug " + filesystem::escape_argument(get_project_path().string()), debug_build_path); message.hide(); if (exit_status == EXIT_SUCCESS) return true; diff --git a/src/buildsystem/meson.h b/src/buildsystem/meson.h index 17b9a540..d22625d3 100644 --- a/src/buildsystem/meson.h +++ b/src/buildsystem/meson.h @@ -1,18 +1,15 @@ #pragma once -#include <boost/filesystem.hpp> -#include <vector> - -class Meson { +#include "buildsystem.h" +class Meson : public BuildSystemBase { public: Meson(const boost::filesystem::path &path); - boost::filesystem::path project_path; - - bool update_default_build(const boost::filesystem::path &default_build_path, bool force = false); + bool update_default_build(const boost::filesystem::path &default_build_path, bool force) override; - bool update_debug_build(const boost::filesystem::path &debug_build_path, bool force = false); + bool update_debug_build(const boost::filesystem::path &debug_build_path, bool force) override; boost::filesystem::path - get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path); + get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) override; + }; diff --git a/src/project_build.cc b/src/project_build.cc index ca050a2c..d3294916 100644 --- a/src/project_build.cc +++ b/src/project_build.cc @@ -118,7 +118,7 @@ boost::filesystem::path Project::CMakeBuild::get_executable(const boost::filesys } Project::MesonBuild::MesonBuild(const boost::filesystem::path &path) : Project::Build(), meson(path) { - project_path = meson.project_path; + project_path = meson.get_project_path(); } bool Project::MesonBuild::update_default(bool force) { From c09cf8ba8c8550d2d227bc0af6e0067de9564e83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E4=BA=88=E9=A1=BA?= <ys_37677@126.com> Date: Mon, 21 May 2018 17:05:54 +0800 Subject: [PATCH 04/12] Move Files --- src/{buildsystem => }/buildsystem.cc | 4 ++-- src/{buildsystem => }/buildsystem.h | 0 src/{buildsystem => }/cmake.cc | 10 +++++----- src/{buildsystem => }/cmake.h | 0 src/{buildsystem => }/meson.cc | 10 +++++----- src/{buildsystem => }/meson.h | 0 src/project_build.h | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) rename src/{buildsystem => }/buildsystem.cc (97%) rename src/{buildsystem => }/buildsystem.h (100%) rename src/{buildsystem => }/cmake.cc (99%) rename src/{buildsystem => }/cmake.h (100%) rename src/{buildsystem => }/meson.cc (96%) rename src/{buildsystem => }/meson.h (100%) diff --git a/src/buildsystem/buildsystem.cc b/src/buildsystem.cc similarity index 97% rename from src/buildsystem/buildsystem.cc rename to src/buildsystem.cc index 0d528364..cd0f075a 100644 --- a/src/buildsystem/buildsystem.cc +++ b/src/buildsystem.cc @@ -1,8 +1,8 @@ #include <regex> #include "meson.h" #include "buildsystem.h" -#include "../terminal.h" -#include "../filesystem.h" +#include "terminal.h" +#include "filesystem.h" bool BuildSystemBase::create_build_directory(const boost::filesystem::path &build_path) { if (!boost::filesystem::exists(build_path)) { diff --git a/src/buildsystem/buildsystem.h b/src/buildsystem.h similarity index 100% rename from src/buildsystem/buildsystem.h rename to src/buildsystem.h diff --git a/src/buildsystem/cmake.cc b/src/cmake.cc similarity index 99% rename from src/buildsystem/cmake.cc rename to src/cmake.cc index c2a5d39b..d61f05b8 100644 --- a/src/buildsystem/cmake.cc +++ b/src/cmake.cc @@ -1,10 +1,10 @@ #include "cmake.h" -#include "../filesystem.h" -#include "../dialogs.h" -#include "../config.h" -#include "../terminal.h" +#include "filesystem.h" +#include "dialogs.h" +#include "config.h" +#include "terminal.h" #include <regex> -#include "../compile_commands.h" +#include "compile_commands.h" CMake::CMake(const boost::filesystem::path &path): BuildSystemBase(path, "CMakeLists.txt") {} diff --git a/src/buildsystem/cmake.h b/src/cmake.h similarity index 100% rename from src/buildsystem/cmake.h rename to src/cmake.h diff --git a/src/buildsystem/meson.cc b/src/meson.cc similarity index 96% rename from src/buildsystem/meson.cc rename to src/meson.cc index a81584a3..3bc43006 100644 --- a/src/buildsystem/meson.cc +++ b/src/meson.cc @@ -1,9 +1,9 @@ #include "meson.h" -#include "../filesystem.h" -#include "../compile_commands.h" -#include "../terminal.h" -#include "../dialogs.h" -#include "../config.h" +#include "filesystem.h" +#include "compile_commands.h" +#include "terminal.h" +#include "dialogs.h" +#include "config.h" Meson::Meson(const boost::filesystem::path &path): BuildSystemBase(path, "meson.build") {} diff --git a/src/buildsystem/meson.h b/src/meson.h similarity index 100% rename from src/buildsystem/meson.h rename to src/meson.h diff --git a/src/project_build.h b/src/project_build.h index 0900a851..8720ea30 100644 --- a/src/project_build.h +++ b/src/project_build.h @@ -1,8 +1,8 @@ #pragma once #include <boost/filesystem.hpp> -#include "buildsystem/cmake.h" -#include "buildsystem/meson.h" +#include "cmake.h" +#include "meson.h" namespace Project { class Build { From 76319bbf0c7ad2b4768059a3d8c978da95b7b326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E4=BA=88=E9=A1=BA?= <ys_37677@126.com> Date: Mon, 21 May 2018 17:13:03 +0800 Subject: [PATCH 05/12] Reintendent --- src/autocomplete.cc | 320 +- src/autocomplete.h | 78 +- src/buildsystem.cc | 62 +- src/buildsystem.h | 19 +- src/cmake.cc | 533 +-- src/cmake.h | 36 +- src/compile_commands.cc | 282 +- src/compile_commands.h | 24 +- src/config.cc | 340 +- src/config.h | 200 +- src/ctags.cc | 338 +- src/ctags.h | 32 +- src/debug_lldb.cc | 846 ++--- src/debug_lldb.h | 140 +- src/dialogs.cc | 112 +- src/dialogs.h | 28 +- src/dialogs_unix.cc | 30 +- src/dialogs_win.cc | 248 +- src/directories.cc | 1272 +++---- src/directories.h | 130 +- src/dispatcher.cc | 40 +- src/dispatcher.h | 28 +- src/documentation_cppreference.cc | 59 +- src/documentation_cppreference.h | 8 +- src/entrybox.cc | 150 +- src/entrybox.h | 76 +- src/filesystem.cc | 354 +- src/filesystem.h | 56 +- src/git.cc | 473 ++- src/git.h | 162 +- src/info.cc | 50 +- src/info.h | 16 +- src/juci.cc | 201 +- src/juci.h | 14 +- src/menu.cc | 54 +- src/menu.h | 30 +- src/meson.cc | 110 +- src/meson.h | 11 +- src/notebook.cc | 1124 +++--- src/notebook.h | 126 +- src/project.cc | 932 ++--- src/project.h | 254 +- src/project_build.cc | 186 +- src/project_build.h | 90 +- src/selection_dialog.cc | 778 ++--- src/selection_dialog.h | 158 +- src/source.cc | 5278 ++++++++++++++--------------- src/source.h | 278 +- src/source_base.cc | 582 ++-- src/source_base.h | 138 +- src/source_clang.cc | 3438 +++++++++---------- src/source_clang.h | 184 +- src/source_diff.cc | 616 ++-- src/source_diff.h | 98 +- src/source_language_protocol.cc | 2766 +++++++-------- src/source_language_protocol.h | 188 +- src/source_spellcheck.cc | 858 ++--- src/source_spellcheck.h | 62 +- src/terminal.cc | 692 ++-- src/terminal.h | 62 +- src/tooltips.cc | 410 +-- src/tooltips.h | 68 +- src/usages_clang.cc | 1204 +++---- src/usages_clang.h | 282 +- src/window.cc | 3200 ++++++++--------- src/window.h | 58 +- 66 files changed, 15538 insertions(+), 15534 deletions(-) diff --git a/src/autocomplete.cc b/src/autocomplete.cc index 374e25c7..03c0dcf9 100644 --- a/src/autocomplete.cc +++ b/src/autocomplete.cc @@ -2,178 +2,178 @@ #include "selection_dialog.h" Autocomplete::Autocomplete(Gtk::TextView *view, bool &interactive_completion, guint &last_keyval, bool strip_word) - : view(view), interactive_completion(interactive_completion), strip_word(strip_word), state(State::IDLE) { - view->get_buffer()->signal_changed().connect([this, &last_keyval] { - if (CompletionDialog::get() && CompletionDialog::get()->is_visible()) { - cancel_reparse(); - return; - } - if (!this->view->has_focus()) - return; - if (is_continue_key(last_keyval) && (this->interactive_completion || state != State::IDLE)) - run(); - else { - stop(); - - if (is_restart_key(last_keyval) && this->interactive_completion) - run(); - } - }); + : view(view), interactive_completion(interactive_completion), strip_word(strip_word), state(State::IDLE) { + view->get_buffer()->signal_changed().connect([this, &last_keyval] { + if (CompletionDialog::get() && CompletionDialog::get()->is_visible()) { + cancel_reparse(); + return; + } + if (!this->view->has_focus()) + return; + if (is_continue_key(last_keyval) && (this->interactive_completion || state != State::IDLE)) + run(); + else { + stop(); + + if (is_restart_key(last_keyval) && this->interactive_completion) + run(); + } + }); + + view->get_buffer()->signal_mark_set().connect( + [this](const Gtk::TextBuffer::iterator &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { + if (mark->get_name() == "insert") + stop(); + }); + + view->signal_key_release_event().connect([](GdkEventKey *key) { + if (CompletionDialog::get() && CompletionDialog::get()->is_visible()) { + if (CompletionDialog::get()->on_key_release(key)) + return true; + } + return false; + }, false); - view->get_buffer()->signal_mark_set().connect( - [this](const Gtk::TextBuffer::iterator &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { - if (mark->get_name() == "insert") - stop(); - }); - - view->signal_key_release_event().connect([](GdkEventKey *key) { - if (CompletionDialog::get() && CompletionDialog::get()->is_visible()) { - if (CompletionDialog::get()->on_key_release(key)) - return true; - } - return false; - }, false); - - view->signal_focus_out_event().connect([this](GdkEventFocus *event) { - stop(); - return false; - }); + view->signal_focus_out_event().connect([this](GdkEventFocus *event) { + stop(); + return false; + }); } void Autocomplete::run() { - if (run_check()) { - if (!is_processing()) - return; - - if (state == State::CANCELED) - state = State::RESTARTING; - - if (state != State::IDLE) - return; - - state = State::STARTING; - - before_add_rows(); - - if (thread.joinable()) - thread.join(); - auto buffer = view->get_buffer()->get_text(); - auto iter = view->get_buffer()->get_insert()->get_iter(); - auto line_nr = iter.get_line() + 1; - auto column_nr = iter.get_line_index() + 1; - if (strip_word) { - auto pos = iter.get_offset() - 1; - while (pos >= 0 && - ((buffer[pos] >= 'a' && buffer[pos] <= 'z') || (buffer[pos] >= 'A' && buffer[pos] <= 'Z') || - (buffer[pos] >= '0' && buffer[pos] <= '9') || buffer[pos] == '_')) { - buffer.replace(pos, 1, " "); - column_nr--; - pos--; + if (run_check()) { + if (!is_processing()) + return; + + if (state == State::CANCELED) + state = State::RESTARTING; + + if (state != State::IDLE) + return; + + state = State::STARTING; + + before_add_rows(); + + if (thread.joinable()) + thread.join(); + auto buffer = view->get_buffer()->get_text(); + auto iter = view->get_buffer()->get_insert()->get_iter(); + auto line_nr = iter.get_line() + 1; + auto column_nr = iter.get_line_index() + 1; + if (strip_word) { + auto pos = iter.get_offset() - 1; + while (pos >= 0 && + ((buffer[pos] >= 'a' && buffer[pos] <= 'z') || (buffer[pos] >= 'A' && buffer[pos] <= 'Z') || + (buffer[pos] >= '0' && buffer[pos] <= '9') || buffer[pos] == '_')) { + buffer.replace(pos, 1, " "); + column_nr--; + pos--; + } + } + thread = std::thread([this, line_nr, column_nr, buffer = std::move(buffer)] { + auto lock = get_parse_lock(); + if (!is_processing()) + return; + stop_parse(); + + auto &buffer_raw = const_cast<std::string &>(buffer.raw()); + rows.clear(); + add_rows(buffer_raw, line_nr, column_nr); + + if (is_processing()) { + dispatcher.post([this]() { + after_add_rows(); + if (state == State::RESTARTING) { + state = State::IDLE; + reparse(); + run(); + } else if (state == State::CANCELED || rows.empty()) { + state = State::IDLE; + reparse(); + } else { + auto start_iter = view->get_buffer()->get_insert()->get_iter(); + if (prefix.size() > 0 && !start_iter.backward_chars(prefix.size())) { + state = State::IDLE; + reparse(); + return; } - } - thread = std::thread([this, line_nr, column_nr, buffer = std::move(buffer)] { - auto lock = get_parse_lock(); - if (!is_processing()) - return; - stop_parse(); - - auto &buffer_raw = const_cast<std::string &>(buffer.raw()); - rows.clear(); - add_rows(buffer_raw, line_nr, column_nr); - - if (is_processing()) { - dispatcher.post([this]() { - after_add_rows(); - if (state == State::RESTARTING) { - state = State::IDLE; - reparse(); - run(); - } else if (state == State::CANCELED || rows.empty()) { - state = State::IDLE; - reparse(); - } else { - auto start_iter = view->get_buffer()->get_insert()->get_iter(); - if (prefix.size() > 0 && !start_iter.backward_chars(prefix.size())) { - state = State::IDLE; - reparse(); - return; - } - CompletionDialog::create(view, view->get_buffer()->create_mark(start_iter)); - setup_dialog(); - for (auto &row : rows) { - CompletionDialog::get()->add_row(row); - row.clear(); - } - state = State::IDLE; - - view->get_buffer()->begin_user_action(); - CompletionDialog::get()->show(); - } - }); - } else { - dispatcher.post([this] { - state = State::CANCELED; - on_add_rows_error(); - }); + CompletionDialog::create(view, view->get_buffer()->create_mark(start_iter)); + setup_dialog(); + for (auto &row : rows) { + CompletionDialog::get()->add_row(row); + row.clear(); } + state = State::IDLE; + + view->get_buffer()->begin_user_action(); + CompletionDialog::get()->show(); + } }); - } + } else { + dispatcher.post([this] { + state = State::CANCELED; + on_add_rows_error(); + }); + } + }); + } - if (state != State::IDLE) - cancel_reparse(); + if (state != State::IDLE) + cancel_reparse(); } void Autocomplete::stop() { - if (state == State::STARTING || state == State::RESTARTING) - state = State::CANCELED; + if (state == State::STARTING || state == State::RESTARTING) + state = State::CANCELED; } void Autocomplete::setup_dialog() { - CompletionDialog::get()->on_show = [this] { - on_show(); - }; - - CompletionDialog::get()->on_hide = [this]() { - view->get_buffer()->end_user_action(); - tooltips.hide(); - tooltips.clear(); - on_hide(); - reparse(); - }; - - CompletionDialog::get()->on_changed = [this](unsigned int index, const std::string &text) { - if (index >= rows.size()) { - tooltips.hide(); - return; - } - - on_changed(index, text); - - auto tooltip = get_tooltip(index); - if (tooltip.empty()) - tooltips.hide(); - else { - tooltips.clear(); - auto create_tooltip_buffer = [this, tooltip = std::move(tooltip)]() { - auto tooltip_buffer = Gtk::TextBuffer::create(view->get_buffer()->get_tag_table()); - - tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), tooltip); - - return tooltip_buffer; - }; - - auto iter = CompletionDialog::get()->start_mark->get_iter(); - tooltips.emplace_back(create_tooltip_buffer, view, view->get_buffer()->create_mark(iter), - view->get_buffer()->create_mark(iter)); - - tooltips.show(true); - } - }; - - CompletionDialog::get()->on_select = [this](unsigned int index, const std::string &text, bool hide_window) { - if (index >= rows.size()) - return; - - on_select(index, text, hide_window); - }; + CompletionDialog::get()->on_show = [this] { + on_show(); + }; + + CompletionDialog::get()->on_hide = [this]() { + view->get_buffer()->end_user_action(); + tooltips.hide(); + tooltips.clear(); + on_hide(); + reparse(); + }; + + CompletionDialog::get()->on_changed = [this](unsigned int index, const std::string &text) { + if (index >= rows.size()) { + tooltips.hide(); + return; + } + + on_changed(index, text); + + auto tooltip = get_tooltip(index); + if (tooltip.empty()) + tooltips.hide(); + else { + tooltips.clear(); + auto create_tooltip_buffer = [this, tooltip = std::move(tooltip)]() { + auto tooltip_buffer = Gtk::TextBuffer::create(view->get_buffer()->get_tag_table()); + + tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), tooltip); + + return tooltip_buffer; + }; + + auto iter = CompletionDialog::get()->start_mark->get_iter(); + tooltips.emplace_back(create_tooltip_buffer, view, view->get_buffer()->create_mark(iter), + view->get_buffer()->create_mark(iter)); + + tooltips.show(true); + } + }; + + CompletionDialog::get()->on_select = [this](unsigned int index, const std::string &text, bool hide_window) { + if (index >= rows.size()) + return; + + on_select(index, text, hide_window); + }; } diff --git a/src/autocomplete.h b/src/autocomplete.h index b808ca04..13a2500d 100644 --- a/src/autocomplete.h +++ b/src/autocomplete.h @@ -6,60 +6,60 @@ #include <thread> class Autocomplete { - Gtk::TextView *view; - bool &interactive_completion; - /// Some libraries/utilities, like libclang, require that autocomplete is started at the beginning of a word - bool strip_word; + Gtk::TextView *view; + bool &interactive_completion; + /// Some libraries/utilities, like libclang, require that autocomplete is started at the beginning of a word + bool strip_word; - Dispatcher dispatcher; + Dispatcher dispatcher; public: - enum class State { - IDLE, STARTING, RESTARTING, CANCELED - }; + enum class State { + IDLE, STARTING, RESTARTING, CANCELED + }; - std::string prefix; - std::mutex prefix_mutex; - std::vector<std::string> rows; - Tooltips tooltips; + std::string prefix; + std::mutex prefix_mutex; + std::vector<std::string> rows; + Tooltips tooltips; - std::atomic<State> state; + std::atomic<State> state; - std::thread thread; + std::thread thread; - std::function<bool()> is_processing = [] { return true; }; - std::function<void()> reparse = [] {}; - std::function<void()> cancel_reparse = [] {}; - std::function<std::unique_ptr<std::lock_guard<std::mutex>>()> get_parse_lock = [] { return nullptr; }; - std::function<void()> stop_parse = [] {}; + std::function<bool()> is_processing = [] { return true; }; + std::function<void()> reparse = [] {}; + std::function<void()> cancel_reparse = [] {}; + std::function<std::unique_ptr<std::lock_guard<std::mutex>>()> get_parse_lock = [] { return nullptr; }; + std::function<void()> stop_parse = [] {}; - std::function<bool(guint last_keyval)> is_continue_key = [](guint) { return false; }; - std::function<bool(guint last_keyval)> is_restart_key = [](guint) { return false; }; - std::function<bool()> run_check = [] { return false; }; + std::function<bool(guint last_keyval)> is_continue_key = [](guint) { return false; }; + std::function<bool(guint last_keyval)> is_restart_key = [](guint) { return false; }; + std::function<bool()> run_check = [] { return false; }; - std::function<void()> before_add_rows = [] {}; - std::function<void()> after_add_rows = [] {}; - std::function<void()> on_add_rows_error = [] {}; + std::function<void()> before_add_rows = [] {}; + std::function<void()> after_add_rows = [] {}; + std::function<void()> on_add_rows_error = [] {}; - /// The handler is not run in the main loop. - std::function<void(std::string &buffer, int line_number, int column)> add_rows = [](std::string &, int, int) {}; + /// The handler is not run in the main loop. + std::function<void(std::string &buffer, int line_number, int column)> add_rows = [](std::string &, int, int) {}; - std::function<void()> on_show = [] {}; - std::function<void()> on_hide = [] {}; - std::function<void(unsigned int, const std::string &)> on_changed = [](unsigned int index, - const std::string &text) {}; - std::function<void(unsigned int, const std::string &, bool)> on_select = [](unsigned int index, - const std::string &text, - bool hide_window) {}; + std::function<void()> on_show = [] {}; + std::function<void()> on_hide = [] {}; + std::function<void(unsigned int, const std::string &)> on_changed = [](unsigned int index, + const std::string &text) {}; + std::function<void(unsigned int, const std::string &, bool)> on_select = [](unsigned int index, + const std::string &text, + bool hide_window) {}; - std::function<std::string(unsigned int)> get_tooltip = [](unsigned int index) { return std::string(); }; + std::function<std::string(unsigned int)> get_tooltip = [](unsigned int index) { return std::string(); }; - Autocomplete(Gtk::TextView *view, bool &interactive_completion, guint &last_keyval, bool strip_word); + Autocomplete(Gtk::TextView *view, bool &interactive_completion, guint &last_keyval, bool strip_word); - void run(); + void run(); - void stop(); + void stop(); private: - void setup_dialog(); + void setup_dialog(); }; diff --git a/src/buildsystem.cc b/src/buildsystem.cc index cd0f075a..e6ddefae 100644 --- a/src/buildsystem.cc +++ b/src/buildsystem.cc @@ -5,45 +5,45 @@ #include "filesystem.h" bool BuildSystemBase::create_build_directory(const boost::filesystem::path &build_path) { - if (!boost::filesystem::exists(build_path)) { - boost::system::error_code ec; - boost::filesystem::create_directories(build_path, ec); - if (ec) { - Terminal::get().print("Error: could not create " + build_path.string() + ": " + ec.message() + "\n", - true); - return false; - } + if (!boost::filesystem::exists(build_path)) { + boost::system::error_code ec; + boost::filesystem::create_directories(build_path, ec); + if (ec) { + Terminal::get().print("Error: could not create " + build_path.string() + ": " + ec.message() + "\n", + true); + return false; } - return true; + } + return true; } namespace { - bool find_project_impl(const boost::filesystem::path &file_path) { - for (auto &line: filesystem::read_lines(file_path)) { - const static std::regex project_regex("^ *project *\\(.*\\r?$", std::regex::icase); - std::smatch sm; - if (std::regex_match(line, sm, project_regex)) - return true; - } - return false; + bool find_project_impl(const boost::filesystem::path &file_path) { + for (auto &line: filesystem::read_lines(file_path)) { + const static std::regex project_regex("^ *project *\\(.*\\r?$", std::regex::icase); + std::smatch sm; + if (std::regex_match(line, sm, project_regex)) + return true; } + return false; + } - boost::filesystem::path find_project(const boost::filesystem::path &path, const std::string &filename) { - auto search_path = is_directory(path) ? path : path.parent_path(); - while (true) { - auto search_file = search_path / filename; - if (exists(search_file)) { - if (find_project_impl(search_file)) { - return search_path; - } - } - if (search_path == search_path.root_directory()) - break; - search_path = search_path.parent_path(); + boost::filesystem::path find_project(const boost::filesystem::path &path, const std::string &filename) { + auto search_path = is_directory(path) ? path : path.parent_path(); + while (true) { + auto search_file = search_path / filename; + if (exists(search_file)) { + if (find_project_impl(search_file)) { + return search_path; } - return ""; + } + if (search_path == search_path.root_directory()) + break; + search_path = search_path.parent_path(); } + return ""; + } } BuildSystemBase::BuildSystemBase(const boost::filesystem::path &path, const std::string &filename) : project_path( - find_project(path, filename)) {} + find_project(path, filename)) {} diff --git a/src/buildsystem.h b/src/buildsystem.h index 962748ab..202a5674 100644 --- a/src/buildsystem.h +++ b/src/buildsystem.h @@ -5,21 +5,22 @@ class BuildSystemBase { public: - BuildSystemBase(const boost::filesystem::path &path, const std::string &filename); + BuildSystemBase(const boost::filesystem::path &path, const std::string &filename); - virtual ~BuildSystemBase() = default; + virtual ~BuildSystemBase() = default; - auto& get_project_path() const { return project_path; } + auto &get_project_path() const { return project_path; } - virtual bool update_default_build(const boost::filesystem::path &default_build_path, bool force) = 0; + virtual bool update_default_build(const boost::filesystem::path &default_build_path, bool force) = 0; - virtual bool update_debug_build(const boost::filesystem::path &debug_build_path, bool force) = 0; + virtual bool update_debug_build(const boost::filesystem::path &debug_build_path, bool force) = 0; - virtual boost::filesystem::path - get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) = 0; + virtual boost::filesystem::path + get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) = 0; protected: - static bool create_build_directory(const boost::filesystem::path &build_path); + static bool create_build_directory(const boost::filesystem::path &build_path); + private: - const boost::filesystem::path project_path; + const boost::filesystem::path project_path; }; diff --git a/src/cmake.cc b/src/cmake.cc index d61f05b8..b67573c9 100644 --- a/src/cmake.cc +++ b/src/cmake.cc @@ -6,340 +6,341 @@ #include <regex> #include "compile_commands.h" -CMake::CMake(const boost::filesystem::path &path): BuildSystemBase(path, "CMakeLists.txt") {} +CMake::CMake(const boost::filesystem::path &path) : BuildSystemBase(path, "CMakeLists.txt") {} bool CMake::update_default_build(const boost::filesystem::path &default_build_path, bool force) { - if (get_project_path().empty() || !boost::filesystem::exists(get_project_path() / "CMakeLists.txt") || - default_build_path.empty()) - return false; + if (get_project_path().empty() || !boost::filesystem::exists(get_project_path() / "CMakeLists.txt") || + default_build_path.empty()) + return false; - if (!create_build_directory(default_build_path)) return false; + if (!create_build_directory(default_build_path)) return false; - if (!force && boost::filesystem::exists(default_build_path / "compile_commands.json")) - return true; + if (!force && boost::filesystem::exists(default_build_path / "compile_commands.json")) + return true; - 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().project.cmake.command + ' ' + - filesystem::escape_argument(get_project_path().string()) + - " -DCMAKE_EXPORT_COMPILE_COMMANDS=ON", default_build_path); - message.hide(); - if (exit_status == EXIT_SUCCESS) { + 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().project.cmake.command + ' ' + + filesystem::escape_argument(get_project_path().string()) + + " -DCMAKE_EXPORT_COMPILE_COMMANDS=ON", default_build_path); + message.hide(); + if (exit_status == EXIT_SUCCESS) { #ifdef _WIN32 //Temporary fix to MSYS2's libclang - auto compile_commands_file = filesystem::read(compile_commands_path); - auto replace_drive = [&compile_commands_file](const std::string ¶m) { - size_t pos = 0; - auto param_size = param.length(); - while ((pos = compile_commands_file.find(param + "/", pos)) != std::string::npos) { - if (pos + param_size + 1 < compile_commands_file.size()) - compile_commands_file.replace(pos, param_size + 2, - param + compile_commands_file[pos + param_size + 1] + ":"); - else - break; - } - }; - replace_drive("-I"); - replace_drive("-isystem "); - filesystem::write(compile_commands_path, compile_commands_file); + auto compile_commands_file = filesystem::read(compile_commands_path); + auto replace_drive = [&compile_commands_file](const std::string ¶m) { + size_t pos = 0; + auto param_size = param.length(); + while ((pos = compile_commands_file.find(param + "/", pos)) != std::string::npos) { + if (pos + param_size + 1 < compile_commands_file.size()) + compile_commands_file.replace(pos, param_size + 2, + param + compile_commands_file[pos + param_size + 1] + ":"); + else + break; + } + }; + replace_drive("-I"); + replace_drive("-isystem "); + filesystem::write(compile_commands_path, compile_commands_file); #endif - return true; - } - return false; + return true; + } + return false; } bool CMake::update_debug_build(const boost::filesystem::path &debug_build_path, bool force) { - if (get_project_path().empty() || !boost::filesystem::exists(get_project_path() / "CMakeLists.txt") || debug_build_path.empty()) - return false; + if (get_project_path().empty() || !boost::filesystem::exists(get_project_path() / "CMakeLists.txt") || + debug_build_path.empty()) + return false; - if (!create_build_directory(debug_build_path)) return false; + if (!create_build_directory(debug_build_path)) return false; - if (!force && boost::filesystem::exists(debug_build_path / "CMakeCache.txt")) - return true; + if (!force && boost::filesystem::exists(debug_build_path / "CMakeCache.txt")) + return true; - Dialog::Message message("Creating/updating debug build"); - auto exit_status = Terminal::get().process(Config::get().project.cmake.command + ' ' + - filesystem::escape_argument(get_project_path().string()) + - " -DCMAKE_BUILD_TYPE=Debug", debug_build_path); - message.hide(); - if (exit_status == EXIT_SUCCESS) - return true; - return false; + Dialog::Message message("Creating/updating debug build"); + auto exit_status = Terminal::get().process(Config::get().project.cmake.command + ' ' + + filesystem::escape_argument(get_project_path().string()) + + " -DCMAKE_BUILD_TYPE=Debug", debug_build_path); + message.hide(); + if (exit_status == EXIT_SUCCESS) + return true; + return false; } boost::filesystem::path CMake::get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) { - // CMake does not store in compile_commands.json if an object is part of an executable or not. - // Therefore, executables are first attempted found in the cmake files. These executables - // are then used to identify if a file in compile_commands.json is part of an executable or not + // CMake does not store in compile_commands.json if an object is part of an executable or not. + // Therefore, executables are first attempted found in the cmake files. These executables + // are then used to identify if a file in compile_commands.json is part of an executable or not - auto parameters = get_functions_parameters("add_executable"); + auto parameters = get_functions_parameters("add_executable"); - std::vector<boost::filesystem::path> cmake_executables; - for (auto ¶meter: parameters) { - if (parameter.second.size() > 1 && parameter.second[0].size() > 0 && - parameter.second[0].compare(0, 2, "${") != 0) { - auto executable = (parameter.first.parent_path() / parameter.second[0]).string(); - auto project_path_str = get_project_path().string(); - size_t pos = executable.find(project_path_str); - if (pos != std::string::npos) - executable.replace(pos, project_path_str.size(), build_path.string()); - cmake_executables.emplace_back(executable); - } + std::vector<boost::filesystem::path> cmake_executables; + for (auto ¶meter: parameters) { + if (parameter.second.size() > 1 && parameter.second[0].size() > 0 && + parameter.second[0].compare(0, 2, "${") != 0) { + auto executable = (parameter.first.parent_path() / parameter.second[0]).string(); + auto project_path_str = get_project_path().string(); + size_t pos = executable.find(project_path_str); + if (pos != std::string::npos) + executable.replace(pos, project_path_str.size(), build_path.string()); + cmake_executables.emplace_back(executable); } + } - CompileCommands compile_commands(build_path); - std::vector<std::pair<boost::filesystem::path, boost::filesystem::path>> command_files_and_maybe_executables; - for (auto &command: compile_commands.commands) { - auto command_file = filesystem::get_normal_path(command.file); - auto values = command.parameter_values("-o"); - if (!values.empty()) { - size_t pos; - values[0].erase(0, 11); - if ((pos = values[0].find(".dir")) != std::string::npos) { - auto executable = command.directory / values[0].substr(0, pos); - command_files_and_maybe_executables.emplace_back(command_file, executable); - } - } + CompileCommands compile_commands(build_path); + std::vector<std::pair<boost::filesystem::path, boost::filesystem::path>> command_files_and_maybe_executables; + for (auto &command: compile_commands.commands) { + auto command_file = filesystem::get_normal_path(command.file); + auto values = command.parameter_values("-o"); + if (!values.empty()) { + size_t pos; + values[0].erase(0, 11); + if ((pos = values[0].find(".dir")) != std::string::npos) { + auto executable = command.directory / values[0].substr(0, pos); + command_files_and_maybe_executables.emplace_back(command_file, executable); + } } + } - size_t best_match_size = -1; - boost::filesystem::path best_match_executable; - - for (auto &cmake_executable: cmake_executables) { - for (auto &command_file_and_maybe_executable: command_files_and_maybe_executables) { - auto &command_file = command_file_and_maybe_executable.first; - auto &maybe_executable = command_file_and_maybe_executable.second; - if (cmake_executable == maybe_executable) { - if (command_file == file_path) - return maybe_executable; - auto command_file_directory = command_file.parent_path(); - if (filesystem::file_in_path(file_path, command_file_directory)) { - auto size = static_cast<size_t>(std::distance(command_file_directory.begin(), - command_file_directory.end())); - if (best_match_size == static_cast<size_t>(-1) || best_match_size < size) { - best_match_size = size; - best_match_executable = maybe_executable; - } - } - } - } - } - if (!best_match_executable.empty()) - return best_match_executable; + size_t best_match_size = -1; + boost::filesystem::path best_match_executable; + for (auto &cmake_executable: cmake_executables) { for (auto &command_file_and_maybe_executable: command_files_and_maybe_executables) { - auto &command_file = command_file_and_maybe_executable.first; - auto &maybe_executable = command_file_and_maybe_executable.second; + auto &command_file = command_file_and_maybe_executable.first; + auto &maybe_executable = command_file_and_maybe_executable.second; + if (cmake_executable == maybe_executable) { if (command_file == file_path) - return maybe_executable; + return maybe_executable; auto command_file_directory = command_file.parent_path(); if (filesystem::file_in_path(file_path, command_file_directory)) { - auto size = static_cast<size_t>(std::distance(command_file_directory.begin(), - command_file_directory.end())); - if (best_match_size == static_cast<size_t>(-1) || best_match_size < size) { - best_match_size = size; - best_match_executable = maybe_executable; - } + auto size = static_cast<size_t>(std::distance(command_file_directory.begin(), + command_file_directory.end())); + if (best_match_size == static_cast<size_t>(-1) || best_match_size < size) { + best_match_size = size; + best_match_executable = maybe_executable; + } } + } } + } + if (!best_match_executable.empty()) return best_match_executable; + + for (auto &command_file_and_maybe_executable: command_files_and_maybe_executables) { + auto &command_file = command_file_and_maybe_executable.first; + auto &maybe_executable = command_file_and_maybe_executable.second; + if (command_file == file_path) + return maybe_executable; + auto command_file_directory = command_file.parent_path(); + if (filesystem::file_in_path(file_path, command_file_directory)) { + auto size = static_cast<size_t>(std::distance(command_file_directory.begin(), + command_file_directory.end())); + if (best_match_size == static_cast<size_t>(-1) || best_match_size < size) { + best_match_size = size; + best_match_executable = maybe_executable; + } + } + } + return best_match_executable; } void CMake::read_files() { - for (auto &path: paths) - files.emplace_back(filesystem::read(path)); + for (auto &path: paths) + files.emplace_back(filesystem::read(path)); } void CMake::remove_tabs() { - for (auto &file: files) { - for (auto &chr: file) { - if (chr == '\t') - chr = ' '; - } + for (auto &file: files) { + for (auto &chr: file) { + if (chr == '\t') + chr = ' '; } + } } void CMake::remove_comments() { - for (auto &file: files) { - size_t pos = 0; - size_t comment_start; - bool inside_comment = false; - while (pos < file.size()) { - if (!inside_comment && file[pos] == '#') { - comment_start = pos; - inside_comment = true; - } - if (inside_comment && file[pos] == '\n') { - file.erase(comment_start, pos - comment_start); - pos -= pos - comment_start; - inside_comment = false; - } - pos++; - } - if (inside_comment) - file.erase(comment_start); + for (auto &file: files) { + size_t pos = 0; + size_t comment_start; + bool inside_comment = false; + while (pos < file.size()) { + if (!inside_comment && file[pos] == '#') { + comment_start = pos; + inside_comment = true; + } + if (inside_comment && file[pos] == '\n') { + file.erase(comment_start, pos - comment_start); + pos -= pos - comment_start; + inside_comment = false; + } + pos++; } + if (inside_comment) + file.erase(comment_start); + } } void CMake::remove_newlines_inside_parentheses() { - for (auto &file: files) { - size_t pos = 0; - bool inside_para = false; - bool inside_quote = false; - char last_char = 0; - while (pos < file.size()) { - if (!inside_quote && file[pos] == '"' && last_char != '\\') - inside_quote = true; - else if (inside_quote && file[pos] == '"' && last_char != '\\') - inside_quote = false; + for (auto &file: files) { + size_t pos = 0; + bool inside_para = false; + bool inside_quote = false; + char last_char = 0; + while (pos < file.size()) { + if (!inside_quote && file[pos] == '"' && last_char != '\\') + inside_quote = true; + else if (inside_quote && file[pos] == '"' && last_char != '\\') + inside_quote = false; - else if (!inside_quote && file[pos] == '(') - inside_para = true; - else if (!inside_quote && file[pos] == ')') - inside_para = false; + else if (!inside_quote && file[pos] == '(') + inside_para = true; + else if (!inside_quote && file[pos] == ')') + inside_para = false; - else if (inside_para && file[pos] == '\n') - file.replace(pos, 1, 1, ' '); - last_char = file[pos]; - pos++; - } + else if (inside_para && file[pos] == '\n') + file.replace(pos, 1, 1, ' '); + last_char = file[pos]; + pos++; } + } } void CMake::parse_variable_parameters(std::string &data) { - size_t pos = 0; - bool inside_quote = false; - char last_char = 0; - while (pos < data.size()) { - if (!inside_quote && data[pos] == '"' && last_char != '\\') { - inside_quote = true; - data.erase(pos, - 1); //TODO: instead remove quote-mark if pasted into a quote, for instance: "test${test}test"<-remove quotes from ${test} - pos--; - } else if (inside_quote && data[pos] == '"' && last_char != '\\') { - inside_quote = false; - data.erase(pos, - 1); //TODO: instead remove quote-mark if pasted into a quote, for instance: "test${test}test"<-remove quotes from ${test} - pos--; - } else if (!inside_quote && data[pos] == ' ' && pos + 1 < data.size() && data[pos + 1] == ' ') { - data.erase(pos, 1); - pos--; - } - - if (pos != static_cast<size_t>(-1)) - last_char = data[pos]; - pos++; + size_t pos = 0; + bool inside_quote = false; + char last_char = 0; + while (pos < data.size()) { + if (!inside_quote && data[pos] == '"' && last_char != '\\') { + inside_quote = true; + data.erase(pos, + 1); //TODO: instead remove quote-mark if pasted into a quote, for instance: "test${test}test"<-remove quotes from ${test} + pos--; + } else if (inside_quote && data[pos] == '"' && last_char != '\\') { + inside_quote = false; + data.erase(pos, + 1); //TODO: instead remove quote-mark if pasted into a quote, for instance: "test${test}test"<-remove quotes from ${test} + pos--; + } else if (!inside_quote && data[pos] == ' ' && pos + 1 < data.size() && data[pos + 1] == ' ') { + data.erase(pos, 1); + pos--; } - for (auto &var: variables) { - auto pos = data.find("${" + var.first + '}'); - while (pos != std::string::npos) { - data.replace(pos, var.first.size() + 3, var.second); - pos = data.find("${" + var.first + '}'); - } + + if (pos != static_cast<size_t>(-1)) + last_char = data[pos]; + pos++; + } + for (auto &var: variables) { + auto pos = data.find("${" + var.first + '}'); + while (pos != std::string::npos) { + data.replace(pos, var.first.size() + 3, var.second); + pos = data.find("${" + var.first + '}'); } + } - //Remove variables we do not know: + //Remove variables we do not know: + pos = data.find("${"); + auto pos_end = data.find("}", pos + 2); + while (pos != std::string::npos && pos_end != std::string::npos) { + data.erase(pos, pos_end - pos + 1); pos = data.find("${"); - auto pos_end = data.find("}", pos + 2); - while (pos != std::string::npos && pos_end != std::string::npos) { - data.erase(pos, pos_end - pos + 1); - pos = data.find("${"); - pos_end = data.find("}", pos + 2); - } + pos_end = data.find("}", pos + 2); + } } void CMake::parse() { - read_files(); - remove_tabs(); - remove_comments(); - remove_newlines_inside_parentheses(); - parsed = true; + read_files(); + remove_tabs(); + remove_comments(); + remove_newlines_inside_parentheses(); + parsed = true; } std::vector<std::string> CMake::get_function_parameters(std::string &data) { - std::vector<std::string> parameters; - size_t pos = 0; - size_t parameter_pos = 0; - bool inside_quote = false; - char last_char = 0; - while (pos < data.size()) { - if (!inside_quote && data[pos] == '"' && last_char != '\\') { - inside_quote = true; - data.erase(pos, 1); - pos--; - } else if (inside_quote && data[pos] == '"' && last_char != '\\') { - inside_quote = false; - data.erase(pos, 1); - pos--; - } else if (!inside_quote && pos + 1 < data.size() && data[pos] == ' ' && data[pos + 1] == ' ') { - data.erase(pos, 1); - pos--; - } else if (!inside_quote && data[pos] == ' ') { - parameters.emplace_back(data.substr(parameter_pos, pos - parameter_pos)); - if (pos + 1 < data.size()) - parameter_pos = pos + 1; - } - - if (pos != static_cast<size_t>(-1)) - last_char = data[pos]; - pos++; + std::vector<std::string> parameters; + size_t pos = 0; + size_t parameter_pos = 0; + bool inside_quote = false; + char last_char = 0; + while (pos < data.size()) { + if (!inside_quote && data[pos] == '"' && last_char != '\\') { + inside_quote = true; + data.erase(pos, 1); + pos--; + } else if (inside_quote && data[pos] == '"' && last_char != '\\') { + inside_quote = false; + data.erase(pos, 1); + pos--; + } else if (!inside_quote && pos + 1 < data.size() && data[pos] == ' ' && data[pos + 1] == ' ') { + data.erase(pos, 1); + pos--; + } else if (!inside_quote && data[pos] == ' ') { + parameters.emplace_back(data.substr(parameter_pos, pos - parameter_pos)); + if (pos + 1 < data.size()) + parameter_pos = pos + 1; } - parameters.emplace_back(data.substr(parameter_pos)); - for (auto &var: variables) { - for (auto ¶meter: parameters) { - auto pos = parameter.find("${" + var.first + '}'); - while (pos != std::string::npos) { - parameter.replace(pos, var.first.size() + 3, var.second); - pos = parameter.find("${" + var.first + '}'); - } - } + + if (pos != static_cast<size_t>(-1)) + last_char = data[pos]; + pos++; + } + parameters.emplace_back(data.substr(parameter_pos)); + for (auto &var: variables) { + for (auto ¶meter: parameters) { + auto pos = parameter.find("${" + var.first + '}'); + while (pos != std::string::npos) { + parameter.replace(pos, var.first.size() + 3, var.second); + pos = parameter.find("${" + var.first + '}'); + } } - return parameters; + } + return parameters; } std::vector<std::pair<boost::filesystem::path, std::vector<std::string> > > CMake::get_functions_parameters(const std::string &name) { - const std::regex function_regex("^ *" + name + " *\\( *(.*)\\) *\\r?$", std::regex::icase); - variables.clear(); - if (!parsed) - parse(); - std::vector<std::pair<boost::filesystem::path, std::vector<std::string> > > functions; - for (size_t c = 0; c < files.size(); ++c) { - size_t pos = 0; - while (pos < files[c].size()) { - auto start_line = pos; - auto end_line = files[c].find('\n', start_line); - if (end_line == std::string::npos) - end_line = files[c].size(); - if (end_line > start_line) { - auto line = files[c].substr(start_line, end_line - start_line); - std::smatch sm; - const static std::regex set_regex("^ *set *\\( *([A-Za-z_][A-Za-z_0-9]*) +(.*)\\) *\\r?$", - std::regex::icase); - const static std::regex project_regex("^ *project *\\( *([^ ]+).*\\) *\\r?$", std::regex::icase); - if (std::regex_match(line, sm, set_regex)) { - auto data = sm[2].str(); - while (data.size() > 0 && data.back() == ' ') - data.pop_back(); - parse_variable_parameters(data); - variables[sm[1].str()] = data; - } else if (std::regex_match(line, sm, project_regex)) { - auto data = sm[1].str(); - parse_variable_parameters(data); - variables["CMAKE_PROJECT_NAME"] = data; //TODO: is this variable deprecated/non-standard? - variables["PROJECT_NAME"] = data; - } - if (std::regex_match(line, sm, function_regex)) { - auto data = sm[1].str(); - while (data.size() > 0 && data.back() == ' ') - data.pop_back(); - auto parameters = get_function_parameters(data); - functions.emplace_back(paths[c], parameters); - } - } - pos = end_line + 1; + const std::regex function_regex("^ *" + name + " *\\( *(.*)\\) *\\r?$", std::regex::icase); + variables.clear(); + if (!parsed) + parse(); + std::vector<std::pair<boost::filesystem::path, std::vector<std::string> > > functions; + for (size_t c = 0; c < files.size(); ++c) { + size_t pos = 0; + while (pos < files[c].size()) { + auto start_line = pos; + auto end_line = files[c].find('\n', start_line); + if (end_line == std::string::npos) + end_line = files[c].size(); + if (end_line > start_line) { + auto line = files[c].substr(start_line, end_line - start_line); + std::smatch sm; + const static std::regex set_regex("^ *set *\\( *([A-Za-z_][A-Za-z_0-9]*) +(.*)\\) *\\r?$", + std::regex::icase); + const static std::regex project_regex("^ *project *\\( *([^ ]+).*\\) *\\r?$", std::regex::icase); + if (std::regex_match(line, sm, set_regex)) { + auto data = sm[2].str(); + while (data.size() > 0 && data.back() == ' ') + data.pop_back(); + parse_variable_parameters(data); + variables[sm[1].str()] = data; + } else if (std::regex_match(line, sm, project_regex)) { + auto data = sm[1].str(); + parse_variable_parameters(data); + variables["CMAKE_PROJECT_NAME"] = data; //TODO: is this variable deprecated/non-standard? + variables["PROJECT_NAME"] = data; + } + if (std::regex_match(line, sm, function_regex)) { + auto data = sm[1].str(); + while (data.size() > 0 && data.back() == ' ') + data.pop_back(); + auto parameters = get_function_parameters(data); + functions.emplace_back(paths[c], parameters); } + } + pos = end_line + 1; } - return functions; + } + return functions; } diff --git a/src/cmake.h b/src/cmake.h index 0f44b7a0..7783ae28 100644 --- a/src/cmake.h +++ b/src/cmake.h @@ -6,36 +6,36 @@ class CMake : public BuildSystemBase { public: - CMake(const boost::filesystem::path &path); + CMake(const boost::filesystem::path &path); - bool update_default_build(const boost::filesystem::path &default_build_path, bool force) override; + bool update_default_build(const boost::filesystem::path &default_build_path, bool force) override; - bool update_debug_build(const boost::filesystem::path &debug_build_path, bool force) override; + bool update_debug_build(const boost::filesystem::path &debug_build_path, bool force) override; - boost::filesystem::path - get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) override; + boost::filesystem::path + get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) override; private: - std::vector<boost::filesystem::path> paths; - std::vector<std::string> files; - std::unordered_map<std::string, std::string> variables; + std::vector<boost::filesystem::path> paths; + std::vector<std::string> files; + std::unordered_map<std::string, std::string> variables; - void read_files(); + void read_files(); - void remove_tabs(); + void remove_tabs(); - void remove_comments(); + void remove_comments(); - void remove_newlines_inside_parentheses(); + void remove_newlines_inside_parentheses(); - void parse_variable_parameters(std::string &data); + void parse_variable_parameters(std::string &data); - void parse(); + void parse(); - std::vector<std::string> get_function_parameters(std::string &data); + std::vector<std::string> get_function_parameters(std::string &data); - std::vector<std::pair<boost::filesystem::path, std::vector<std::string> > > - get_functions_parameters(const std::string &name); + std::vector<std::pair<boost::filesystem::path, std::vector<std::string> > > + get_functions_parameters(const std::string &name); - bool parsed = false; + bool parsed = false; }; diff --git a/src/compile_commands.cc b/src/compile_commands.cc index 7f283d8d..a3e82b75 100644 --- a/src/compile_commands.cc +++ b/src/compile_commands.cc @@ -4,159 +4,159 @@ #include <regex> std::vector<std::string> CompileCommands::Command::parameter_values(const std::string ¶meter_name) const { - std::vector<std::string> parameter_values; - - bool found_argument = false; - for (auto ¶meter: parameters) { - if (found_argument) { - parameter_values.emplace_back(parameter); - found_argument = false; - } else if (parameter == parameter_name) - found_argument = true; - } - - return parameter_values; + std::vector<std::string> parameter_values; + + bool found_argument = false; + for (auto ¶meter: parameters) { + if (found_argument) { + parameter_values.emplace_back(parameter); + found_argument = false; + } else if (parameter == parameter_name) + found_argument = true; + } + + return parameter_values; } CompileCommands::CompileCommands(const boost::filesystem::path &build_path) { - try { - boost::property_tree::ptree root_pt; - boost::property_tree::json_parser::read_json((build_path / "compile_commands.json").string(), root_pt); - - auto commands_pt = root_pt.get_child(""); - for (auto &command: commands_pt) { - boost::filesystem::path directory = command.second.get<std::string>("directory"); - auto parameters_str = command.second.get<std::string>("command"); - boost::filesystem::path file = command.second.get<std::string>("file"); - - std::vector<std::string> parameters; - bool backslash = false; - bool single_quote = false; - bool double_quote = false; - size_t parameter_start_pos = std::string::npos; - size_t parameter_size = 0; - auto add_parameter = [¶meters, ¶meters_str, ¶meter_start_pos, ¶meter_size] { - auto parameter = parameters_str.substr(parameter_start_pos, parameter_size); - // Remove escaping - for (size_t c = 0; c < parameter.size() - 1; ++c) { - if (parameter[c] == '\\') - parameter.replace(c, 2, std::string() + parameter[c + 1]); - } - parameters.emplace_back(parameter); - }; - for (size_t c = 0; c < parameters_str.size(); ++c) { - if (backslash) - backslash = false; - else if (parameters_str[c] == '\\') - backslash = true; - else if ((parameters_str[c] == ' ' || parameters_str[c] == '\t') && !backslash && !single_quote && - !double_quote) { - if (parameter_start_pos != std::string::npos) { - add_parameter(); - parameter_start_pos = std::string::npos; - parameter_size = 0; - } - continue; - } else if (parameters_str[c] == '\'' && !backslash && !double_quote) { - single_quote = !single_quote; - continue; - } else if (parameters_str[c] == '\"' && !backslash && !single_quote) { - double_quote = !double_quote; - continue; - } - - if (parameter_start_pos == std::string::npos) - parameter_start_pos = c; - ++parameter_size; - } - if (parameter_start_pos != std::string::npos) - add_parameter(); - - commands.emplace_back(Command{directory, parameters, boost::filesystem::absolute(file, build_path)}); + try { + boost::property_tree::ptree root_pt; + boost::property_tree::json_parser::read_json((build_path / "compile_commands.json").string(), root_pt); + + auto commands_pt = root_pt.get_child(""); + for (auto &command: commands_pt) { + boost::filesystem::path directory = command.second.get<std::string>("directory"); + auto parameters_str = command.second.get<std::string>("command"); + boost::filesystem::path file = command.second.get<std::string>("file"); + + std::vector<std::string> parameters; + bool backslash = false; + bool single_quote = false; + bool double_quote = false; + size_t parameter_start_pos = std::string::npos; + size_t parameter_size = 0; + auto add_parameter = [¶meters, ¶meters_str, ¶meter_start_pos, ¶meter_size] { + auto parameter = parameters_str.substr(parameter_start_pos, parameter_size); + // Remove escaping + for (size_t c = 0; c < parameter.size() - 1; ++c) { + if (parameter[c] == '\\') + parameter.replace(c, 2, std::string() + parameter[c + 1]); } + parameters.emplace_back(parameter); + }; + for (size_t c = 0; c < parameters_str.size(); ++c) { + if (backslash) + backslash = false; + else if (parameters_str[c] == '\\') + backslash = true; + else if ((parameters_str[c] == ' ' || parameters_str[c] == '\t') && !backslash && !single_quote && + !double_quote) { + if (parameter_start_pos != std::string::npos) { + add_parameter(); + parameter_start_pos = std::string::npos; + parameter_size = 0; + } + continue; + } else if (parameters_str[c] == '\'' && !backslash && !double_quote) { + single_quote = !single_quote; + continue; + } else if (parameters_str[c] == '\"' && !backslash && !single_quote) { + double_quote = !double_quote; + continue; + } + + if (parameter_start_pos == std::string::npos) + parameter_start_pos = c; + ++parameter_size; + } + if (parameter_start_pos != std::string::npos) + add_parameter(); + + commands.emplace_back(Command{directory, parameters, boost::filesystem::absolute(file, build_path)}); } - catch (...) {} + } + catch (...) {} } std::vector<std::string> CompileCommands::get_arguments(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) { - std::string default_std_argument = "-std=c++1y"; - - std::vector<std::string> arguments; - if (!build_path.empty()) { - clangmm::CompilationDatabase db(build_path.string()); - if (db) { - clangmm::CompileCommands commands(file_path.string(), db); - auto cmds = commands.get_commands(); - for (auto &cmd : cmds) { - auto cmd_arguments = cmd.get_arguments(); - bool ignore_next = false; - for (size_t c = 1; c < cmd_arguments.size(); c++) { - if (ignore_next) { - ignore_next = false; - continue; - } else if (cmd_arguments[c] == "-o" || cmd_arguments[c] == "-c") { - ignore_next = true; - continue; - } - arguments.emplace_back(cmd_arguments[c]); - } - } - } else - arguments.emplace_back(default_std_argument); + std::string default_std_argument = "-std=c++1y"; + + std::vector<std::string> arguments; + if (!build_path.empty()) { + clangmm::CompilationDatabase db(build_path.string()); + if (db) { + clangmm::CompileCommands commands(file_path.string(), db); + auto cmds = commands.get_commands(); + for (auto &cmd : cmds) { + auto cmd_arguments = cmd.get_arguments(); + bool ignore_next = false; + for (size_t c = 1; c < cmd_arguments.size(); c++) { + if (ignore_next) { + ignore_next = false; + continue; + } else if (cmd_arguments[c] == "-o" || cmd_arguments[c] == "-c") { + ignore_next = true; + continue; + } + arguments.emplace_back(cmd_arguments[c]); + } + } } else - arguments.emplace_back(default_std_argument); - - auto clang_version_string = clangmm::to_string(clang_getClangVersion()); - const static std::regex clang_version_regex("^[A-Za-z ]+([0-9.]+).*$"); - std::smatch sm; - if (std::regex_match(clang_version_string, sm, clang_version_regex)) { - auto clang_version = sm[1].str(); - arguments.emplace_back("-I/usr/lib/clang/" + clang_version + "/include"); - arguments.emplace_back("-I/usr/lib64/clang/" + clang_version + "/include"); // For Fedora + arguments.emplace_back(default_std_argument); + } else + arguments.emplace_back(default_std_argument); + + auto clang_version_string = clangmm::to_string(clang_getClangVersion()); + const static std::regex clang_version_regex("^[A-Za-z ]+([0-9.]+).*$"); + std::smatch sm; + if (std::regex_match(clang_version_string, sm, clang_version_regex)) { + auto clang_version = sm[1].str(); + arguments.emplace_back("-I/usr/lib/clang/" + clang_version + "/include"); + arguments.emplace_back("-I/usr/lib64/clang/" + clang_version + "/include"); // For Fedora #if defined(__APPLE__) && CINDEX_VERSION_MAJOR == 0 && CINDEX_VERSION_MINOR < 32 // TODO: remove during 2018 if llvm3.7 is no longer in homebrew (CINDEX_VERSION_MINOR=32 equals clang-3.8 I think) - arguments.emplace_back("-I/usr/local/Cellar/llvm/"+clang_version+"/lib/clang/"+clang_version+"/include"); - arguments.emplace_back("-I/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1"); - arguments.emplace_back("-I/Library/Developer/CommandLineTools/usr/bin/../include/c++/v1"); //Added for OS X 10.11 + arguments.emplace_back("-I/usr/local/Cellar/llvm/"+clang_version+"/lib/clang/"+clang_version+"/include"); + arguments.emplace_back("-I/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1"); + arguments.emplace_back("-I/Library/Developer/CommandLineTools/usr/bin/../include/c++/v1"); //Added for OS X 10.11 #endif #ifdef _WIN32 - auto env_msystem_prefix = std::getenv("MSYSTEM_PREFIX"); - if (env_msystem_prefix != nullptr) - arguments.emplace_back("-I" + (boost::filesystem::path(env_msystem_prefix) / "lib/clang" / clang_version / - "include").string()); + auto env_msystem_prefix = std::getenv("MSYSTEM_PREFIX"); + if (env_msystem_prefix != nullptr) + arguments.emplace_back("-I" + (boost::filesystem::path(env_msystem_prefix) / "lib/clang" / clang_version / + "include").string()); #endif - } - arguments.emplace_back("-fretain-comments-from-system-headers"); - - auto extension = file_path.extension().string(); - if (extension == ".h" || //TODO: temporary fix for .h-files (parse as c++) - extension != ".c") - arguments.emplace_back("-xc++"); - - if (extension.empty() || (1 < extension.size() && extension[1] == 'h') || extension == ".tcc" || - extension == ".cuh") { - arguments.emplace_back("-Wno-pragma-once-outside-header"); - arguments.emplace_back("-Wno-pragma-system-header-outside-header"); - arguments.emplace_back("-Wno-include-next-outside-header"); - } - - if (extension == ".cu" || extension == ".cuh") { - arguments.emplace_back("-include"); - arguments.emplace_back("cuda_runtime.h"); - } - - if (extension == ".cl") { - arguments.emplace_back("-xcl"); - arguments.emplace_back("-cl-std=CL2.0"); - arguments.emplace_back("-Xclang"); - arguments.emplace_back("-finclude-default-header"); - arguments.emplace_back("-Wno-gcc-compat"); - } - - if (!build_path.empty()) { - arguments.emplace_back("-working-directory"); - arguments.emplace_back(build_path.string()); - } - - return arguments; + } + arguments.emplace_back("-fretain-comments-from-system-headers"); + + auto extension = file_path.extension().string(); + if (extension == ".h" || //TODO: temporary fix for .h-files (parse as c++) + extension != ".c") + arguments.emplace_back("-xc++"); + + if (extension.empty() || (1 < extension.size() && extension[1] == 'h') || extension == ".tcc" || + extension == ".cuh") { + arguments.emplace_back("-Wno-pragma-once-outside-header"); + arguments.emplace_back("-Wno-pragma-system-header-outside-header"); + arguments.emplace_back("-Wno-include-next-outside-header"); + } + + if (extension == ".cu" || extension == ".cuh") { + arguments.emplace_back("-include"); + arguments.emplace_back("cuda_runtime.h"); + } + + if (extension == ".cl") { + arguments.emplace_back("-xcl"); + arguments.emplace_back("-cl-std=CL2.0"); + arguments.emplace_back("-Xclang"); + arguments.emplace_back("-finclude-default-header"); + arguments.emplace_back("-Wno-gcc-compat"); + } + + if (!build_path.empty()) { + arguments.emplace_back("-working-directory"); + arguments.emplace_back(build_path.string()); + } + + return arguments; } diff --git a/src/compile_commands.h b/src/compile_commands.h index 83c3d6dd..c7bb0f4a 100644 --- a/src/compile_commands.h +++ b/src/compile_commands.h @@ -6,20 +6,20 @@ class CompileCommands { public: - class Command { - public: - boost::filesystem::path directory; - std::vector<std::string> parameters; - boost::filesystem::path file; + class Command { + public: + boost::filesystem::path directory; + std::vector<std::string> parameters; + boost::filesystem::path file; - std::vector<std::string> parameter_values(const std::string ¶meter_name) const; - }; + std::vector<std::string> parameter_values(const std::string ¶meter_name) const; + }; - CompileCommands(const boost::filesystem::path &build_path); + CompileCommands(const boost::filesystem::path &build_path); - std::vector<Command> commands; + std::vector<Command> commands; - /// Return arguments for the given file using libclangmm - static std::vector<std::string> - get_arguments(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path); + /// Return arguments for the given file using libclangmm + static std::vector<std::string> + get_arguments(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path); }; diff --git a/src/config.cc b/src/config.cc index 625da547..bc8d2e8d 100644 --- a/src/config.cc +++ b/src/config.cc @@ -7,203 +7,203 @@ #include <algorithm> Config::Config() { - home_path = filesystem::get_home_path(); - if (home_path.empty()) - throw std::runtime_error("Could not find home path"); - home_juci_path = home_path / ".juci"; + home_path = filesystem::get_home_path(); + if (home_path.empty()) + throw std::runtime_error("Could not find home path"); + home_juci_path = home_path / ".juci"; } void Config::load() { - auto config_json = (home_juci_path / "config" / - "config.json").string(); // This causes some redundant copies, but assures windows support - boost::property_tree::ptree cfg; - try { - find_or_create_config_files(); - boost::property_tree::json_parser::read_json(config_json, cfg); - update(cfg); - read(cfg); - } - catch (const std::exception &e) { - dispatcher.post([config_json, e_what = std::string(e.what())] { - ::Terminal::get().print("Error: could not parse " + config_json + ": " + e_what + "\n", true); - }); - std::stringstream ss; - ss << default_config_file; - boost::property_tree::read_json(ss, cfg); - read(cfg); - } + auto config_json = (home_juci_path / "config" / + "config.json").string(); // This causes some redundant copies, but assures windows support + boost::property_tree::ptree cfg; + try { + find_or_create_config_files(); + boost::property_tree::json_parser::read_json(config_json, cfg); + update(cfg); + read(cfg); + } + catch (const std::exception &e) { + dispatcher.post([config_json, e_what = std::string(e.what())] { + ::Terminal::get().print("Error: could not parse " + config_json + ": " + e_what + "\n", true); + }); + std::stringstream ss; + ss << default_config_file; + boost::property_tree::read_json(ss, cfg); + read(cfg); + } } void Config::find_or_create_config_files() { - auto config_dir = home_juci_path / "config"; - auto config_json = config_dir / "config.json"; - - boost::filesystem::create_directories(config_dir); // io exp captured by calling method - - if (!boost::filesystem::exists(config_json)) - filesystem::write(config_json, default_config_file); - - auto juci_style_path = home_juci_path / "styles"; - boost::filesystem::create_directories(juci_style_path); // io exp captured by calling method - - juci_style_path /= "juci-light.xml"; - if (!boost::filesystem::exists(juci_style_path)) - filesystem::write(juci_style_path, juci_light_style); - juci_style_path = juci_style_path.parent_path(); - juci_style_path /= "juci-dark.xml"; - if (!boost::filesystem::exists(juci_style_path)) - filesystem::write(juci_style_path, juci_dark_style); - juci_style_path = juci_style_path.parent_path(); - juci_style_path /= "juci-dark-blue.xml"; - if (!boost::filesystem::exists(juci_style_path)) - filesystem::write(juci_style_path, juci_dark_blue_style); + auto config_dir = home_juci_path / "config"; + auto config_json = config_dir / "config.json"; + + boost::filesystem::create_directories(config_dir); // io exp captured by calling method + + if (!boost::filesystem::exists(config_json)) + filesystem::write(config_json, default_config_file); + + auto juci_style_path = home_juci_path / "styles"; + boost::filesystem::create_directories(juci_style_path); // io exp captured by calling method + + juci_style_path /= "juci-light.xml"; + if (!boost::filesystem::exists(juci_style_path)) + filesystem::write(juci_style_path, juci_light_style); + juci_style_path = juci_style_path.parent_path(); + juci_style_path /= "juci-dark.xml"; + if (!boost::filesystem::exists(juci_style_path)) + filesystem::write(juci_style_path, juci_dark_style); + juci_style_path = juci_style_path.parent_path(); + juci_style_path /= "juci-dark-blue.xml"; + if (!boost::filesystem::exists(juci_style_path)) + filesystem::write(juci_style_path, juci_dark_blue_style); } void Config::update(boost::property_tree::ptree &cfg) { - boost::property_tree::ptree default_cfg; - bool cfg_ok = true; - if (cfg.get<std::string>("version") != JUCI_VERSION) { - std::stringstream ss; - ss << default_config_file; - boost::property_tree::read_json(ss, default_cfg); - cfg_ok = false; - auto it_version = cfg.find("version"); - if (it_version != cfg.not_found()) { - make_version_dependent_corrections(cfg, default_cfg, it_version->second.data()); - it_version->second.data() = JUCI_VERSION; - } - - auto style_path = home_juci_path / "styles"; - filesystem::write(style_path / "juci-light.xml", juci_light_style); - filesystem::write(style_path / "juci-dark.xml", juci_dark_style); - filesystem::write(style_path / "juci-dark-blue.xml", juci_dark_blue_style); - } else - return; - cfg_ok &= add_missing_nodes(cfg, default_cfg); - cfg_ok &= remove_deprecated_nodes(cfg, default_cfg); - if (!cfg_ok) - boost::property_tree::write_json((home_juci_path / "config" / "config.json").string(), cfg); + boost::property_tree::ptree default_cfg; + bool cfg_ok = true; + if (cfg.get<std::string>("version") != JUCI_VERSION) { + std::stringstream ss; + ss << default_config_file; + boost::property_tree::read_json(ss, default_cfg); + cfg_ok = false; + auto it_version = cfg.find("version"); + if (it_version != cfg.not_found()) { + make_version_dependent_corrections(cfg, default_cfg, it_version->second.data()); + it_version->second.data() = JUCI_VERSION; + } + + auto style_path = home_juci_path / "styles"; + filesystem::write(style_path / "juci-light.xml", juci_light_style); + filesystem::write(style_path / "juci-dark.xml", juci_dark_style); + filesystem::write(style_path / "juci-dark-blue.xml", juci_dark_blue_style); + } else + return; + cfg_ok &= add_missing_nodes(cfg, default_cfg); + cfg_ok &= remove_deprecated_nodes(cfg, default_cfg); + if (!cfg_ok) + boost::property_tree::write_json((home_juci_path / "config" / "config.json").string(), cfg); } void Config::make_version_dependent_corrections(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, const std::string &version) { - auto &keybindings_cfg = cfg.get_child("keybindings"); - try { - if (version <= "1.2.4") { - auto it_file_print = keybindings_cfg.find("print"); - if (it_file_print != keybindings_cfg.not_found() && it_file_print->second.data() == "<primary>p") { - dispatcher.post([] { - ::Terminal::get().print("Preference change: keybindings.print set to \"\"\n"); - }); - it_file_print->second.data() = ""; - } - } - } - catch (const std::exception &e) { - std::cerr << "Error correcting preferences: " << e.what() << std::endl; + auto &keybindings_cfg = cfg.get_child("keybindings"); + try { + if (version <= "1.2.4") { + auto it_file_print = keybindings_cfg.find("print"); + if (it_file_print != keybindings_cfg.not_found() && it_file_print->second.data() == "<primary>p") { + dispatcher.post([] { + ::Terminal::get().print("Preference change: keybindings.print set to \"\"\n"); + }); + it_file_print->second.data() = ""; + } } + } + catch (const std::exception &e) { + std::cerr << "Error correcting preferences: " << e.what() << std::endl; + } } bool Config::add_missing_nodes(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, std::string parent_path) { - if (parent_path.size() > 0) - parent_path += "."; - bool unchanged = true; - for (auto &node: default_cfg) { - auto path = parent_path + node.first; - try { - cfg.get<std::string>(path); - } - catch (const std::exception &e) { - cfg.add(path, node.second.data()); - unchanged = false; - } - unchanged &= add_missing_nodes(cfg, node.second, path); + if (parent_path.size() > 0) + parent_path += "."; + bool unchanged = true; + for (auto &node: default_cfg) { + auto path = parent_path + node.first; + try { + cfg.get<std::string>(path); } - return unchanged; + catch (const std::exception &e) { + cfg.add(path, node.second.data()); + unchanged = false; + } + unchanged &= add_missing_nodes(cfg, node.second, path); + } + return unchanged; } bool Config::remove_deprecated_nodes(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, std::string parent_path) { - if (parent_path.size() > 0) - parent_path += "."; - bool unchanged = true; - for (auto it = cfg.begin(); it != cfg.end();) { - auto path = parent_path + it->first; - try { - default_cfg.get<std::string>(path); - unchanged &= remove_deprecated_nodes(it->second, default_cfg, path); - ++it; - } - catch (const std::exception &e) { - it = cfg.erase(it); - unchanged = false; - } + if (parent_path.size() > 0) + parent_path += "."; + bool unchanged = true; + for (auto it = cfg.begin(); it != cfg.end();) { + auto path = parent_path + it->first; + try { + default_cfg.get<std::string>(path); + unchanged &= remove_deprecated_nodes(it->second, default_cfg, path); + ++it; } - return unchanged; + catch (const std::exception &e) { + it = cfg.erase(it); + unchanged = false; + } + } + return unchanged; } void Config::read(const boost::property_tree::ptree &cfg) { - auto keybindings_pt = cfg.get_child("keybindings"); - for (auto &i : keybindings_pt) { - menu.keys[i.first] = i.second.get_value<std::string>(); + auto keybindings_pt = cfg.get_child("keybindings"); + for (auto &i : keybindings_pt) { + menu.keys[i.first] = i.second.get_value<std::string>(); + } + + auto source_json = cfg.get_child("source"); + source.style = source_json.get<std::string>("style"); + source.font = source_json.get<std::string>("font"); + source.cleanup_whitespace_characters = source_json.get<bool>("cleanup_whitespace_characters"); + source.show_whitespace_characters = source_json.get<std::string>("show_whitespace_characters"); + source.format_style_on_save = source_json.get<bool>("format_style_on_save"); + source.format_style_on_save_if_style_file_found = source_json.get<bool>("format_style_on_save_if_style_file_found"); + source.smart_brackets = source_json.get<bool>("smart_brackets"); + source.smart_inserts = source_json.get<bool>("smart_inserts"); + if (source.smart_inserts) + source.smart_brackets = true; + source.show_map = source_json.get<bool>("show_map"); + source.map_font_size = source_json.get<std::string>("map_font_size"); + source.show_git_diff = source_json.get<bool>("show_git_diff"); + source.show_background_pattern = source_json.get<bool>("show_background_pattern"); + source.show_right_margin = source_json.get<bool>("show_right_margin"); + source.right_margin_position = source_json.get<unsigned>("right_margin_position"); + source.spellcheck_language = source_json.get<std::string>("spellcheck_language"); + source.default_tab_char = source_json.get<char>("default_tab_char"); + source.default_tab_size = source_json.get<unsigned>("default_tab_size"); + source.auto_tab_char_and_size = source_json.get<bool>("auto_tab_char_and_size"); + source.tab_indents_line = source_json.get<bool>("tab_indents_line"); + source.wrap_lines = source_json.get<bool>("wrap_lines"); + source.highlight_current_line = source_json.get<bool>("highlight_current_line"); + source.show_line_numbers = source_json.get<bool>("show_line_numbers"); + source.enable_multiple_cursors = source_json.get<bool>("enable_multiple_cursors"); + source.auto_reload_changed_files = source_json.get<bool>("auto_reload_changed_files"); + source.clang_format_style = source_json.get<std::string>("clang_format_style"); + source.clang_usages_threads = static_cast<unsigned>(source_json.get<int>("clang_usages_threads")); + auto pt_doc_search = cfg.get_child("documentation_searches"); + for (auto &pt_doc_search_lang: pt_doc_search) { + source.documentation_searches[pt_doc_search_lang.first].separator = pt_doc_search_lang.second.get<std::string>( + "separator"); + auto &queries = source.documentation_searches.find(pt_doc_search_lang.first)->second.queries; + for (auto &i: pt_doc_search_lang.second.get_child("queries")) { + queries[i.first] = i.second.get_value<std::string>(); } - - auto source_json = cfg.get_child("source"); - source.style = source_json.get<std::string>("style"); - source.font = source_json.get<std::string>("font"); - source.cleanup_whitespace_characters = source_json.get<bool>("cleanup_whitespace_characters"); - source.show_whitespace_characters = source_json.get<std::string>("show_whitespace_characters"); - source.format_style_on_save = source_json.get<bool>("format_style_on_save"); - source.format_style_on_save_if_style_file_found = source_json.get<bool>("format_style_on_save_if_style_file_found"); - source.smart_brackets = source_json.get<bool>("smart_brackets"); - source.smart_inserts = source_json.get<bool>("smart_inserts"); - if (source.smart_inserts) - source.smart_brackets = true; - source.show_map = source_json.get<bool>("show_map"); - source.map_font_size = source_json.get<std::string>("map_font_size"); - source.show_git_diff = source_json.get<bool>("show_git_diff"); - source.show_background_pattern = source_json.get<bool>("show_background_pattern"); - source.show_right_margin = source_json.get<bool>("show_right_margin"); - source.right_margin_position = source_json.get<unsigned>("right_margin_position"); - source.spellcheck_language = source_json.get<std::string>("spellcheck_language"); - source.default_tab_char = source_json.get<char>("default_tab_char"); - source.default_tab_size = source_json.get<unsigned>("default_tab_size"); - source.auto_tab_char_and_size = source_json.get<bool>("auto_tab_char_and_size"); - source.tab_indents_line = source_json.get<bool>("tab_indents_line"); - source.wrap_lines = source_json.get<bool>("wrap_lines"); - source.highlight_current_line = source_json.get<bool>("highlight_current_line"); - source.show_line_numbers = source_json.get<bool>("show_line_numbers"); - source.enable_multiple_cursors = source_json.get<bool>("enable_multiple_cursors"); - source.auto_reload_changed_files = source_json.get<bool>("auto_reload_changed_files"); - source.clang_format_style = source_json.get<std::string>("clang_format_style"); - source.clang_usages_threads = static_cast<unsigned>(source_json.get<int>("clang_usages_threads")); - auto pt_doc_search = cfg.get_child("documentation_searches"); - for (auto &pt_doc_search_lang: pt_doc_search) { - source.documentation_searches[pt_doc_search_lang.first].separator = pt_doc_search_lang.second.get<std::string>( - "separator"); - auto &queries = source.documentation_searches.find(pt_doc_search_lang.first)->second.queries; - for (auto &i: pt_doc_search_lang.second.get_child("queries")) { - queries[i.first] = i.second.get_value<std::string>(); - } - } - - window.theme_name = cfg.get<std::string>("gtk_theme.name"); - window.theme_variant = cfg.get<std::string>("gtk_theme.variant"); - window.version = cfg.get<std::string>("version"); - - 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.cmake.command = cfg.get<std::string>("project.cmake.command"); - project.cmake.compile_command = cfg.get<std::string>("project.cmake.compile_command"); - project.meson.command = cfg.get<std::string>("project.meson.command"); - project.meson.compile_command = cfg.get<std::string>("project.meson.compile_command"); - project.save_on_compile_or_run = cfg.get<bool>("project.save_on_compile_or_run"); - project.clear_terminal_on_compile = cfg.get<bool>("project.clear_terminal_on_compile"); - project.ctags_command = cfg.get<std::string>("project.ctags_command"); - project.python_command = cfg.get<std::string>("project.python_command"); - - terminal.history_size = cfg.get<int>("terminal.history_size"); - terminal.font = cfg.get<std::string>("terminal.font"); + } + + window.theme_name = cfg.get<std::string>("gtk_theme.name"); + window.theme_variant = cfg.get<std::string>("gtk_theme.variant"); + window.version = cfg.get<std::string>("version"); + + 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.cmake.command = cfg.get<std::string>("project.cmake.command"); + project.cmake.compile_command = cfg.get<std::string>("project.cmake.compile_command"); + project.meson.command = cfg.get<std::string>("project.meson.command"); + project.meson.compile_command = cfg.get<std::string>("project.meson.compile_command"); + project.save_on_compile_or_run = cfg.get<bool>("project.save_on_compile_or_run"); + project.clear_terminal_on_compile = cfg.get<bool>("project.clear_terminal_on_compile"); + project.ctags_command = cfg.get<std::string>("project.ctags_command"); + project.python_command = cfg.get<std::string>("project.python_command"); + + terminal.history_size = cfg.get<int>("terminal.history_size"); + terminal.font = cfg.get<std::string>("terminal.font"); } diff --git a/src/config.h b/src/config.h index 5ec14d86..096374b8 100644 --- a/src/config.h +++ b/src/config.h @@ -10,129 +10,129 @@ class Config { public: - class Menu { + class Menu { + public: + std::unordered_map<std::string, std::string> keys; + }; + + class Window { + public: + std::string theme_name; + std::string theme_variant; + std::string version; + }; + + class Terminal { + public: + int history_size; + std::string font; + }; + + class Project { + public: + class CMake { public: - std::unordered_map<std::string, std::string> keys; + std::string command; + std::string compile_command; }; - class Window { + class Meson { public: - std::string theme_name; - std::string theme_variant; - std::string version; + std::string command; + std::string compile_command; }; - class Terminal { + std::string default_build_path; + std::string debug_build_path; + CMake cmake; + Meson meson; + bool save_on_compile_or_run; + bool clear_terminal_on_compile; + std::string ctags_command; + std::string python_command; + }; + + class Source { + public: + class DocumentationSearch { public: - int history_size; - std::string font; + std::string separator; + std::unordered_map<std::string, std::string> queries; }; - class Project { - public: - class CMake { - public: - std::string command; - std::string compile_command; - }; - - class Meson { - public: - std::string command; - std::string compile_command; - }; - - std::string default_build_path; - std::string debug_build_path; - CMake cmake; - Meson meson; - bool save_on_compile_or_run; - bool clear_terminal_on_compile; - std::string ctags_command; - std::string python_command; - }; + std::string style; + std::string font; + std::string spellcheck_language; - class Source { - public: - class DocumentationSearch { - public: - std::string separator; - std::unordered_map<std::string, std::string> queries; - }; - - std::string style; - std::string font; - std::string spellcheck_language; - - bool cleanup_whitespace_characters; - std::string show_whitespace_characters; - - bool format_style_on_save; - bool format_style_on_save_if_style_file_found; - - bool smart_brackets; - bool smart_inserts; - - bool show_map; - std::string map_font_size; - bool show_git_diff; - bool show_background_pattern; - bool show_right_margin; - unsigned right_margin_position; - - bool auto_tab_char_and_size; - char default_tab_char; - unsigned default_tab_size; - bool tab_indents_line; - bool wrap_lines; - bool highlight_current_line; - bool show_line_numbers; - bool enable_multiple_cursors; - bool auto_reload_changed_files; - - std::string clang_format_style; - unsigned clang_usages_threads; - - std::unordered_map<std::string, DocumentationSearch> documentation_searches; - }; + bool cleanup_whitespace_characters; + std::string show_whitespace_characters; + + bool format_style_on_save; + bool format_style_on_save_if_style_file_found; + + bool smart_brackets; + bool smart_inserts; + + bool show_map; + std::string map_font_size; + bool show_git_diff; + bool show_background_pattern; + bool show_right_margin; + unsigned right_margin_position; + + bool auto_tab_char_and_size; + char default_tab_char; + unsigned default_tab_size; + bool tab_indents_line; + bool wrap_lines; + bool highlight_current_line; + bool show_line_numbers; + bool enable_multiple_cursors; + bool auto_reload_changed_files; + + std::string clang_format_style; + unsigned clang_usages_threads; + + std::unordered_map<std::string, DocumentationSearch> documentation_searches; + }; private: - Config(); + Config(); public: - static Config &get() { - static Config singleton; - return singleton; - } + static Config &get() { + static Config singleton; + return singleton; + } - void load(); + void load(); - Menu menu; - Window window; - Terminal terminal; - Project project; - Source source; + Menu menu; + Window window; + Terminal terminal; + Project project; + Source source; - boost::filesystem::path home_path; - boost::filesystem::path home_juci_path; + boost::filesystem::path home_path; + boost::filesystem::path home_juci_path; private: - /// Used to dispatch Terminal outputs after juCi++ GUI setup and configuration - Dispatcher dispatcher; + /// Used to dispatch Terminal outputs after juCi++ GUI setup and configuration + Dispatcher dispatcher; - void find_or_create_config_files(); + void find_or_create_config_files(); - void update(boost::property_tree::ptree &cfg); + void update(boost::property_tree::ptree &cfg); - void - make_version_dependent_corrections(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, - const std::string &version); + void + make_version_dependent_corrections(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, + const std::string &version); - bool add_missing_nodes(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, - std::string parent_path = ""); + bool add_missing_nodes(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, + std::string parent_path = ""); - bool remove_deprecated_nodes(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, - std::string parent_path = ""); + bool remove_deprecated_nodes(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, + std::string parent_path = ""); - void read(const boost::property_tree::ptree &cfg); + void read(const boost::property_tree::ptree &cfg); }; diff --git a/src/ctags.cc b/src/ctags.cc index e51213c1..08642d53 100644 --- a/src/ctags.cc +++ b/src/ctags.cc @@ -10,197 +10,197 @@ std::pair<boost::filesystem::path, std::unique_ptr<std::stringstream> > Ctags::get_result(const boost::filesystem::path &path) { - auto build = Project::Build::create(path); - auto run_path = build->project_path; - std::string exclude; - if (!run_path.empty()) { - auto relative_default_path = filesystem::get_relative_path(build->get_default_path(), run_path); - if (!relative_default_path.empty()) - exclude += " --exclude=" + relative_default_path.string(); - - auto relative_debug_path = filesystem::get_relative_path(build->get_debug_path(), run_path); - if (!relative_debug_path.empty()) - exclude += " --exclude=" + relative_debug_path.string(); - } else { - boost::system::error_code ec; - if (boost::filesystem::is_directory(path, ec) || ec) - run_path = path; - else - run_path = path.parent_path(); - } - - std::stringstream stdin_stream; - //TODO: when debian stable gets newer g++ version that supports move on streams, remove unique_ptr below - auto stdout_stream = std::make_unique<std::stringstream>(); - auto command = Config::get().project.ctags_command + exclude + - " --fields=ns --sort=foldcase -I \"override noexcept\" -f - -R *"; - Terminal::get().process(stdin_stream, *stdout_stream, command, run_path); - return {run_path, std::move(stdout_stream)}; + auto build = Project::Build::create(path); + auto run_path = build->project_path; + std::string exclude; + if (!run_path.empty()) { + auto relative_default_path = filesystem::get_relative_path(build->get_default_path(), run_path); + if (!relative_default_path.empty()) + exclude += " --exclude=" + relative_default_path.string(); + + auto relative_debug_path = filesystem::get_relative_path(build->get_debug_path(), run_path); + if (!relative_debug_path.empty()) + exclude += " --exclude=" + relative_debug_path.string(); + } else { + boost::system::error_code ec; + if (boost::filesystem::is_directory(path, ec) || ec) + run_path = path; + else + run_path = path.parent_path(); + } + + std::stringstream stdin_stream; + //TODO: when debian stable gets newer g++ version that supports move on streams, remove unique_ptr below + auto stdout_stream = std::make_unique<std::stringstream>(); + auto command = Config::get().project.ctags_command + exclude + + " --fields=ns --sort=foldcase -I \"override noexcept\" -f - -R *"; + Terminal::get().process(stdin_stream, *stdout_stream, command, run_path); + return {run_path, std::move(stdout_stream)}; } Ctags::Location Ctags::get_location(const std::string &line, bool markup) { - Location location; + Location location; #ifdef _WIN32 - auto line_fixed = line; - if (!line_fixed.empty() && line_fixed.back() == '\r') - line_fixed.pop_back(); + auto line_fixed = line; + if (!line_fixed.empty() && line_fixed.back() == '\r') + line_fixed.pop_back(); #else - auto &line_fixed=line; + auto &line_fixed=line; #endif - const static std::regex regex( - "^([^\t]+)\t([^\t]+)\t(?:/\\^)?([ \t]*)(.+?)(\\$/)?;\"\tline:([0-9]+)\t?[a-zA-Z]*:?(.*)$"); - std::smatch sm; - if (std::regex_match(line_fixed, sm, regex)) { - location.symbol = sm[1].str(); - //fix location.symbol for operators - if (9 < location.symbol.size() && location.symbol[8] == ' ' && location.symbol.compare(0, 8, "operator") == 0) { - auto &chr = location.symbol[9]; - if (!((chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z') || (chr >= '0' && chr <= '9') || chr == '_')) - location.symbol.erase(8, 1); - } + const static std::regex regex( + "^([^\t]+)\t([^\t]+)\t(?:/\\^)?([ \t]*)(.+?)(\\$/)?;\"\tline:([0-9]+)\t?[a-zA-Z]*:?(.*)$"); + std::smatch sm; + if (std::regex_match(line_fixed, sm, regex)) { + location.symbol = sm[1].str(); + //fix location.symbol for operators + if (9 < location.symbol.size() && location.symbol[8] == ' ' && location.symbol.compare(0, 8, "operator") == 0) { + auto &chr = location.symbol[9]; + if (!((chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z') || (chr >= '0' && chr <= '9') || chr == '_')) + location.symbol.erase(8, 1); + } - location.file_path = sm[2].str(); - location.source = sm[4].str(); - try { - location.line = std::stoul(sm[6]) - 1; - } - catch (const std::exception &) { - location.line = 0; - } - location.scope = sm[7].str(); - if (!sm[5].str().empty()) { - location.index = sm[3].str().size(); - - size_t pos = location.source.find(location.symbol); - if (pos != std::string::npos) - location.index += pos; - - if (markup) { - location.source = Glib::Markup::escape_text(location.source); - auto symbol = Glib::Markup::escape_text(location.symbol); - pos = -1; - while ((pos = location.source.find(symbol, pos + 1)) != std::string::npos) { - location.source.insert(pos + symbol.size(), "</b>"); - location.source.insert(pos, "<b>"); - pos += 7 + symbol.size(); - } - } - } else { - location.index = 0; - location.source = location.symbol; - if (markup) - location.source = "<b>" + Glib::Markup::escape_text(location.source) + "</b>"; + location.file_path = sm[2].str(); + location.source = sm[4].str(); + try { + location.line = std::stoul(sm[6]) - 1; + } + catch (const std::exception &) { + location.line = 0; + } + location.scope = sm[7].str(); + if (!sm[5].str().empty()) { + location.index = sm[3].str().size(); + + size_t pos = location.source.find(location.symbol); + if (pos != std::string::npos) + location.index += pos; + + if (markup) { + location.source = Glib::Markup::escape_text(location.source); + auto symbol = Glib::Markup::escape_text(location.symbol); + pos = -1; + while ((pos = location.source.find(symbol, pos + 1)) != std::string::npos) { + location.source.insert(pos + symbol.size(), "</b>"); + location.source.insert(pos, "<b>"); + pos += 7 + symbol.size(); } - } else - std::cerr << "Warning (ctags): please report to the juCi++ project that the following line was not parsed:\n" - << line << std::endl; + } + } else { + location.index = 0; + location.source = location.symbol; + if (markup) + location.source = "<b>" + Glib::Markup::escape_text(location.source) + "</b>"; + } + } else + std::cerr << "Warning (ctags): please report to the juCi++ project that the following line was not parsed:\n" + << line << std::endl; - return location; + return location; } ///Split up a type into its various significant parts std::vector<std::string> Ctags::get_type_parts(const std::string type) { - std::vector<std::string> parts; - size_t text_start = -1; - for (size_t c = 0; c < type.size(); ++c) { - auto &chr = type[c]; - if ((chr >= '0' && chr <= '9') || (chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z') || chr == '_' || - chr == '~') { - if (text_start == static_cast<size_t>(-1)) - text_start = c; - } else { - if (text_start != static_cast<size_t>(-1)) { - parts.emplace_back(type.substr(text_start, c - text_start)); - text_start = -1; - } - if (chr == '*' || chr == '&') - parts.emplace_back(std::string() + chr); - } + std::vector<std::string> parts; + size_t text_start = -1; + for (size_t c = 0; c < type.size(); ++c) { + auto &chr = type[c]; + if ((chr >= '0' && chr <= '9') || (chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z') || chr == '_' || + chr == '~') { + if (text_start == static_cast<size_t>(-1)) + text_start = c; + } else { + if (text_start != static_cast<size_t>(-1)) { + parts.emplace_back(type.substr(text_start, c - text_start)); + text_start = -1; + } + if (chr == '*' || chr == '&') + parts.emplace_back(std::string() + chr); } - return parts; + } + return parts; } std::vector<Ctags::Location> Ctags::get_locations(const boost::filesystem::path &path, const std::string &name, const std::string &type) { - auto result = get_result(path); - result.second->seekg(0, std::ios::end); - if (result.second->tellg() == 0) - return std::vector<Location>(); - result.second->seekg(0, std::ios::beg); - - //insert name into type - size_t c = 0; - size_t bracket_count = 0; - for (; c < type.size(); ++c) { - if (type[c] == '<') - ++bracket_count; - else if (type[c] == '>') - --bracket_count; - else if (bracket_count == 0 && type[c] == '(') - break; - } - auto full_type = type; - full_type.insert(c, name); - - auto parts = get_type_parts(full_type); - - std::string line; - long best_score = LONG_MIN; - std::vector<Location> best_locations; - while (std::getline(*result.second, line)) { - if (line.size() > 2048) - continue; - auto location = Ctags::get_location(line, false); - if (!location.scope.empty()) { - if (location.scope + "::" + location.symbol != name) - continue; - } else if (location.symbol != name) - continue; - - location.file_path = result.first / location.file_path; - - auto source_parts = get_type_parts(location.source); - - //Find match score - long score = 0; - size_t source_index = 0; - for (auto &part: parts) { - bool found = false; - for (auto c = source_index; c < source_parts.size(); ++c) { - if (part == source_parts[c]) { - source_index = c + 1; - ++score; - found = true; - break; - } - } - if (!found) - --score; + auto result = get_result(path); + result.second->seekg(0, std::ios::end); + if (result.second->tellg() == 0) + return std::vector<Location>(); + result.second->seekg(0, std::ios::beg); + + //insert name into type + size_t c = 0; + size_t bracket_count = 0; + for (; c < type.size(); ++c) { + if (type[c] == '<') + ++bracket_count; + else if (type[c] == '>') + --bracket_count; + else if (bracket_count == 0 && type[c] == '(') + break; + } + auto full_type = type; + full_type.insert(c, name); + + auto parts = get_type_parts(full_type); + + std::string line; + long best_score = LONG_MIN; + std::vector<Location> best_locations; + while (std::getline(*result.second, line)) { + if (line.size() > 2048) + continue; + auto location = Ctags::get_location(line, false); + if (!location.scope.empty()) { + if (location.scope + "::" + location.symbol != name) + continue; + } else if (location.symbol != name) + continue; + + location.file_path = result.first / location.file_path; + + auto source_parts = get_type_parts(location.source); + + //Find match score + long score = 0; + size_t source_index = 0; + for (auto &part: parts) { + bool found = false; + for (auto c = source_index; c < source_parts.size(); ++c) { + if (part == source_parts[c]) { + source_index = c + 1; + ++score; + found = true; + break; } - size_t index = 0; - for (auto &source_part: source_parts) { - bool found = false; - for (auto c = index; c < parts.size(); ++c) { - if (source_part == parts[c]) { - index = c + 1; - ++score; - found = true; - break; - } - } - if (!found) - --score; + } + if (!found) + --score; + } + size_t index = 0; + for (auto &source_part: source_parts) { + bool found = false; + for (auto c = index; c < parts.size(); ++c) { + if (source_part == parts[c]) { + index = c + 1; + ++score; + found = true; + break; } - - if (score > best_score) { - best_score = score; - best_locations.clear(); - best_locations.emplace_back(location); - } else if (score == best_score) - best_locations.emplace_back(location); + } + if (!found) + --score; } - return best_locations; + if (score > best_score) { + best_score = score; + best_locations.clear(); + best_locations.emplace_back(location); + } else if (score == best_score) + best_locations.emplace_back(location); + } + + return best_locations; } diff --git a/src/ctags.h b/src/ctags.h index 0d0aefad..8ff5452b 100644 --- a/src/ctags.h +++ b/src/ctags.h @@ -7,26 +7,26 @@ class Ctags { public: - class Location { - public: - boost::filesystem::path file_path; - unsigned long line; - unsigned long index; - std::string symbol; - std::string scope; - std::string source; + class Location { + public: + boost::filesystem::path file_path; + unsigned long line; + unsigned long index; + std::string symbol; + std::string scope; + std::string source; - operator bool() const { return !file_path.empty(); } - }; + operator bool() const { return !file_path.empty(); } + }; - static std::pair<boost::filesystem::path, std::unique_ptr<std::stringstream> > - get_result(const boost::filesystem::path &path); + static std::pair<boost::filesystem::path, std::unique_ptr<std::stringstream> > + get_result(const boost::filesystem::path &path); - static Location get_location(const std::string &line, bool markup); + static Location get_location(const std::string &line, bool markup); - static std::vector<Location> - get_locations(const boost::filesystem::path &path, const std::string &name, const std::string &type); + static std::vector<Location> + get_locations(const boost::filesystem::path &path, const std::string &name, const std::string &type); private: - static std::vector<std::string> get_type_parts(const std::string type); + static std::vector<std::string> get_type_parts(const std::string type); }; diff --git a/src/debug_lldb.cc b/src/debug_lldb.cc index 7bfa7172..a64fde3d 100644 --- a/src/debug_lldb.cc +++ b/src/debug_lldb.cc @@ -15,529 +15,529 @@ extern char **environ; void log(const char *msg, void *) { - std::cout << "debugger log: " << msg << std::endl; + std::cout << "debugger log: " << msg << std::endl; } Debug::LLDB::LLDB() : state(lldb::StateType::eStateInvalid), buffer_size(131072) { - if (!getenv("LLDB_DEBUGSERVER_PATH")) { + if (!getenv("LLDB_DEBUGSERVER_PATH")) { #ifdef __APPLE__ - std::string debug_server_path("/usr/local/opt/llvm/bin/debugserver"); - if(boost::filesystem::exists(debug_server_path)) - setenv("LLDB_DEBUGSERVER_PATH", debug_server_path.c_str(), 0); + std::string debug_server_path("/usr/local/opt/llvm/bin/debugserver"); + if(boost::filesystem::exists(debug_server_path)) + setenv("LLDB_DEBUGSERVER_PATH", debug_server_path.c_str(), 0); #else - auto debug_server_path = filesystem::get_executable("lldb-server").string(); - if (debug_server_path != "lldb-server") - setenv("LLDB_DEBUGSERVER_PATH", debug_server_path.c_str(), 0); + auto debug_server_path = filesystem::get_executable("lldb-server").string(); + if (debug_server_path != "lldb-server") + setenv("LLDB_DEBUGSERVER_PATH", debug_server_path.c_str(), 0); #endif - } + } } std::tuple<std::vector<std::string>, std::string, std::vector<std::string> > Debug::LLDB::parse_run_arguments(const std::string &command) { - std::vector<std::string> environment; - std::string executable; - std::vector<std::string> arguments; - - size_t start_pos = std::string::npos; - bool quote = false; - bool double_quote = false; - size_t backslash_count = 0; - for (size_t c = 0; c <= command.size(); c++) { - if (c == command.size() || (!quote && !double_quote && backslash_count % 2 == 0 && command[c] == ' ')) { - if (c > 0 && start_pos != std::string::npos) { - auto argument = command.substr(start_pos, c - start_pos); - if (executable.empty()) { - //Check for environment variable - bool env_arg = false; - for (size_t c = 0; c < argument.size(); ++c) { - if ((argument[c] >= 'a' && argument[c] <= 'z') || (argument[c] >= 'A' && argument[c] <= 'Z') || - (argument[c] >= '0' && argument[c] <= '9') || argument[c] == '_') - continue; - else if (argument[c] == '=' && c + 1 < argument.size()) { - environment.emplace_back( - argument.substr(0, c + 1) + filesystem::unescape_argument(argument.substr(c + 1))); - env_arg = true; - break; - } else - break; - } - - if (!env_arg) { - executable = filesystem::unescape_argument(argument); + std::vector<std::string> environment; + std::string executable; + std::vector<std::string> arguments; + + size_t start_pos = std::string::npos; + bool quote = false; + bool double_quote = false; + size_t backslash_count = 0; + for (size_t c = 0; c <= command.size(); c++) { + if (c == command.size() || (!quote && !double_quote && backslash_count % 2 == 0 && command[c] == ' ')) { + if (c > 0 && start_pos != std::string::npos) { + auto argument = command.substr(start_pos, c - start_pos); + if (executable.empty()) { + //Check for environment variable + bool env_arg = false; + for (size_t c = 0; c < argument.size(); ++c) { + if ((argument[c] >= 'a' && argument[c] <= 'z') || (argument[c] >= 'A' && argument[c] <= 'Z') || + (argument[c] >= '0' && argument[c] <= '9') || argument[c] == '_') + continue; + else if (argument[c] == '=' && c + 1 < argument.size()) { + environment.emplace_back( + argument.substr(0, c + 1) + filesystem::unescape_argument(argument.substr(c + 1))); + env_arg = true; + break; + } else + break; + } + + if (!env_arg) { + executable = filesystem::unescape_argument(argument); #ifdef _WIN32 - if (remote_host.empty()) - executable += ".exe"; + if (remote_host.empty()) + executable += ".exe"; #endif - } - } else - arguments.emplace_back(filesystem::unescape_argument(argument)); - start_pos = std::string::npos; - } - } else if (command[c] == '\'' && backslash_count % 2 == 0 && !double_quote) - quote = !quote; - else if (command[c] == '"' && backslash_count % 2 == 0 && !quote) - double_quote = !double_quote; - else if (command[c] == '\\' && !quote && !double_quote) - ++backslash_count; - else - backslash_count = 0; - if (c < command.size() && start_pos == std::string::npos && command[c] != ' ') - start_pos = c; - } - - return std::make_tuple(environment, executable, arguments); + } + } else + arguments.emplace_back(filesystem::unescape_argument(argument)); + start_pos = std::string::npos; + } + } else if (command[c] == '\'' && backslash_count % 2 == 0 && !double_quote) + quote = !quote; + else if (command[c] == '"' && backslash_count % 2 == 0 && !quote) + double_quote = !double_quote; + else if (command[c] == '\\' && !quote && !double_quote) + ++backslash_count; + else + backslash_count = 0; + if (c < command.size() && start_pos == std::string::npos && command[c] != ' ') + start_pos = c; + } + + return std::make_tuple(environment, executable, arguments); } void Debug::LLDB::start(const std::string &command, const boost::filesystem::path &path, const std::vector<std::pair<boost::filesystem::path, int> > &breakpoints, const std::vector<std::string> &startup_commands, const std::string &remote_host) { - if (!debugger) { - lldb::SBDebugger::Initialize(); - debugger = std::make_unique<lldb::SBDebugger>(lldb::SBDebugger::Create(true, log, nullptr)); - listener = std::make_unique<lldb::SBListener>("juCi++ lldb listener"); + if (!debugger) { + lldb::SBDebugger::Initialize(); + debugger = std::make_unique<lldb::SBDebugger>(lldb::SBDebugger::Create(true, log, nullptr)); + listener = std::make_unique<lldb::SBListener>("juCi++ lldb listener"); + } + + //Create executable string and argument array + auto parsed_run_arguments = parse_run_arguments(command); + auto &environment_from_arguments = std::get<0>(parsed_run_arguments); + auto &executable = std::get<1>(parsed_run_arguments); + auto &arguments = std::get<2>(parsed_run_arguments); + + std::vector<const char *> argv; + for (size_t c = 0; c < arguments.size(); c++) + argv.emplace_back(arguments[c].c_str()); + argv.emplace_back(nullptr); + + auto target = debugger->CreateTarget(executable.c_str()); + if (!target.IsValid()) { + Terminal::get().async_print("Error (debug): Could not create debug target to: " + executable + '\n', true); + for (auto &handler: on_exit) + handler(-1); + return; + } + + //Set 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); + for (auto &handler: on_exit) + handler(-1); + return; } + } - //Create executable string and argument array - auto parsed_run_arguments = parse_run_arguments(command); - auto &environment_from_arguments = std::get<0>(parsed_run_arguments); - auto &executable = std::get<1>(parsed_run_arguments); - auto &arguments = std::get<2>(parsed_run_arguments); - - std::vector<const char *> argv; - for (size_t c = 0; c < arguments.size(); c++) - argv.emplace_back(arguments[c].c_str()); - argv.emplace_back(nullptr); - - auto target = debugger->CreateTarget(executable.c_str()); - if (!target.IsValid()) { - Terminal::get().async_print("Error (debug): Could not create debug target to: " + executable + '\n', true); - for (auto &handler: on_exit) - handler(-1); - return; + lldb::SBError error; + if (!remote_host.empty()) { + auto connect_string = "connect://" + remote_host; + process = std::make_unique<lldb::SBProcess>( + target.ConnectRemote(*listener, connect_string.c_str(), "gdb-remote", error)); + if (error.Fail()) { + Terminal::get().async_print(std::string("Error (debug): ") + error.GetCString() + '\n', true); + for (auto &handler: on_exit) + handler(-1); + return; } - - //Set 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); - for (auto &handler: on_exit) - handler(-1); - return; + lldb::SBEvent event; + while (true) { + if (listener->GetNextEvent(event)) { + if ((event.GetType() & lldb::SBProcess::eBroadcastBitStateChanged) > 0) { + auto state = process->GetStateFromEvent(event); + this->state = state; + if (state == lldb::StateType::eStateConnected) + break; } + } } - lldb::SBError error; - if (!remote_host.empty()) { - auto connect_string = "connect://" + remote_host; - process = std::make_unique<lldb::SBProcess>( - target.ConnectRemote(*listener, connect_string.c_str(), "gdb-remote", error)); - if (error.Fail()) { - Terminal::get().async_print(std::string("Error (debug): ") + error.GetCString() + '\n', true); - for (auto &handler: on_exit) - handler(-1); - return; - } - lldb::SBEvent event; - while (true) { - if (listener->GetNextEvent(event)) { - if ((event.GetType() & lldb::SBProcess::eBroadcastBitStateChanged) > 0) { - auto state = process->GetStateFromEvent(event); - this->state = state; - if (state == lldb::StateType::eStateConnected) - break; - } + // Create environment array + std::vector<const char *> environment; + for (auto &e: environment_from_arguments) + environment.emplace_back(e.c_str()); + environment.emplace_back(nullptr); + + process->RemoteLaunch(argv.data(), environment.data(), nullptr, nullptr, nullptr, nullptr, + lldb::eLaunchFlagNone, false, error); + if (!error.Fail()) + process->Continue(); + } else { + // Create environment array + std::vector<const char *> environment; + for (auto &e: environment_from_arguments) + environment.emplace_back(e.c_str()); + size_t environ_size = 0; + while (environ[environ_size] != nullptr) + ++environ_size; + for (size_t c = 0; c < environ_size; ++c) + environment.emplace_back(environ[c]); + environment.emplace_back(nullptr); + + process = std::make_unique<lldb::SBProcess>( + target.Launch(*listener, argv.data(), environment.data(), nullptr, nullptr, nullptr, + path.string().c_str(), lldb::eLaunchFlagNone, false, error)); + } + if (error.Fail()) { + Terminal::get().async_print(std::string("Error (debug): ") + error.GetCString() + '\n', true); + for (auto &handler: on_exit) + handler(-1); + return; + } + if (debug_thread.joinable()) + debug_thread.join(); + for (auto &handler: on_start) + handler(*process); + + for (auto &command: startup_commands) { + lldb::SBCommandReturnObject command_return_object; + debugger->GetCommandInterpreter().HandleCommand(command.c_str(), command_return_object, false); + } + + debug_thread = std::thread([this]() { + lldb::SBEvent event; + while (true) { + std::unique_lock<std::mutex> lock(mutex); + if (listener->GetNextEvent(event)) { + if ((event.GetType() & lldb::SBProcess::eBroadcastBitStateChanged) > 0) { + auto state = process->GetStateFromEvent(event); + this->state = state; + + if (state == lldb::StateType::eStateStopped) { + for (uint32_t c = 0; c < process->GetNumThreads(); c++) { + auto thread = process->GetThreadAtIndex(c); + if (thread.GetStopReason() >= 2) { + process->SetSelectedThreadByIndexID(thread.GetIndexID()); + break; + } } - } + } - // Create environment array - std::vector<const char *> environment; - for (auto &e: environment_from_arguments) - environment.emplace_back(e.c_str()); - environment.emplace_back(nullptr); - - process->RemoteLaunch(argv.data(), environment.data(), nullptr, nullptr, nullptr, nullptr, - lldb::eLaunchFlagNone, false, error); - if (!error.Fail()) - process->Continue(); - } else { - // Create environment array - std::vector<const char *> environment; - for (auto &e: environment_from_arguments) - environment.emplace_back(e.c_str()); - size_t environ_size = 0; - while (environ[environ_size] != nullptr) - ++environ_size; - for (size_t c = 0; c < environ_size; ++c) - environment.emplace_back(environ[c]); - environment.emplace_back(nullptr); - - process = std::make_unique<lldb::SBProcess>( - target.Launch(*listener, argv.data(), environment.data(), nullptr, nullptr, nullptr, - path.string().c_str(), lldb::eLaunchFlagNone, false, error)); - } - if (error.Fail()) { - Terminal::get().async_print(std::string("Error (debug): ") + error.GetCString() + '\n', true); - for (auto &handler: on_exit) - handler(-1); - return; - } - if (debug_thread.joinable()) - debug_thread.join(); - for (auto &handler: on_start) - handler(*process); - - for (auto &command: startup_commands) { - lldb::SBCommandReturnObject command_return_object; - debugger->GetCommandInterpreter().HandleCommand(command.c_str(), command_return_object, false); - } + lock.unlock(); + for (auto &handler: on_event) + handler(event); + lock.lock(); - debug_thread = std::thread([this]() { - lldb::SBEvent event; - while (true) { - std::unique_lock<std::mutex> lock(mutex); - if (listener->GetNextEvent(event)) { - if ((event.GetType() & lldb::SBProcess::eBroadcastBitStateChanged) > 0) { - auto state = process->GetStateFromEvent(event); - this->state = state; - - if (state == lldb::StateType::eStateStopped) { - for (uint32_t c = 0; c < process->GetNumThreads(); c++) { - auto thread = process->GetThreadAtIndex(c); - if (thread.GetStopReason() >= 2) { - process->SetSelectedThreadByIndexID(thread.GetIndexID()); - break; - } - } - } - - lock.unlock(); - for (auto &handler: on_event) - handler(event); - lock.lock(); - - if (state == lldb::StateType::eStateExited || state == lldb::StateType::eStateCrashed) { - auto exit_status = state == lldb::StateType::eStateCrashed ? -1 : process->GetExitStatus(); - lock.unlock(); - for (auto &handler: on_exit) - handler(exit_status); - lock.lock(); - process.reset(); - this->state = lldb::StateType::eStateInvalid; - return; - } - } - if ((event.GetType() & lldb::SBProcess::eBroadcastBitSTDOUT) > 0) { - char buffer[buffer_size]; - size_t n; - while ((n = process->GetSTDOUT(buffer, buffer_size)) != 0) - Terminal::get().async_print(std::string(buffer, n)); - } - //TODO: for some reason stderr is redirected to stdout - if ((event.GetType() & lldb::SBProcess::eBroadcastBitSTDERR) > 0) { - char buffer[buffer_size]; - size_t n; - while ((n = process->GetSTDERR(buffer, buffer_size)) != 0) - Terminal::get().async_print(std::string(buffer, n), true); - } - } + if (state == lldb::StateType::eStateExited || state == lldb::StateType::eStateCrashed) { + auto exit_status = state == lldb::StateType::eStateCrashed ? -1 : process->GetExitStatus(); lock.unlock(); - std::this_thread::sleep_for(std::chrono::milliseconds(200)); + for (auto &handler: on_exit) + handler(exit_status); + lock.lock(); + process.reset(); + this->state = lldb::StateType::eStateInvalid; + return; + } + } + if ((event.GetType() & lldb::SBProcess::eBroadcastBitSTDOUT) > 0) { + char buffer[buffer_size]; + size_t n; + while ((n = process->GetSTDOUT(buffer, buffer_size)) != 0) + Terminal::get().async_print(std::string(buffer, n)); + } + //TODO: for some reason stderr is redirected to stdout + if ((event.GetType() & lldb::SBProcess::eBroadcastBitSTDERR) > 0) { + char buffer[buffer_size]; + size_t n; + while ((n = process->GetSTDERR(buffer, buffer_size)) != 0) + Terminal::get().async_print(std::string(buffer, n), true); } - }); + } + lock.unlock(); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + }); } void Debug::LLDB::continue_debug() { - std::unique_lock<std::mutex> lock(mutex); - if (state == lldb::StateType::eStateStopped) - process->Continue(); + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::StateType::eStateStopped) + process->Continue(); } void Debug::LLDB::stop() { - std::unique_lock<std::mutex> lock(mutex); - if (state == lldb::StateType::eStateRunning) { - auto error = process->Stop(); - if (error.Fail()) - Terminal::get().async_print(std::string("Error (debug): ") + error.GetCString() + '\n', true); - } + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::StateType::eStateRunning) { + auto error = process->Stop(); + if (error.Fail()) + Terminal::get().async_print(std::string("Error (debug): ") + error.GetCString() + '\n', true); + } } void Debug::LLDB::kill() { - std::unique_lock<std::mutex> lock(mutex); - if (process) { - auto error = process->Kill(); - if (error.Fail()) - Terminal::get().async_print(std::string("Error (debug): ") + error.GetCString() + '\n', true); - } + std::unique_lock<std::mutex> lock(mutex); + if (process) { + auto error = process->Kill(); + if (error.Fail()) + Terminal::get().async_print(std::string("Error (debug): ") + error.GetCString() + '\n', true); + } } void Debug::LLDB::step_over() { - std::unique_lock<std::mutex> lock(mutex); - if (state == lldb::StateType::eStateStopped) { - process->GetSelectedThread().StepOver(); - } + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::StateType::eStateStopped) { + process->GetSelectedThread().StepOver(); + } } void Debug::LLDB::step_into() { - std::unique_lock<std::mutex> lock(mutex); - if (state == lldb::StateType::eStateStopped) { - process->GetSelectedThread().StepInto(); - } + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::StateType::eStateStopped) { + process->GetSelectedThread().StepInto(); + } } void Debug::LLDB::step_out() { - std::unique_lock<std::mutex> lock(mutex); - if (state == lldb::StateType::eStateStopped) { - process->GetSelectedThread().StepOut(); - } + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::StateType::eStateStopped) { + process->GetSelectedThread().StepOut(); + } } std::pair<std::string, std::string> Debug::LLDB::run_command(const std::string &command) { - std::pair<std::string, std::string> command_return; - std::unique_lock<std::mutex> lock(mutex); - if (state == lldb::StateType::eStateStopped || state == lldb::StateType::eStateRunning) { - lldb::SBCommandReturnObject command_return_object; - debugger->GetCommandInterpreter().HandleCommand(command.c_str(), command_return_object, true); - auto output = command_return_object.GetOutput(); - if (output) - command_return.first = output; - auto error = command_return_object.GetError(); - if (error) - command_return.second = error; - } - return command_return; + std::pair<std::string, std::string> command_return; + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::StateType::eStateStopped || state == lldb::StateType::eStateRunning) { + lldb::SBCommandReturnObject command_return_object; + debugger->GetCommandInterpreter().HandleCommand(command.c_str(), command_return_object, true); + auto output = command_return_object.GetOutput(); + if (output) + command_return.first = output; + auto error = command_return_object.GetError(); + if (error) + command_return.second = error; + } + return command_return; } std::vector<Debug::LLDB::Frame> Debug::LLDB::get_backtrace() { - std::vector<Frame> backtrace; - std::unique_lock<std::mutex> lock(mutex); - if (state == lldb::StateType::eStateStopped) { - auto thread = process->GetSelectedThread(); - for (uint32_t c_f = 0; c_f < thread.GetNumFrames(); c_f++) { - Frame backtrace_frame; - auto frame = thread.GetFrameAtIndex(c_f); - - backtrace_frame.index = c_f; - - if (frame.GetFunctionName() != nullptr) - backtrace_frame.function_name = frame.GetFunctionName(); - - auto module_filename = frame.GetModule().GetFileSpec().GetFilename(); - if (module_filename != nullptr) { - backtrace_frame.module_filename = module_filename; - } - - auto line_entry = frame.GetLineEntry(); - if (line_entry.IsValid()) { - lldb::SBStream stream; - line_entry.GetFileSpec().GetDescription(stream); - auto column = line_entry.GetColumn(); - if (column == 0) - column = 1; - backtrace_frame.file_path = filesystem::get_normal_path(stream.GetData()); - backtrace_frame.line_nr = line_entry.GetLine(); - backtrace_frame.line_index = column; - } - backtrace.emplace_back(backtrace_frame); - } + std::vector<Frame> backtrace; + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::StateType::eStateStopped) { + auto thread = process->GetSelectedThread(); + for (uint32_t c_f = 0; c_f < thread.GetNumFrames(); c_f++) { + Frame backtrace_frame; + auto frame = thread.GetFrameAtIndex(c_f); + + backtrace_frame.index = c_f; + + if (frame.GetFunctionName() != nullptr) + backtrace_frame.function_name = frame.GetFunctionName(); + + auto module_filename = frame.GetModule().GetFileSpec().GetFilename(); + if (module_filename != nullptr) { + backtrace_frame.module_filename = module_filename; + } + + auto line_entry = frame.GetLineEntry(); + if (line_entry.IsValid()) { + lldb::SBStream stream; + line_entry.GetFileSpec().GetDescription(stream); + auto column = line_entry.GetColumn(); + if (column == 0) + column = 1; + backtrace_frame.file_path = filesystem::get_normal_path(stream.GetData()); + backtrace_frame.line_nr = line_entry.GetLine(); + backtrace_frame.line_index = column; + } + backtrace.emplace_back(backtrace_frame); } - return backtrace; + } + return backtrace; } std::vector<Debug::LLDB::Variable> Debug::LLDB::get_variables() { - std::vector<Debug::LLDB::Variable> variables; - std::unique_lock<std::mutex> lock(mutex); - if (state == lldb::StateType::eStateStopped) { - for (uint32_t c_t = 0; c_t < process->GetNumThreads(); c_t++) { - auto thread = process->GetThreadAtIndex(c_t); - for (uint32_t c_f = 0; c_f < thread.GetNumFrames(); c_f++) { - auto frame = thread.GetFrameAtIndex(c_f); - auto values = frame.GetVariables(true, true, true, false); - for (uint32_t value_index = 0; value_index < values.GetSize(); value_index++) { - lldb::SBStream stream; - auto value = values.GetValueAtIndex(value_index); - - Debug::LLDB::Variable variable; - variable.thread_index_id = thread.GetIndexID(); - variable.frame_index = c_f; - if (value.GetName() != nullptr) - variable.name = value.GetName(); - value.GetDescription(stream); - variable.value = stream.GetData(); - - auto declaration = value.GetDeclaration(); - if (declaration.IsValid()) { - variable.declaration_found = true; - variable.line_nr = declaration.GetLine(); - variable.line_index = declaration.GetColumn(); - if (variable.line_index == 0) - variable.line_index = 1; - - auto file_spec = declaration.GetFileSpec(); - variable.file_path = filesystem::get_normal_path(file_spec.GetDirectory()); - variable.file_path /= file_spec.GetFilename(); - } else { - variable.declaration_found = false; - auto line_entry = frame.GetLineEntry(); - if (line_entry.IsValid()) { - variable.line_nr = line_entry.GetLine(); - variable.line_index = line_entry.GetColumn(); - if (variable.line_index == 0) - variable.line_index = 1; - - auto file_spec = line_entry.GetFileSpec(); - variable.file_path = filesystem::get_normal_path(file_spec.GetDirectory()); - variable.file_path /= file_spec.GetFilename(); - } - } - variables.emplace_back(variable); - } + std::vector<Debug::LLDB::Variable> variables; + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::StateType::eStateStopped) { + for (uint32_t c_t = 0; c_t < process->GetNumThreads(); c_t++) { + auto thread = process->GetThreadAtIndex(c_t); + for (uint32_t c_f = 0; c_f < thread.GetNumFrames(); c_f++) { + auto frame = thread.GetFrameAtIndex(c_f); + auto values = frame.GetVariables(true, true, true, false); + for (uint32_t value_index = 0; value_index < values.GetSize(); value_index++) { + lldb::SBStream stream; + auto value = values.GetValueAtIndex(value_index); + + Debug::LLDB::Variable variable; + variable.thread_index_id = thread.GetIndexID(); + variable.frame_index = c_f; + if (value.GetName() != nullptr) + variable.name = value.GetName(); + value.GetDescription(stream); + variable.value = stream.GetData(); + + auto declaration = value.GetDeclaration(); + if (declaration.IsValid()) { + variable.declaration_found = true; + variable.line_nr = declaration.GetLine(); + variable.line_index = declaration.GetColumn(); + if (variable.line_index == 0) + variable.line_index = 1; + + auto file_spec = declaration.GetFileSpec(); + variable.file_path = filesystem::get_normal_path(file_spec.GetDirectory()); + variable.file_path /= file_spec.GetFilename(); + } else { + variable.declaration_found = false; + auto line_entry = frame.GetLineEntry(); + if (line_entry.IsValid()) { + variable.line_nr = line_entry.GetLine(); + variable.line_index = line_entry.GetColumn(); + if (variable.line_index == 0) + variable.line_index = 1; + + auto file_spec = line_entry.GetFileSpec(); + variable.file_path = filesystem::get_normal_path(file_spec.GetDirectory()); + variable.file_path /= file_spec.GetFilename(); } + } + variables.emplace_back(variable); } + } } - return variables; + } + return variables; } void Debug::LLDB::select_frame(uint32_t frame_index, uint32_t thread_index_id) { - std::unique_lock<std::mutex> lock(mutex); - if (state == lldb::StateType::eStateStopped) { - if (thread_index_id != 0) - process->SetSelectedThreadByIndexID(thread_index_id); - process->GetSelectedThread().SetSelectedFrame(frame_index);; - } + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::StateType::eStateStopped) { + if (thread_index_id != 0) + process->SetSelectedThreadByIndexID(thread_index_id); + process->GetSelectedThread().SetSelectedFrame(frame_index);; + } } void Debug::LLDB::cancel() { - kill(); - if (debug_thread.joinable()) - debug_thread.join(); + kill(); + if (debug_thread.joinable()) + debug_thread.join(); } std::string Debug::LLDB::get_value(const std::string &variable, const boost::filesystem::path &file_path, unsigned int line_nr, unsigned int line_index) { - std::string variable_value; - std::unique_lock<std::mutex> lock(mutex); - if (state == lldb::StateType::eStateStopped) { - auto frame = process->GetSelectedThread().GetSelectedFrame(); - - auto values = frame.GetVariables(true, true, true, false); - //First try to find variable based on name, file and line number - if (!file_path.empty()) { - for (uint32_t value_index = 0; value_index < values.GetSize(); value_index++) { - lldb::SBStream stream; - auto value = values.GetValueAtIndex(value_index); - - if (value.GetName() != nullptr && value.GetName() == variable) { - auto declaration = value.GetDeclaration(); - if (declaration.IsValid()) { - if (declaration.GetLine() == line_nr && - (declaration.GetColumn() == 0 || declaration.GetColumn() == line_index)) { - auto file_spec = declaration.GetFileSpec(); - auto value_decl_path = filesystem::get_normal_path(file_spec.GetDirectory()); - value_decl_path /= file_spec.GetFilename(); - if (value_decl_path == file_path) { - value.GetDescription(stream); - variable_value = stream.GetData(); - break; - } - } - } - } - } - } - if (variable_value.empty()) { - //In case a variable is missing file and line number, only do check on name - auto value = frame.GetValueForVariablePath(variable.c_str()); - if (value.IsValid()) { - lldb::SBStream stream; + std::string variable_value; + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::StateType::eStateStopped) { + auto frame = process->GetSelectedThread().GetSelectedFrame(); + + auto values = frame.GetVariables(true, true, true, false); + //First try to find variable based on name, file and line number + if (!file_path.empty()) { + for (uint32_t value_index = 0; value_index < values.GetSize(); value_index++) { + lldb::SBStream stream; + auto value = values.GetValueAtIndex(value_index); + + if (value.GetName() != nullptr && value.GetName() == variable) { + auto declaration = value.GetDeclaration(); + if (declaration.IsValid()) { + if (declaration.GetLine() == line_nr && + (declaration.GetColumn() == 0 || declaration.GetColumn() == line_index)) { + auto file_spec = declaration.GetFileSpec(); + auto value_decl_path = filesystem::get_normal_path(file_spec.GetDirectory()); + value_decl_path /= file_spec.GetFilename(); + if (value_decl_path == file_path) { value.GetDescription(stream); variable_value = stream.GetData(); + break; + } } + } } + } } - return variable_value; + if (variable_value.empty()) { + //In case a variable is missing file and line number, only do check on name + auto value = frame.GetValueForVariablePath(variable.c_str()); + if (value.IsValid()) { + lldb::SBStream stream; + value.GetDescription(stream); + variable_value = stream.GetData(); + } + } + } + return variable_value; } std::string Debug::LLDB::get_return_value(const boost::filesystem::path &file_path, unsigned int line_nr, unsigned int line_index) { - std::string return_value; - std::unique_lock<std::mutex> lock(mutex); - if (state == lldb::StateType::eStateStopped) { - auto thread = process->GetSelectedThread(); - auto thread_return_value = thread.GetStopReturnValue(); - if (thread_return_value.IsValid()) { - auto line_entry = thread.GetSelectedFrame().GetLineEntry(); - if (line_entry.IsValid()) { - lldb::SBStream stream; - line_entry.GetFileSpec().GetDescription(stream); - if (filesystem::get_normal_path(stream.GetData()) == file_path && line_entry.GetLine() == line_nr && - (line_entry.GetColumn() == 0 || line_entry.GetColumn() == line_index)) { - lldb::SBStream stream; - thread_return_value.GetDescription(stream); - return_value = stream.GetData(); - } - } + std::string return_value; + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::StateType::eStateStopped) { + auto thread = process->GetSelectedThread(); + auto thread_return_value = thread.GetStopReturnValue(); + if (thread_return_value.IsValid()) { + auto line_entry = thread.GetSelectedFrame().GetLineEntry(); + if (line_entry.IsValid()) { + lldb::SBStream stream; + line_entry.GetFileSpec().GetDescription(stream); + if (filesystem::get_normal_path(stream.GetData()) == file_path && line_entry.GetLine() == line_nr && + (line_entry.GetColumn() == 0 || line_entry.GetColumn() == line_index)) { + lldb::SBStream stream; + thread_return_value.GetDescription(stream); + return_value = stream.GetData(); } + } } - return return_value; + } + return return_value; } bool Debug::LLDB::is_invalid() { - std::unique_lock<std::mutex> lock(mutex); - return state == lldb::StateType::eStateInvalid; + std::unique_lock<std::mutex> lock(mutex); + return state == lldb::StateType::eStateInvalid; } bool Debug::LLDB::is_stopped() { - std::unique_lock<std::mutex> lock(mutex); - return state == lldb::StateType::eStateStopped; + std::unique_lock<std::mutex> lock(mutex); + return state == lldb::StateType::eStateStopped; } bool Debug::LLDB::is_running() { - std::unique_lock<std::mutex> lock(mutex); - return state == lldb::StateType::eStateRunning; + std::unique_lock<std::mutex> lock(mutex); + return state == lldb::StateType::eStateRunning; } void Debug::LLDB::add_breakpoint(const boost::filesystem::path &file_path, int line_nr) { - std::unique_lock<std::mutex> lock(mutex); - if (state == lldb::eStateStopped || state == lldb::eStateRunning) { - if (!(process->GetTarget().BreakpointCreateByLocation(file_path.string().c_str(), line_nr)).IsValid()) - Terminal::get().async_print("Error (debug): Could not create breakpoint at: " + file_path.string() + ":" + - std::to_string(line_nr) + '\n', true); - } + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::eStateStopped || state == lldb::eStateRunning) { + if (!(process->GetTarget().BreakpointCreateByLocation(file_path.string().c_str(), line_nr)).IsValid()) + Terminal::get().async_print("Error (debug): Could not create breakpoint at: " + file_path.string() + ":" + + std::to_string(line_nr) + '\n', true); + } } void Debug::LLDB::remove_breakpoint(const boost::filesystem::path &file_path, int line_nr, int line_count) { - std::unique_lock<std::mutex> lock(mutex); - if (state == lldb::eStateStopped || state == lldb::eStateRunning) { - auto target = process->GetTarget(); - for (int line_nr_try = line_nr; line_nr_try < line_count; line_nr_try++) { - for (uint32_t b_index = 0; b_index < target.GetNumBreakpoints(); b_index++) { - auto breakpoint = target.GetBreakpointAtIndex(b_index); - for (uint32_t l_index = 0; l_index < breakpoint.GetNumLocations(); l_index++) { - auto line_entry = breakpoint.GetLocationAtIndex(l_index).GetAddress().GetLineEntry(); - if (line_entry.GetLine() == static_cast<uint32_t>(line_nr_try)) { - auto file_spec = line_entry.GetFileSpec(); - auto breakpoint_path = filesystem::get_normal_path(file_spec.GetDirectory()); - breakpoint_path /= file_spec.GetFilename(); - if (breakpoint_path == file_path) { - if (!target.BreakpointDelete(breakpoint.GetID())) - Terminal::get().async_print( - "Error (debug): Could not delete breakpoint at: " + file_path.string() + ":" + - std::to_string(line_nr) + '\n', true); - return; - } - } - } + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::eStateStopped || state == lldb::eStateRunning) { + auto target = process->GetTarget(); + for (int line_nr_try = line_nr; line_nr_try < line_count; line_nr_try++) { + for (uint32_t b_index = 0; b_index < target.GetNumBreakpoints(); b_index++) { + auto breakpoint = target.GetBreakpointAtIndex(b_index); + for (uint32_t l_index = 0; l_index < breakpoint.GetNumLocations(); l_index++) { + auto line_entry = breakpoint.GetLocationAtIndex(l_index).GetAddress().GetLineEntry(); + if (line_entry.GetLine() == static_cast<uint32_t>(line_nr_try)) { + auto file_spec = line_entry.GetFileSpec(); + auto breakpoint_path = filesystem::get_normal_path(file_spec.GetDirectory()); + breakpoint_path /= file_spec.GetFilename(); + if (breakpoint_path == file_path) { + if (!target.BreakpointDelete(breakpoint.GetID())) + Terminal::get().async_print( + "Error (debug): Could not delete breakpoint at: " + file_path.string() + ":" + + std::to_string(line_nr) + '\n', true); + return; } + } } + } } + } } void Debug::LLDB::write(const std::string &buffer) { - std::unique_lock<std::mutex> lock(mutex); - if (state == lldb::StateType::eStateRunning) { - process->PutSTDIN(buffer.c_str(), buffer.size()); - } + std::unique_lock<std::mutex> lock(mutex); + if (state == lldb::StateType::eStateRunning) { + process->PutSTDIN(buffer.c_str(), buffer.size()); + } } diff --git a/src/debug_lldb.h b/src/debug_lldb.h index 1b948a7b..b7215d1c 100644 --- a/src/debug_lldb.h +++ b/src/debug_lldb.h @@ -8,102 +8,102 @@ #include <tuple> namespace Debug { - class LLDB { + class LLDB { + public: + class Frame { public: - class Frame { - public: - uint32_t index; - std::string module_filename; - boost::filesystem::path 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; - bool declaration_found; - boost::filesystem::path file_path; - int line_nr; - int line_index; - }; - - private: - LLDB(); + uint32_t index; + std::string module_filename; + boost::filesystem::path file_path; + std::string function_name; + int line_nr; + int line_index; + }; + class Variable { public: - static LLDB &get() { - static LLDB singleton; - return singleton; - } + uint32_t thread_index_id; + uint32_t frame_index; + std::string name; + std::string value; + bool declaration_found; + boost::filesystem::path file_path; + int line_nr; + int line_index; + }; - std::list<std::function<void(const lldb::SBProcess &)>> on_start; - /// The handlers are not run in the main loop. - std::list<std::function<void(int exit_status)>> on_exit; - /// The handlers are not run in the main loop. - std::list<std::function<void(const lldb::SBEvent &)>> on_event; + private: + LLDB(); - std::mutex mutex; + public: + static LLDB &get() { + static LLDB singleton; + return singleton; + } - void start(const std::string &command, const boost::filesystem::path &path = "", - const std::vector<std::pair<boost::filesystem::path, int> > &breakpoints = {}, - const std::vector<std::string> &startup_commands = {}, const std::string &remote_host = ""); + std::list<std::function<void(const lldb::SBProcess &)>> on_start; + /// The handlers are not run in the main loop. + std::list<std::function<void(int exit_status)>> on_exit; + /// The handlers are not run in the main loop. + std::list<std::function<void(const lldb::SBEvent &)>> on_event; - void continue_debug(); //can't use continue as function name - void stop(); + std::mutex mutex; - void kill(); + void start(const std::string &command, const boost::filesystem::path &path = "", + const std::vector<std::pair<boost::filesystem::path, int> > &breakpoints = {}, + const std::vector<std::string> &startup_commands = {}, const std::string &remote_host = ""); - void step_over(); + void continue_debug(); //can't use continue as function name + void stop(); - void step_into(); + void kill(); - void step_out(); + void step_over(); - std::pair<std::string, std::string> run_command(const std::string &command); + void step_into(); - std::vector<Frame> get_backtrace(); + void step_out(); - std::vector<Variable> get_variables(); + std::pair<std::string, std::string> run_command(const std::string &command); - void select_frame(uint32_t frame_index, uint32_t thread_index_id = 0); + std::vector<Frame> get_backtrace(); - void cancel(); + std::vector<Variable> get_variables(); - std::string - get_value(const std::string &variable, const boost::filesystem::path &file_path, unsigned int line_nr, - unsigned int line_index); + void select_frame(uint32_t frame_index, uint32_t thread_index_id = 0); - std::string - get_return_value(const boost::filesystem::path &file_path, unsigned int line_nr, unsigned int line_index); + void cancel(); - bool is_invalid(); + std::string + get_value(const std::string &variable, const boost::filesystem::path &file_path, unsigned int line_nr, + unsigned int line_index); - bool is_stopped(); + std::string + get_return_value(const boost::filesystem::path &file_path, unsigned int line_nr, unsigned int line_index); - bool is_running(); + bool is_invalid(); - void add_breakpoint(const boost::filesystem::path &file_path, int line_nr); + bool is_stopped(); - void remove_breakpoint(const boost::filesystem::path &file_path, int line_nr, int line_count); + bool is_running(); - void write(const std::string &buffer); + void add_breakpoint(const boost::filesystem::path &file_path, int line_nr); - private: - std::tuple<std::vector<std::string>, std::string, std::vector<std::string>> - parse_run_arguments(const std::string &command); + void remove_breakpoint(const boost::filesystem::path &file_path, int line_nr, int line_count); - std::unique_ptr<lldb::SBDebugger> debugger; - std::unique_ptr<lldb::SBListener> listener; - std::unique_ptr<lldb::SBProcess> process; - std::thread debug_thread; + void write(const std::string &buffer); - lldb::StateType state; + private: + std::tuple<std::vector<std::string>, std::string, std::vector<std::string>> + parse_run_arguments(const std::string &command); - size_t buffer_size; - }; + 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; + + size_t buffer_size; + }; } diff --git a/src/dialogs.cc b/src/dialogs.cc index f4194848..efd95a9e 100644 --- a/src/dialogs.cc +++ b/src/dialogs.cc @@ -2,79 +2,79 @@ #include <cmath> Dialog::Message::Message(const std::string &text) : Gtk::Window(Gtk::WindowType::WINDOW_POPUP) { - auto g_application = g_application_get_default(); - auto gio_application = Glib::wrap(g_application, true); - auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application); - set_transient_for(*application->get_active_window()); + auto g_application = g_application_get_default(); + auto gio_application = Glib::wrap(g_application, true); + auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application); + set_transient_for(*application->get_active_window()); - set_position(Gtk::WindowPosition::WIN_POS_CENTER_ON_PARENT); - set_modal(true); - set_type_hint(Gdk::WindowTypeHint::WINDOW_TYPE_HINT_NOTIFICATION); - property_decorated() = false; - set_skip_taskbar_hint(true); + set_position(Gtk::WindowPosition::WIN_POS_CENTER_ON_PARENT); + set_modal(true); + set_type_hint(Gdk::WindowTypeHint::WINDOW_TYPE_HINT_NOTIFICATION); + property_decorated() = false; + set_skip_taskbar_hint(true); - auto box = Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL)); - auto label = Gtk::manage(new Gtk::Label(text)); - label->set_padding(10, 10); - box->pack_start(*label); - add(*box); + auto box = Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL)); + auto label = Gtk::manage(new Gtk::Label(text)); + label->set_padding(10, 10); + box->pack_start(*label); + add(*box); - show_all_children(); - show_now(); + show_all_children(); + show_now(); - while (Gtk::Main::events_pending()) - Gtk::Main::iteration(false); + while (Gtk::Main::events_pending()) + Gtk::Main::iteration(false); } bool Dialog::Message::on_delete_event(GdkEventAny *event) { - return true; + return true; } std::string Dialog::gtk_dialog(const boost::filesystem::path &path, const std::string &title, const std::vector<std::pair<std::string, Gtk::ResponseType>> &buttons, Gtk::FileChooserAction action) { - // Workaround for crash on MacOS when filtering files in file/folder dialogs. - // See also https://github.com/cppit/jucipp/issues/259. - // TODO 2018: check if this bug has been fixed + // Workaround for crash on MacOS when filtering files in file/folder dialogs. + // See also https://github.com/cppit/jucipp/issues/259. + // TODO 2018: check if this bug has been fixed #ifdef __APPLE__ - class FileChooserDialog : public Gtk::FileChooserDialog { - Gtk::FileChooserAction action; - public: - FileChooserDialog(const Glib::ustring& title, Gtk::FileChooserAction action) : Gtk::FileChooserDialog(title, action), action(action) {} - protected: - bool on_key_press_event(GdkEventKey *key_event) override { - if(action==Gtk::FileChooserAction::FILE_CHOOSER_ACTION_OPEN || action==Gtk::FileChooserAction::FILE_CHOOSER_ACTION_SELECT_FOLDER) { - auto unicode=gdk_keyval_to_unicode(key_event->keyval); - if(unicode>31 && unicode!=127) - return true; - } - return Gtk::FileChooserDialog::on_key_press_event(key_event); + class FileChooserDialog : public Gtk::FileChooserDialog { + Gtk::FileChooserAction action; + public: + FileChooserDialog(const Glib::ustring& title, Gtk::FileChooserAction action) : Gtk::FileChooserDialog(title, action), action(action) {} + protected: + bool on_key_press_event(GdkEventKey *key_event) override { + if(action==Gtk::FileChooserAction::FILE_CHOOSER_ACTION_OPEN || action==Gtk::FileChooserAction::FILE_CHOOSER_ACTION_SELECT_FOLDER) { + auto unicode=gdk_keyval_to_unicode(key_event->keyval); + if(unicode>31 && unicode!=127) + return true; } - }; - FileChooserDialog dialog(title, action); + return Gtk::FileChooserDialog::on_key_press_event(key_event); + } + }; + FileChooserDialog dialog(title, action); #else - Gtk::FileChooserDialog dialog(title, action); + Gtk::FileChooserDialog dialog(title, action); #endif - auto g_application = g_application_get_default(); - auto gio_application = Glib::wrap(g_application, true); - auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application); - dialog.set_transient_for(*application->get_active_window()); - dialog.set_position(Gtk::WindowPosition::WIN_POS_CENTER_ON_PARENT); + auto g_application = g_application_get_default(); + auto gio_application = Glib::wrap(g_application, true); + auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application); + dialog.set_transient_for(*application->get_active_window()); + dialog.set_position(Gtk::WindowPosition::WIN_POS_CENTER_ON_PARENT); - if (title == "Save File As") - gtk_file_chooser_set_filename(reinterpret_cast<GtkFileChooser *>(dialog.gobj()), path.string().c_str()); - else if (!path.empty()) - gtk_file_chooser_set_current_folder(reinterpret_cast<GtkFileChooser *>(dialog.gobj()), path.string().c_str()); - else { - boost::system::error_code ec; - auto current_path = boost::filesystem::current_path(ec); - if (!ec) - gtk_file_chooser_set_current_folder(reinterpret_cast<GtkFileChooser *>(dialog.gobj()), - current_path.string().c_str()); - } + if (title == "Save File As") + gtk_file_chooser_set_filename(reinterpret_cast<GtkFileChooser *>(dialog.gobj()), path.string().c_str()); + else if (!path.empty()) + gtk_file_chooser_set_current_folder(reinterpret_cast<GtkFileChooser *>(dialog.gobj()), path.string().c_str()); + else { + boost::system::error_code ec; + auto current_path = boost::filesystem::current_path(ec); + if (!ec) + gtk_file_chooser_set_current_folder(reinterpret_cast<GtkFileChooser *>(dialog.gobj()), + current_path.string().c_str()); + } - for (auto &button : buttons) - dialog.add_button(button.first, button.second); - return dialog.run() == Gtk::RESPONSE_OK ? dialog.get_filename() : ""; + for (auto &button : buttons) + dialog.add_button(button.first, button.second); + return dialog.run() == Gtk::RESPONSE_OK ? dialog.get_filename() : ""; } diff --git a/src/dialogs.h b/src/dialogs.h index b3e57730..ad46d450 100644 --- a/src/dialogs.h +++ b/src/dialogs.h @@ -7,26 +7,26 @@ class Dialog { public: - static std::string open_folder(const boost::filesystem::path &path); + static std::string open_folder(const boost::filesystem::path &path); - static std::string open_file(const boost::filesystem::path &path); + static std::string open_file(const boost::filesystem::path &path); - static std::string new_file(const boost::filesystem::path &path); + static std::string new_file(const boost::filesystem::path &path); - static std::string new_folder(const boost::filesystem::path &path); + static std::string new_folder(const boost::filesystem::path &path); - static std::string save_file_as(const boost::filesystem::path &path); + static std::string save_file_as(const boost::filesystem::path &path); - class Message : public Gtk::Window { - public: - Message(const std::string &text); + class Message : public Gtk::Window { + public: + Message(const std::string &text); - protected: - bool on_delete_event(GdkEventAny *event) override; - }; + protected: + bool on_delete_event(GdkEventAny *event) override; + }; private: - static std::string gtk_dialog(const boost::filesystem::path &path, const std::string &title, - const std::vector<std::pair<std::string, Gtk::ResponseType>> &buttons, - Gtk::FileChooserAction gtk_options); + static std::string gtk_dialog(const boost::filesystem::path &path, const std::string &title, + const std::vector<std::pair<std::string, Gtk::ResponseType>> &buttons, + Gtk::FileChooserAction gtk_options); }; diff --git a/src/dialogs_unix.cc b/src/dialogs_unix.cc index 865bf3d9..cbd1f052 100644 --- a/src/dialogs_unix.cc +++ b/src/dialogs_unix.cc @@ -1,32 +1,32 @@ #include "dialogs.h" std::string Dialog::open_folder(const boost::filesystem::path &path) { - return gtk_dialog(path, "Open Folder", - {std::make_pair("Cancel", Gtk::RESPONSE_CANCEL), std::make_pair("Open", Gtk::RESPONSE_OK)}, - Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER); + return gtk_dialog(path, "Open Folder", + {std::make_pair("Cancel", Gtk::RESPONSE_CANCEL), std::make_pair("Open", Gtk::RESPONSE_OK)}, + Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER); } std::string Dialog::new_file(const boost::filesystem::path &path) { - return gtk_dialog(path, "New File", - {std::make_pair("Cancel", Gtk::RESPONSE_CANCEL), std::make_pair("Save", Gtk::RESPONSE_OK)}, - Gtk::FILE_CHOOSER_ACTION_SAVE); + return gtk_dialog(path, "New File", + {std::make_pair("Cancel", Gtk::RESPONSE_CANCEL), std::make_pair("Save", Gtk::RESPONSE_OK)}, + Gtk::FILE_CHOOSER_ACTION_SAVE); } std::string Dialog::new_folder(const boost::filesystem::path &path) { - return gtk_dialog(path, "New Folder", - {std::make_pair("Cancel", Gtk::RESPONSE_CANCEL), std::make_pair("Create", Gtk::RESPONSE_OK)}, - Gtk::FILE_CHOOSER_ACTION_CREATE_FOLDER); + return gtk_dialog(path, "New Folder", + {std::make_pair("Cancel", Gtk::RESPONSE_CANCEL), std::make_pair("Create", Gtk::RESPONSE_OK)}, + Gtk::FILE_CHOOSER_ACTION_CREATE_FOLDER); } std::string Dialog::open_file(const boost::filesystem::path &path) { - return gtk_dialog(path, "Open File", - {std::make_pair("Cancel", Gtk::RESPONSE_CANCEL), std::make_pair("Select", Gtk::RESPONSE_OK)}, - Gtk::FILE_CHOOSER_ACTION_OPEN); + return gtk_dialog(path, "Open File", + {std::make_pair("Cancel", Gtk::RESPONSE_CANCEL), std::make_pair("Select", Gtk::RESPONSE_OK)}, + Gtk::FILE_CHOOSER_ACTION_OPEN); } std::string Dialog::save_file_as(const boost::filesystem::path &path) { - return gtk_dialog(path, "Save File As", - {std::make_pair("Cancel", Gtk::RESPONSE_CANCEL), std::make_pair("Save", Gtk::RESPONSE_OK)}, - Gtk::FILE_CHOOSER_ACTION_SAVE); + return gtk_dialog(path, "Save File As", + {std::make_pair("Cancel", Gtk::RESPONSE_CANCEL), std::make_pair("Save", Gtk::RESPONSE_OK)}, + Gtk::FILE_CHOOSER_ACTION_SAVE); } diff --git a/src/dialogs_win.cc b/src/dialogs_win.cc index 6322a9ae..cb0f27f4 100644 --- a/src/dialogs_win.cc +++ b/src/dialogs_win.cc @@ -17,149 +17,149 @@ using namespace std; //TODO: remove class Win32Dialog { public: - Win32Dialog() {}; - - ~Win32Dialog() { - if (dialog != nullptr) - dialog->Release(); - } - - /** Returns the selected item's path as a string */ - std::string open(const std::wstring &title, unsigned option = 0) { - if (!init(CLSID_FileOpenDialog)) - return ""; - - if (!set_title(title) || !add_option(option)) - return ""; - if (!set_folder()) - return ""; - - return show(); + Win32Dialog() {}; + + ~Win32Dialog() { + if (dialog != nullptr) + dialog->Release(); + } + + /** Returns the selected item's path as a string */ + std::string open(const std::wstring &title, unsigned option = 0) { + if (!init(CLSID_FileOpenDialog)) + return ""; + + if (!set_title(title) || !add_option(option)) + return ""; + if (!set_folder()) + return ""; + + return show(); + } + + std::string save(const std::wstring &title, const boost::filesystem::path &file_path = "", unsigned option = 0) { + if (!init(CLSID_FileSaveDialog)) + return ""; + + if (!set_title(title) || !add_option(option)) + return ""; + if (!set_folder()) + return ""; + std::vector<COMDLG_FILTERSPEC> extensions; + if (!file_path.empty()) { + if (file_path.has_extension() && file_path.filename() != file_path.extension()) { + auto extension = (L"*" + file_path.extension().native()).c_str(); + extensions.emplace_back(COMDLG_FILTERSPEC{extension, extension}); + if (!set_default_file_extension(extension)) + return ""; + } } + extensions.emplace_back(COMDLG_FILTERSPEC{L"All files", L"*.*"}); + if (dialog->SetFileTypes(extensions.size(), extensions.data()) != S_OK) + return ""; - std::string save(const std::wstring &title, const boost::filesystem::path &file_path = "", unsigned option = 0) { - if (!init(CLSID_FileSaveDialog)) - return ""; - - if (!set_title(title) || !add_option(option)) - return ""; - if (!set_folder()) - return ""; - std::vector<COMDLG_FILTERSPEC> extensions; - if (!file_path.empty()) { - if (file_path.has_extension() && file_path.filename() != file_path.extension()) { - auto extension = (L"*" + file_path.extension().native()).c_str(); - extensions.emplace_back(COMDLG_FILTERSPEC{extension, extension}); - if (!set_default_file_extension(extension)) - return ""; - } - } - extensions.emplace_back(COMDLG_FILTERSPEC{L"All files", L"*.*"}); - if (dialog->SetFileTypes(extensions.size(), extensions.data()) != S_OK) - return ""; - - return show(); - } + return show(); + } private: - IFileDialog *dialog = nullptr; - DWORD options; - - bool init(CLSID type) { - if (CoCreateInstance(type, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&dialog)) != S_OK) - return false; - if (dialog->GetOptions(&options) != S_OK) - return false; - return true; - } - - /** available options are listed at https://msdn.microsoft.com/en-gb/library/windows/desktop/dn457282(v=vs.85).aspx */ - bool add_option(unsigned option) { - if (dialog->SetOptions(options | option) != S_OK) - return false; - return true; + IFileDialog *dialog = nullptr; + DWORD options; + + bool init(CLSID type) { + if (CoCreateInstance(type, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&dialog)) != S_OK) + return false; + if (dialog->GetOptions(&options) != S_OK) + return false; + return true; + } + + /** available options are listed at https://msdn.microsoft.com/en-gb/library/windows/desktop/dn457282(v=vs.85).aspx */ + bool add_option(unsigned option) { + if (dialog->SetOptions(options | option) != S_OK) + return false; + return true; + } + + bool set_title(const std::wstring &title) { + if (dialog->SetTitle(title.c_str()) != S_OK) + return false; + return true; + } + + /** Sets the extensions the browser can find */ + bool set_default_file_extension(const std::wstring &file_extension) { + if (dialog->SetDefaultExtension(file_extension.c_str()) != S_OK) + return false; + return true; + } + + /** Sets the directory to start browsing */ + bool set_folder() { + auto g_application = g_application_get_default(); //TODO: Post issue that Gio::Application::get_default should return pointer and not Glib::RefPtr + auto gio_application = Glib::wrap(g_application, true); + auto application = Glib::RefPtr<Application>::cast_static(gio_application); + + auto current_path = application->window->notebook.get_current_folder(); + boost::system::error_code ec; + if (current_path.empty()) + current_path = boost::filesystem::current_path(ec); + if (ec) + return false; + + std::wstring path = current_path.native(); + size_t pos = 0; + while ((pos = path.find(L'/', pos)) != + std::wstring::npos) {//TODO: issue bug report on boost::filesystem::path::native on MSYS2 + path.replace(pos, 1, L"\\"); + pos++; } - bool set_title(const std::wstring &title) { - if (dialog->SetTitle(title.c_str()) != S_OK) - return false; - return true; - } - - /** Sets the extensions the browser can find */ - bool set_default_file_extension(const std::wstring &file_extension) { - if (dialog->SetDefaultExtension(file_extension.c_str()) != S_OK) - return false; - return true; - } - - /** Sets the directory to start browsing */ - bool set_folder() { - auto g_application = g_application_get_default(); //TODO: Post issue that Gio::Application::get_default should return pointer and not Glib::RefPtr - auto gio_application = Glib::wrap(g_application, true); - auto application = Glib::RefPtr<Application>::cast_static(gio_application); - - auto current_path = application->window->notebook.get_current_folder(); - boost::system::error_code ec; - if (current_path.empty()) - current_path = boost::filesystem::current_path(ec); - if (ec) - return false; - - std::wstring path = current_path.native(); - size_t pos = 0; - while ((pos = path.find(L'/', pos)) != - std::wstring::npos) {//TODO: issue bug report on boost::filesystem::path::native on MSYS2 - path.replace(pos, 1, L"\\"); - pos++; - } - - IShellItem *folder = nullptr; - if (SHCreateItemFromParsingName(path.c_str(), nullptr, IID_PPV_ARGS(&folder)) != S_OK) - return false; - if (dialog->SetFolder(folder) != S_OK) - return false; - folder->Release(); - return true; - } - - std::string show() { - if (dialog->Show(nullptr) != S_OK) - return ""; - IShellItem *result = nullptr; - if (dialog->GetResult(&result) != S_OK) - return ""; - LPWSTR file_path = nullptr; - auto hresult = result->GetDisplayName(SIGDN_FILESYSPATH, &file_path); - result->Release(); - if (hresult != S_OK) - return ""; - std::wstring file_path_wstring(file_path); - std::string file_path_string(file_path_wstring.begin(), file_path_wstring.end()); - CoTaskMemFree(file_path); - return file_path_string; - } + IShellItem *folder = nullptr; + if (SHCreateItemFromParsingName(path.c_str(), nullptr, IID_PPV_ARGS(&folder)) != S_OK) + return false; + if (dialog->SetFolder(folder) != S_OK) + return false; + folder->Release(); + return true; + } + + std::string show() { + if (dialog->Show(nullptr) != S_OK) + return ""; + IShellItem *result = nullptr; + if (dialog->GetResult(&result) != S_OK) + return ""; + LPWSTR file_path = nullptr; + auto hresult = result->GetDisplayName(SIGDN_FILESYSPATH, &file_path); + result->Release(); + if (hresult != S_OK) + return ""; + std::wstring file_path_wstring(file_path); + std::string file_path_string(file_path_wstring.begin(), file_path_wstring.end()); + CoTaskMemFree(file_path); + return file_path_string; + } }; std::string Dialog::open_folder() { - return Win32Dialog().open(L"Open Folder", FOS_PICKFOLDERS); + return Win32Dialog().open(L"Open Folder", FOS_PICKFOLDERS); } std::string Dialog::new_file() { - return Win32Dialog().save(L"New File"); + return Win32Dialog().save(L"New File"); } std::string Dialog::new_folder() { - //Win32 (IFileDialog) does not support create folder... - return gtk_dialog("New Folder", - {std::make_pair("Cancel", Gtk::RESPONSE_CANCEL), std::make_pair("Create", Gtk::RESPONSE_OK)}, - Gtk::FILE_CHOOSER_ACTION_CREATE_FOLDER); + //Win32 (IFileDialog) does not support create folder... + return gtk_dialog("New Folder", + {std::make_pair("Cancel", Gtk::RESPONSE_CANCEL), std::make_pair("Create", Gtk::RESPONSE_OK)}, + Gtk::FILE_CHOOSER_ACTION_CREATE_FOLDER); } std::string Dialog::open_file() { - return Win32Dialog().open(L"Open File"); + return Win32Dialog().open(L"Open File"); } std::string Dialog::save_file_as(const boost::filesystem::path &file_path) { - return Win32Dialog().save(L"Save File As", file_path); + return Win32Dialog().save(L"Save File As", file_path); } diff --git a/src/directories.cc b/src/directories.cc index 299f379c..eff783e7 100644 --- a/src/directories.cc +++ b/src/directories.cc @@ -8,724 +8,724 @@ bool Directories::TreeStore::row_drop_possible_vfunc(const Gtk::TreeModel::Path &path, const Gtk::SelectionData &selection_data) const { - return true; + return true; } bool Directories::TreeStore::drag_data_received_vfunc(const TreeModel::Path &path, const Gtk::SelectionData &selection_data) { - auto &directories = Directories::get(); - - auto get_target_folder = [this, &directories](const TreeModel::Path &path) { - if (path.size() == 1) - return directories.path; + auto &directories = Directories::get(); + + auto get_target_folder = [this, &directories](const TreeModel::Path &path) { + if (path.size() == 1) + return directories.path; + else { + auto it = get_iter(path); + if (it) { + auto prev_path = path; + prev_path.up(); + it = get_iter(prev_path); + if (it) + return it->get_value(directories.column_record.path); + } else { + auto prev_path = path; + prev_path.up(); + if (prev_path.size() == 1) + return directories.path; else { - auto it = get_iter(path); - if (it) { - auto prev_path = path; - prev_path.up(); - it = get_iter(prev_path); - if (it) - return it->get_value(directories.column_record.path); - } else { - auto prev_path = path; - prev_path.up(); - if (prev_path.size() == 1) - return directories.path; - else { - prev_path.up(); - it = get_iter(prev_path); - if (it) - return it->get_value(directories.column_record.path); - } - } + prev_path.up(); + it = get_iter(prev_path); + if (it) + return it->get_value(directories.column_record.path); } - return boost::filesystem::path(); - }; + } + } + return boost::filesystem::path(); + }; - auto it = directories.get_selection()->get_selected(); - if (it) { - auto source_path = it->get_value(directories.column_record.path); - if (source_path.empty()) - return false; + auto it = directories.get_selection()->get_selected(); + if (it) { + auto source_path = it->get_value(directories.column_record.path); + if (source_path.empty()) + return false; - auto target_path = get_target_folder(path); - target_path /= source_path.filename(); + auto target_path = get_target_folder(path); + target_path /= source_path.filename(); - if (source_path == target_path) - return false; + if (source_path == target_path) + return false; - if (boost::filesystem::exists(target_path)) { - Terminal::get().print("Error: could not move file: " + target_path.string() + " already exists\n", true); - return false; - } + if (boost::filesystem::exists(target_path)) { + Terminal::get().print("Error: could not move file: " + target_path.string() + " already exists\n", true); + return false; + } - bool is_directory = boost::filesystem::is_directory(source_path); + bool is_directory = boost::filesystem::is_directory(source_path); - if (is_directory) - Directories::get().remove_path(source_path); + if (is_directory) + Directories::get().remove_path(source_path); - boost::system::error_code ec; - boost::filesystem::rename(source_path, target_path, ec); - if (ec) { - Terminal::get().print("Error: could not move file: " + ec.message() + '\n', true); - return false; - } + boost::system::error_code ec; + boost::filesystem::rename(source_path, target_path, ec); + if (ec) { + Terminal::get().print("Error: could not move file: " + ec.message() + '\n', true); + return false; + } - for (size_t c = 0; c < Notebook::get().size(); c++) { - auto view = Notebook::get().get_view(c); - if (is_directory) { - if (filesystem::file_in_path(view->file_path, source_path)) { - auto file_it = view->file_path.begin(); - for (auto source_it = source_path.begin(); source_it != source_path.end(); source_it++) - file_it++; - auto new_file_path = target_path; - for (; file_it != view->file_path.end(); file_it++) - new_file_path /= *file_it; - view->rename(new_file_path); - } - } else if (view->file_path == source_path) { - view->rename(target_path); - break; - } + for (size_t c = 0; c < Notebook::get().size(); c++) { + auto view = Notebook::get().get_view(c); + if (is_directory) { + if (filesystem::file_in_path(view->file_path, source_path)) { + auto file_it = view->file_path.begin(); + for (auto source_it = source_path.begin(); source_it != source_path.end(); source_it++) + file_it++; + auto new_file_path = target_path; + for (; file_it != view->file_path.end(); file_it++) + new_file_path /= *file_it; + view->rename(new_file_path); } - - Directories::get().update(); - Directories::get().on_save_file(target_path); - directories.select(target_path); + } else if (view->file_path == source_path) { + view->rename(target_path); + break; + } } - EntryBox::get().hide(); - return false; + Directories::get().update(); + Directories::get().on_save_file(target_path); + directories.select(target_path); + } + + EntryBox::get().hide(); + return false; } bool Directories::TreeStore::drag_data_delete_vfunc(const Gtk::TreeModel::Path &path) { - return false; + return false; } Directories::Directories() : Gtk::ListViewText(1) { - set_enable_tree_lines(true); - - tree_store = TreeStore::create(); - tree_store->set_column_types(column_record); - set_model(tree_store); - - get_column(0)->set_title(""); - - auto renderer = dynamic_cast<Gtk::CellRendererText *>(get_column(0)->get_first_cell()); - get_column(0)->set_cell_data_func(*renderer, - [this](Gtk::CellRenderer *renderer, const Gtk::TreeModel::iterator &iter) { - if (auto renderer_text = dynamic_cast<Gtk::CellRendererText *>(renderer)) - renderer_text->property_markup() = iter->get_value(column_record.markup); - }); - - get_style_context()->add_class("juci_directories"); - - tree_store->set_sort_column(column_record.id, Gtk::SortType::SORT_ASCENDING); - set_enable_search(true); //TODO: why does this not work in OS X? - set_search_column(column_record.name); - - signal_row_activated().connect([this](const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *column) { - auto iter = tree_store->get_iter(path); - if (iter) { - auto filesystem_path = iter->get_value(column_record.path); - if (filesystem_path != "") { - if (boost::filesystem::is_directory(boost::filesystem::path(filesystem_path))) - row_expanded(path) ? collapse_row(path) : expand_row(path, false); - else - Notebook::get().open(filesystem_path); - } - } - }); + set_enable_tree_lines(true); - signal_test_expand_row().connect([this](const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path) { - if (iter->children().begin()->get_value(column_record.path) == "") - add_or_update_path(iter->get_value(column_record.path), *iter, true); - return false; - }); - signal_row_collapsed().connect([this](const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path) { - this->remove_path(iter->get_value(column_record.path)); - }); + tree_store = TreeStore::create(); + tree_store->set_column_types(column_record); + set_model(tree_store); - enable_model_drag_source(); - enable_model_drag_dest(); + get_column(0)->set_title(""); - auto new_file_label = "New File"; - auto new_file_function = [this] { - if (menu_popup_row_path.empty()) - return; - EntryBox::get().clear(); - EntryBox::get().entries.emplace_back("", [this, source_path = menu_popup_row_path](const std::string &content) { - bool is_directory = boost::filesystem::is_directory(source_path); - auto target_path = (is_directory ? source_path : source_path.parent_path()) / content; - if (!boost::filesystem::exists(target_path)) { - if (filesystem::write(target_path, "")) { - update(); - Notebook::get().open(target_path); - on_save_file(target_path); - } else { - Terminal::get().print("Error: could not create " + target_path.string() + '\n', true); - return; - } - } else { - Terminal::get().print("Error: could not create " + target_path.string() + ": already exists\n", true); - return; - } + auto renderer = dynamic_cast<Gtk::CellRendererText *>(get_column(0)->get_first_cell()); + get_column(0)->set_cell_data_func(*renderer, + [this](Gtk::CellRenderer *renderer, const Gtk::TreeModel::iterator &iter) { + if (auto renderer_text = dynamic_cast<Gtk::CellRendererText *>(renderer)) + renderer_text->property_markup() = iter->get_value(column_record.markup); + }); - EntryBox::get().hide(); - }); - auto entry_it = EntryBox::get().entries.begin(); - entry_it->set_placeholder_text("Filename"); - EntryBox::get().buttons.emplace_back("Create New File", [entry_it]() { - entry_it->activate(); - }); - EntryBox::get().show(); - }; - - menu_item_new_file.set_label(new_file_label); - menu_item_new_file.signal_activate().connect(new_file_function); - menu.append(menu_item_new_file); + get_style_context()->add_class("juci_directories"); - menu_root_item_new_file.set_label(new_file_label); - menu_root_item_new_file.signal_activate().connect(new_file_function); - menu_root.append(menu_root_item_new_file); + tree_store->set_sort_column(column_record.id, Gtk::SortType::SORT_ASCENDING); + set_enable_search(true); //TODO: why does this not work in OS X? + set_search_column(column_record.name); - auto new_folder_label = "New Folder"; - auto new_folder_function = [this] { - if (menu_popup_row_path.empty()) - return; - EntryBox::get().clear(); - EntryBox::get().entries.emplace_back("", [this, source_path = menu_popup_row_path](const std::string &content) { - bool is_directory = boost::filesystem::is_directory(source_path); - auto target_path = (is_directory ? source_path : source_path.parent_path()) / content; - if (!boost::filesystem::exists(target_path)) { - boost::system::error_code ec; - boost::filesystem::create_directory(target_path, ec); - if (!ec) { - update(); - select(target_path); - } else { - Terminal::get().print("Error: could not create " + target_path.string() + ": " + ec.message(), - true); - return; - } - } else { - Terminal::get().print("Error: could not create " + target_path.string() + ": already exists\n", true); - return; - } - - EntryBox::get().hide(); - }); - auto entry_it = EntryBox::get().entries.begin(); - entry_it->set_placeholder_text("Folder name"); - EntryBox::get().buttons.emplace_back("Create New Folder", [entry_it]() { - entry_it->activate(); - }); - EntryBox::get().show(); - }; - - menu_item_new_folder.set_label(new_folder_label); - menu_item_new_folder.signal_activate().connect(new_folder_function); - menu.append(menu_item_new_folder); + signal_row_activated().connect([this](const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *column) { + auto iter = tree_store->get_iter(path); + if (iter) { + auto filesystem_path = iter->get_value(column_record.path); + if (filesystem_path != "") { + if (boost::filesystem::is_directory(boost::filesystem::path(filesystem_path))) + row_expanded(path) ? collapse_row(path) : expand_row(path, false); + else + Notebook::get().open(filesystem_path); + } + } + }); - menu_root_item_new_folder.set_label(new_folder_label); - menu_root_item_new_folder.signal_activate().connect(new_folder_function); - menu_root.append(menu_root_item_new_folder); + signal_test_expand_row().connect([this](const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path) { + if (iter->children().begin()->get_value(column_record.path) == "") + add_or_update_path(iter->get_value(column_record.path), *iter, true); + return false; + }); + signal_row_collapsed().connect([this](const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path) { + this->remove_path(iter->get_value(column_record.path)); + }); + + enable_model_drag_source(); + enable_model_drag_dest(); + + auto new_file_label = "New File"; + auto new_file_function = [this] { + if (menu_popup_row_path.empty()) + return; + EntryBox::get().clear(); + EntryBox::get().entries.emplace_back("", [this, source_path = menu_popup_row_path](const std::string &content) { + bool is_directory = boost::filesystem::is_directory(source_path); + auto target_path = (is_directory ? source_path : source_path.parent_path()) / content; + if (!boost::filesystem::exists(target_path)) { + if (filesystem::write(target_path, "")) { + update(); + Notebook::get().open(target_path); + on_save_file(target_path); + } else { + Terminal::get().print("Error: could not create " + target_path.string() + '\n', true); + return; + } + } else { + Terminal::get().print("Error: could not create " + target_path.string() + ": already exists\n", true); + return; + } - menu.append(menu_item_separator); + EntryBox::get().hide(); + }); + auto entry_it = EntryBox::get().entries.begin(); + entry_it->set_placeholder_text("Filename"); + EntryBox::get().buttons.emplace_back("Create New File", [entry_it]() { + entry_it->activate(); + }); + EntryBox::get().show(); + }; + + menu_item_new_file.set_label(new_file_label); + menu_item_new_file.signal_activate().connect(new_file_function); + menu.append(menu_item_new_file); + + menu_root_item_new_file.set_label(new_file_label); + menu_root_item_new_file.signal_activate().connect(new_file_function); + menu_root.append(menu_root_item_new_file); + + auto new_folder_label = "New Folder"; + auto new_folder_function = [this] { + if (menu_popup_row_path.empty()) + return; + EntryBox::get().clear(); + EntryBox::get().entries.emplace_back("", [this, source_path = menu_popup_row_path](const std::string &content) { + bool is_directory = boost::filesystem::is_directory(source_path); + auto target_path = (is_directory ? source_path : source_path.parent_path()) / content; + if (!boost::filesystem::exists(target_path)) { + boost::system::error_code ec; + boost::filesystem::create_directory(target_path, ec); + if (!ec) { + update(); + select(target_path); + } else { + Terminal::get().print("Error: could not create " + target_path.string() + ": " + ec.message(), + true); + return; + } + } else { + Terminal::get().print("Error: could not create " + target_path.string() + ": already exists\n", true); + return; + } - menu_item_rename.set_label("Rename"); - menu_item_rename.signal_activate().connect([this] { - if (menu_popup_row_path.empty()) - return; - EntryBox::get().clear(); - EntryBox::get().entries.emplace_back(menu_popup_row_path.filename().string(), - [this, source_path = menu_popup_row_path](const std::string &content) { - bool is_directory = boost::filesystem::is_directory(source_path); - - auto target_path = source_path.parent_path() / content; - - if (boost::filesystem::exists(target_path)) { - Terminal::get().print( - "Error: could not rename to " + target_path.string() + - ": already exists\n", true); - return; - } - - if (is_directory) - this->remove_path(source_path); - - boost::system::error_code ec; - boost::filesystem::rename(source_path, target_path, ec); - if (ec) { - Terminal::get().print( - "Error: could not rename " + source_path.string() + ": " + - ec.message() + '\n', true); - return; - } - update(); - on_save_file(target_path); - select(target_path); - - for (size_t c = 0; c < Notebook::get().size(); c++) { - auto view = Notebook::get().get_view(c); - if (is_directory) { - if (filesystem::file_in_path(view->file_path, source_path)) { - auto file_it = view->file_path.begin(); - for (auto source_it = source_path.begin(); - source_it != source_path.end(); source_it++) - file_it++; - auto new_file_path = target_path; - for (; file_it != view->file_path.end(); file_it++) - new_file_path /= *file_it; - view->rename(new_file_path); - } - } else if (view->file_path == source_path) { - view->rename(target_path); - - std::string old_language_id; - if (view->language) - old_language_id = view->language->get_id(); - view->language = Source::guess_language(target_path); - std::string new_language_id; - if (view->language) - new_language_id = view->language->get_id(); - if (new_language_id != old_language_id) - Terminal::get().print( - "Warning: language for " + target_path.string() + - " has changed. Please reopen the file\n"); - } - } - - EntryBox::get().hide(); - }); - auto entry_it = EntryBox::get().entries.begin(); - entry_it->set_placeholder_text("Filename"); - EntryBox::get().buttons.emplace_back("Rename file", [entry_it]() { - entry_it->activate(); - }); - EntryBox::get().show(); + EntryBox::get().hide(); + }); + auto entry_it = EntryBox::get().entries.begin(); + entry_it->set_placeholder_text("Folder name"); + EntryBox::get().buttons.emplace_back("Create New Folder", [entry_it]() { + entry_it->activate(); }); - menu.append(menu_item_rename); + EntryBox::get().show(); + }; + + menu_item_new_folder.set_label(new_folder_label); + menu_item_new_folder.signal_activate().connect(new_folder_function); + menu.append(menu_item_new_folder); + + menu_root_item_new_folder.set_label(new_folder_label); + menu_root_item_new_folder.signal_activate().connect(new_folder_function); + menu_root.append(menu_root_item_new_folder); + + menu.append(menu_item_separator); + + menu_item_rename.set_label("Rename"); + menu_item_rename.signal_activate().connect([this] { + if (menu_popup_row_path.empty()) + return; + EntryBox::get().clear(); + EntryBox::get().entries.emplace_back(menu_popup_row_path.filename().string(), + [this, source_path = menu_popup_row_path](const std::string &content) { + bool is_directory = boost::filesystem::is_directory(source_path); + + auto target_path = source_path.parent_path() / content; + + if (boost::filesystem::exists(target_path)) { + Terminal::get().print( + "Error: could not rename to " + target_path.string() + + ": already exists\n", true); + return; + } + + if (is_directory) + this->remove_path(source_path); + + boost::system::error_code ec; + boost::filesystem::rename(source_path, target_path, ec); + if (ec) { + Terminal::get().print( + "Error: could not rename " + source_path.string() + ": " + + ec.message() + '\n', true); + return; + } + update(); + on_save_file(target_path); + select(target_path); + + for (size_t c = 0; c < Notebook::get().size(); c++) { + auto view = Notebook::get().get_view(c); + if (is_directory) { + if (filesystem::file_in_path(view->file_path, source_path)) { + auto file_it = view->file_path.begin(); + for (auto source_it = source_path.begin(); + source_it != source_path.end(); source_it++) + file_it++; + auto new_file_path = target_path; + for (; file_it != view->file_path.end(); file_it++) + new_file_path /= *file_it; + view->rename(new_file_path); + } + } else if (view->file_path == source_path) { + view->rename(target_path); + + std::string old_language_id; + if (view->language) + old_language_id = view->language->get_id(); + view->language = Source::guess_language(target_path); + std::string new_language_id; + if (view->language) + new_language_id = view->language->get_id(); + if (new_language_id != old_language_id) + Terminal::get().print( + "Warning: language for " + target_path.string() + + " has changed. Please reopen the file\n"); + } + } + + EntryBox::get().hide(); + }); + auto entry_it = EntryBox::get().entries.begin(); + entry_it->set_placeholder_text("Filename"); + EntryBox::get().buttons.emplace_back("Rename file", [entry_it]() { + entry_it->activate(); + }); + EntryBox::get().show(); + }); + menu.append(menu_item_rename); + + menu_item_delete.set_label("Delete"); + menu_item_delete.signal_activate().connect([this] { + if (menu_popup_row_path.empty()) + return; + Gtk::MessageDialog dialog(*static_cast<Gtk::Window *>(get_toplevel()), "Delete!", false, Gtk::MESSAGE_QUESTION, + Gtk::BUTTONS_YES_NO); + dialog.set_default_response(Gtk::RESPONSE_NO); + dialog.set_secondary_text("Are you sure you want to delete " + menu_popup_row_path.string() + "?"); + int result = dialog.run(); + if (result == Gtk::RESPONSE_YES) { + bool is_directory = boost::filesystem::is_directory(menu_popup_row_path); + + boost::system::error_code ec; + boost::filesystem::remove_all(menu_popup_row_path, ec); + if (ec) + Terminal::get().print( + "Error: could not delete " + menu_popup_row_path.string() + ": " + ec.message() + "\n", true); + else { + update(); - menu_item_delete.set_label("Delete"); - menu_item_delete.signal_activate().connect([this] { - if (menu_popup_row_path.empty()) - return; - Gtk::MessageDialog dialog(*static_cast<Gtk::Window *>(get_toplevel()), "Delete!", false, Gtk::MESSAGE_QUESTION, - Gtk::BUTTONS_YES_NO); - dialog.set_default_response(Gtk::RESPONSE_NO); - dialog.set_secondary_text("Are you sure you want to delete " + menu_popup_row_path.string() + "?"); - int result = dialog.run(); - if (result == Gtk::RESPONSE_YES) { - bool is_directory = boost::filesystem::is_directory(menu_popup_row_path); - - boost::system::error_code ec; - boost::filesystem::remove_all(menu_popup_row_path, ec); - if (ec) - Terminal::get().print( - "Error: could not delete " + menu_popup_row_path.string() + ": " + ec.message() + "\n", true); - else { - update(); - - for (size_t c = 0; c < Notebook::get().size(); c++) { - auto view = Notebook::get().get_view(c); - - if (is_directory) { - if (filesystem::file_in_path(view->file_path, menu_popup_row_path)) - view->get_buffer()->set_modified(); - } else if (view->file_path == menu_popup_row_path) - view->get_buffer()->set_modified(); - } - } + for (size_t c = 0; c < Notebook::get().size(); c++) { + auto view = Notebook::get().get_view(c); + + if (is_directory) { + if (filesystem::file_in_path(view->file_path, menu_popup_row_path)) + view->get_buffer()->set_modified(); + } else if (view->file_path == menu_popup_row_path) + view->get_buffer()->set_modified(); } - }); - menu.append(menu_item_delete); - - menu.show_all(); - menu.accelerate(*this); - - menu_root.show_all(); - menu_root.accelerate(*this); - - set_headers_clickable(); - forall([this](Gtk::Widget &widget) { - if (widget.get_name() == "GtkButton") { - widget.signal_button_press_event().connect([this](GdkEventButton *event) { - if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_SECONDARY && !path.empty()) { - menu_popup_row_path = this->path; - menu_root.popup(event->button, event->time); - } - return true; - }); + } + } + }); + menu.append(menu_item_delete); + + menu.show_all(); + menu.accelerate(*this); + + menu_root.show_all(); + menu_root.accelerate(*this); + + set_headers_clickable(); + forall([this](Gtk::Widget &widget) { + if (widget.get_name() == "GtkButton") { + widget.signal_button_press_event().connect([this](GdkEventButton *event) { + if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_SECONDARY && !path.empty()) { + menu_popup_row_path = this->path; + menu_root.popup(event->button, event->time); } - }); + return true; + }); + } + }); } Directories::~Directories() { - dispatcher.disconnect(); + dispatcher.disconnect(); } void Directories::open(const boost::filesystem::path &dir_path) { - boost::system::error_code ec; - if (dir_path.empty() || !boost::filesystem::exists(dir_path, ec) || ec) - return; - - tree_store->clear(); - - path = filesystem::get_normal_path(dir_path); - - //TODO: report that set_title does not handle '_' correctly? - auto title = path.filename().string(); - size_t pos = 0; - while ((pos = title.find('_', pos)) != std::string::npos) { - title.replace(pos, 1, "__"); - pos += 2; - } - get_column(0)->set_title(title); - - for (auto &directory: directories) { - if (directory.second.repository) - directory.second.repository->clear_saved_status(); - } - directories.clear(); - - add_or_update_path(path, Gtk::TreeModel::Row(), true); + boost::system::error_code ec; + if (dir_path.empty() || !boost::filesystem::exists(dir_path, ec) || ec) + return; + + tree_store->clear(); + + path = filesystem::get_normal_path(dir_path); + + //TODO: report that set_title does not handle '_' correctly? + auto title = path.filename().string(); + size_t pos = 0; + while ((pos = title.find('_', pos)) != std::string::npos) { + title.replace(pos, 1, "__"); + pos += 2; + } + get_column(0)->set_title(title); + + for (auto &directory: directories) { + if (directory.second.repository) + directory.second.repository->clear_saved_status(); + } + directories.clear(); + + add_or_update_path(path, Gtk::TreeModel::Row(), true); } void Directories::update() { - std::vector<std::pair<std::string, Gtk::TreeModel::Row> > saved_directories; - for (auto &directory: directories) - saved_directories.emplace_back(directory.first, directory.second.row); - for (auto &directory: saved_directories) - add_or_update_path(directory.first, directory.second, false); + std::vector<std::pair<std::string, Gtk::TreeModel::Row> > saved_directories; + for (auto &directory: directories) + saved_directories.emplace_back(directory.first, directory.second.row); + for (auto &directory: saved_directories) + add_or_update_path(directory.first, directory.second, false); } void Directories::on_save_file(boost::filesystem::path file_path) { - auto it = directories.find(file_path.parent_path().string()); - if (it != directories.end()) { - if (it->second.repository) - it->second.repository->clear_saved_status(); - colorize_path(it->first, true); - } + auto it = directories.find(file_path.parent_path().string()); + if (it != directories.end()) { + if (it->second.repository) + it->second.repository->clear_saved_status(); + colorize_path(it->first, true); + } } void Directories::select(const boost::filesystem::path &select_path) { - if (path == "") - return; - - if (!filesystem::file_in_path(select_path, path)) - return; - - //return if the select_path is already selected - auto iter = get_selection()->get_selected(); - if (iter) { - if (iter->get_value(column_record.path) == select_path) - return; - } - - std::list<boost::filesystem::path> paths; - boost::filesystem::path parent_path; - if (boost::filesystem::is_directory(select_path)) - parent_path = select_path; - else - parent_path = select_path.parent_path(); - - //check if select_path is already expanded - if (directories.find(parent_path.string()) != directories.end()) { - //set cursor at select_path and return - tree_store->foreach_iter([this, &select_path](const Gtk::TreeModel::iterator &iter) { - if (iter->get_value(column_record.path) == select_path) { - auto tree_path = Gtk::TreePath(iter); - expand_to_path(tree_path); - set_cursor(tree_path); - return true; - } - return false; - }); - return; - } + if (path == "") + return; + + if (!filesystem::file_in_path(select_path, path)) + return; + + //return if the select_path is already selected + auto iter = get_selection()->get_selected(); + if (iter) { + if (iter->get_value(column_record.path) == select_path) + return; + } + + std::list<boost::filesystem::path> paths; + boost::filesystem::path parent_path; + if (boost::filesystem::is_directory(select_path)) + parent_path = select_path; + else + parent_path = select_path.parent_path(); + + //check if select_path is already expanded + if (directories.find(parent_path.string()) != directories.end()) { + //set cursor at select_path and return + tree_store->foreach_iter([this, &select_path](const Gtk::TreeModel::iterator &iter) { + if (iter->get_value(column_record.path) == select_path) { + auto tree_path = Gtk::TreePath(iter); + expand_to_path(tree_path); + set_cursor(tree_path); + return true; + } + return false; + }); + return; + } + paths.emplace_front(parent_path); + while (parent_path != path) { + parent_path = parent_path.parent_path(); paths.emplace_front(parent_path); - while (parent_path != path) { - parent_path = parent_path.parent_path(); - paths.emplace_front(parent_path); - } - - //expand to select_path - for (auto &a_path: paths) { - tree_store->foreach_iter([this, &a_path](const Gtk::TreeModel::iterator &iter) { - if (iter->get_value(column_record.path) == a_path) { - add_or_update_path(a_path, *iter, true); - return true; - } - return false; - }); - } - - //set cursor at select_path - tree_store->foreach_iter([this, &select_path](const Gtk::TreeModel::iterator &iter) { - if (iter->get_value(column_record.path) == select_path) { - auto tree_path = Gtk::TreePath(iter); - expand_to_path(tree_path); - set_cursor(tree_path); - return true; - } - return false; + } + + //expand to select_path + for (auto &a_path: paths) { + tree_store->foreach_iter([this, &a_path](const Gtk::TreeModel::iterator &iter) { + if (iter->get_value(column_record.path) == a_path) { + add_or_update_path(a_path, *iter, true); + return true; + } + return false; }); + } + + //set cursor at select_path + tree_store->foreach_iter([this, &select_path](const Gtk::TreeModel::iterator &iter) { + if (iter->get_value(column_record.path) == select_path) { + auto tree_path = Gtk::TreePath(iter); + expand_to_path(tree_path); + set_cursor(tree_path); + return true; + } + return false; + }); } bool Directories::on_button_press_event(GdkEventButton *event) { - if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_SECONDARY) { - EntryBox::get().hide(); - Gtk::TreeModel::Path path; - if (get_path_at_pos(static_cast<int>(event->x), static_cast<int>(event->y), path)) { - menu_popup_row_path = get_model()->get_iter(path)->get_value(column_record.path); - if (menu_popup_row_path.empty()) { - auto parent = get_model()->get_iter(path)->parent(); - if (parent) - menu_popup_row_path = parent->get_value(column_record.path); - else { - menu_popup_row_path = this->path; - menu_root.popup(event->button, event->time); - return true; - } - } - menu.popup(event->button, event->time); - return true; - } else if (!this->path.empty()) { - menu_popup_row_path = this->path; - menu_root.popup(event->button, event->time); - return true; + if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_SECONDARY) { + EntryBox::get().hide(); + Gtk::TreeModel::Path path; + if (get_path_at_pos(static_cast<int>(event->x), static_cast<int>(event->y), path)) { + menu_popup_row_path = get_model()->get_iter(path)->get_value(column_record.path); + if (menu_popup_row_path.empty()) { + auto parent = get_model()->get_iter(path)->parent(); + if (parent) + menu_popup_row_path = parent->get_value(column_record.path); + else { + menu_popup_row_path = this->path; + menu_root.popup(event->button, event->time); + return true; } + } + menu.popup(event->button, event->time); + return true; + } else if (!this->path.empty()) { + menu_popup_row_path = this->path; + menu_root.popup(event->button, event->time); + return true; } + } - return Gtk::TreeView::on_button_press_event(event); + return Gtk::TreeView::on_button_press_event(event); } void Directories::add_or_update_path(const boost::filesystem::path &dir_path, const Gtk::TreeModel::Row &row, bool include_parent_paths) { - auto path_it = directories.find(dir_path.string()); - if (!boost::filesystem::exists(dir_path)) { - if (path_it != directories.end()) - directories.erase(path_it); - return; + auto path_it = directories.find(dir_path.string()); + if (!boost::filesystem::exists(dir_path)) { + if (path_it != directories.end()) + directories.erase(path_it); + return; + } + + if (path_it == directories.end()) { + auto g_file = Gio::File::create_for_path(dir_path.string()); + auto monitor = g_file->monitor_directory(Gio::FileMonitorFlags::FILE_MONITOR_WATCH_MOVES); + auto path_and_row = std::make_shared<std::pair<boost::filesystem::path, Gtk::TreeModel::Row> >(dir_path, row); + auto connection = std::make_shared<sigc::connection>(); + + std::shared_ptr<Git::Repository> repository; + try { + repository = Git::get_repository(dir_path); } + catch (const std::exception &) {} - if (path_it == directories.end()) { - auto g_file = Gio::File::create_for_path(dir_path.string()); - auto monitor = g_file->monitor_directory(Gio::FileMonitorFlags::FILE_MONITOR_WATCH_MOVES); - auto path_and_row = std::make_shared<std::pair<boost::filesystem::path, Gtk::TreeModel::Row> >(dir_path, row); - auto connection = std::make_shared<sigc::connection>(); - - std::shared_ptr<Git::Repository> repository; - try { - repository = Git::get_repository(dir_path); - } - catch (const std::exception &) {} - - monitor->signal_changed().connect( - [this, connection, path_and_row, repository](const Glib::RefPtr<Gio::File> &file, - const Glib::RefPtr<Gio::File> &, - Gio::FileMonitorEvent monitor_event) { - if (monitor_event != Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { - if (repository) - repository->clear_saved_status(); - connection->disconnect(); - *connection = Glib::signal_timeout().connect([path_and_row, this]() { - if (directories.find(path_and_row->first.string()) != directories.end()) - add_or_update_path(path_and_row->first, path_and_row->second, true); - return false; - }, 500); - } - }); - - std::shared_ptr<sigc::connection> repository_connection(new sigc::connection(), - [](sigc::connection *connection) { - connection->disconnect(); - delete connection; - }); - - if (repository) { - auto connection = std::make_shared<sigc::connection>(); - *repository_connection = repository->monitor->signal_changed().connect( - [this, connection, path_and_row](const Glib::RefPtr<Gio::File> &file, + monitor->signal_changed().connect( + [this, connection, path_and_row, repository](const Glib::RefPtr<Gio::File> &file, const Glib::RefPtr<Gio::File> &, Gio::FileMonitorEvent monitor_event) { - if (monitor_event != Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { - connection->disconnect(); - *connection = Glib::signal_timeout().connect([this, path_and_row] { - if (directories.find(path_and_row->first.string()) != directories.end()) - colorize_path(path_and_row->first, false); - return false; - }, 500); - } - }); - } - directories[dir_path.string()] = {row, monitor, repository, repository_connection}; - } + if (monitor_event != Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { + if (repository) + repository->clear_saved_status(); + connection->disconnect(); + *connection = Glib::signal_timeout().connect([path_and_row, this]() { + if (directories.find(path_and_row->first.string()) != directories.end()) + add_or_update_path(path_and_row->first, path_and_row->second, true); + return false; + }, 500); + } + }); - Gtk::TreeNodeChildren children(row ? row.children() : tree_store->children()); - if (children) { - if (children.begin()->get_value(column_record.path) == "") - tree_store->erase(children.begin()); - } - std::unordered_set<std::string> not_deleted; - boost::filesystem::directory_iterator end_it; - for (boost::filesystem::directory_iterator it(dir_path); it != end_it; it++) { - auto filename = it->path().filename().string(); - bool already_added = false; - if (children) { - for (auto &child: children) { - if (child->get_value(column_record.name) == filename) { - not_deleted.emplace(filename); - already_added = true; - break; - } - } - } - if (!already_added) { - auto child = tree_store->append(children); - not_deleted.emplace(filename); - child->set_value(column_record.name, filename); - child->set_value(column_record.markup, Glib::Markup::escape_text(filename)); - child->set_value(column_record.path, it->path()); - if (boost::filesystem::is_directory(it->path())) { - child->set_value(column_record.id, '1' + filename); - auto grandchild = tree_store->append(child->children()); - grandchild->set_value(column_record.name, std::string("(empty)")); - grandchild->set_value(column_record.markup, Glib::Markup::escape_text("(empty)")); - grandchild->set_value(column_record.type, PathType::UNKNOWN); - } else { - child->set_value(column_record.id, '2' + filename); - - auto language = Source::guess_language(it->path().filename()); - if (!language) - child->set_value(column_record.type, PathType::UNKNOWN); + std::shared_ptr<sigc::connection> repository_connection(new sigc::connection(), + [](sigc::connection *connection) { + connection->disconnect(); + delete connection; + }); + + if (repository) { + auto connection = std::make_shared<sigc::connection>(); + *repository_connection = repository->monitor->signal_changed().connect( + [this, connection, path_and_row](const Glib::RefPtr<Gio::File> &file, + const Glib::RefPtr<Gio::File> &, + Gio::FileMonitorEvent monitor_event) { + if (monitor_event != Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { + connection->disconnect(); + *connection = Glib::signal_timeout().connect([this, path_and_row] { + if (directories.find(path_and_row->first.string()) != directories.end()) + colorize_path(path_and_row->first, false); + return false; + }, 500); } - } + }); } + directories[dir_path.string()] = {row, monitor, repository, repository_connection}; + } + + Gtk::TreeNodeChildren children(row ? row.children() : tree_store->children()); + if (children) { + if (children.begin()->get_value(column_record.path) == "") + tree_store->erase(children.begin()); + } + std::unordered_set<std::string> not_deleted; + boost::filesystem::directory_iterator end_it; + for (boost::filesystem::directory_iterator it(dir_path); it != end_it; it++) { + auto filename = it->path().filename().string(); + bool already_added = false; if (children) { - for (auto it = children.begin(); it != children.end();) { - if (not_deleted.count(it->get_value(column_record.name)) == 0) { - it = tree_store->erase(it); - } else - it++; + for (auto &child: children) { + if (child->get_value(column_record.name) == filename) { + not_deleted.emplace(filename); + already_added = true; + break; } + } } - if (!children) { - auto child = tree_store->append(children); - child->set_value(column_record.name, std::string("(empty)")); - child->set_value(column_record.markup, Glib::Markup::escape_text("(empty)")); - child->set_value(column_record.type, PathType::UNKNOWN); + if (!already_added) { + auto child = tree_store->append(children); + not_deleted.emplace(filename); + child->set_value(column_record.name, filename); + child->set_value(column_record.markup, Glib::Markup::escape_text(filename)); + child->set_value(column_record.path, it->path()); + if (boost::filesystem::is_directory(it->path())) { + child->set_value(column_record.id, '1' + filename); + auto grandchild = tree_store->append(child->children()); + grandchild->set_value(column_record.name, std::string("(empty)")); + grandchild->set_value(column_record.markup, Glib::Markup::escape_text("(empty)")); + grandchild->set_value(column_record.type, PathType::UNKNOWN); + } else { + child->set_value(column_record.id, '2' + filename); + + auto language = Source::guess_language(it->path().filename()); + if (!language) + child->set_value(column_record.type, PathType::UNKNOWN); + } } - - colorize_path(dir_path, include_parent_paths); + } + if (children) { + for (auto it = children.begin(); it != children.end();) { + if (not_deleted.count(it->get_value(column_record.name)) == 0) { + it = tree_store->erase(it); + } else + it++; + } + } + if (!children) { + auto child = tree_store->append(children); + child->set_value(column_record.name, std::string("(empty)")); + child->set_value(column_record.markup, Glib::Markup::escape_text("(empty)")); + child->set_value(column_record.type, PathType::UNKNOWN); + } + + colorize_path(dir_path, include_parent_paths); } void Directories::remove_path(const boost::filesystem::path &dir_path) { - auto it = directories.find(dir_path.string()); - if (it == directories.end()) - return; - auto children = it->second.row->children(); - - for (auto it = directories.begin(); it != directories.end();) { - if (filesystem::file_in_path(it->first, dir_path)) - it = directories.erase(it); - else - it++; - } + auto it = directories.find(dir_path.string()); + if (it == directories.end()) + return; + auto children = it->second.row->children(); + + for (auto it = directories.begin(); it != directories.end();) { + if (filesystem::file_in_path(it->first, dir_path)) + it = directories.erase(it); + else + it++; + } - if (children) { - while (children) { - tree_store->erase(children.begin()); - } - auto child = tree_store->append(children); - child->set_value(column_record.name, std::string("(empty)")); - child->set_value(column_record.markup, Glib::Markup::escape_text("(empty)")); - child->set_value(column_record.type, PathType::UNKNOWN); + if (children) { + while (children) { + tree_store->erase(children.begin()); } + auto child = tree_store->append(children); + child->set_value(column_record.name, std::string("(empty)")); + child->set_value(column_record.markup, Glib::Markup::escape_text("(empty)")); + child->set_value(column_record.type, PathType::UNKNOWN); + } } void Directories::colorize_path(const boost::filesystem::path &dir_path, bool include_parent_paths) { - auto it = directories.find(dir_path.string()); - if (it == directories.end()) - return; - - if (it != directories.end() && it->second.repository) { - auto repository = it->second.repository; - std::thread git_status_thread([this, dir_path, repository, include_parent_paths] { - Git::Repository::Status status; - try { - status = repository->get_status(); - } - catch (const std::exception &e) { - Terminal::get().async_print(std::string("Error (git): ") + e.what() + '\n', true); - } + auto it = directories.find(dir_path.string()); + if (it == directories.end()) + return; + + if (it != directories.end() && it->second.repository) { + auto repository = it->second.repository; + std::thread git_status_thread([this, dir_path, repository, include_parent_paths] { + Git::Repository::Status status; + try { + status = repository->get_status(); + } + catch (const std::exception &e) { + Terminal::get().async_print(std::string("Error (git): ") + e.what() + '\n', true); + } + + dispatcher.post([this, dir_path = std::move(dir_path), include_parent_paths, status = std::move(status)] { + auto it = directories.find(dir_path.string()); + if (it == directories.end()) + return; + + auto normal_color = get_style_context()->get_color(Gtk::StateFlags::STATE_FLAG_NORMAL); + Gdk::RGBA gray; + gray.set_rgba(0.5, 0.5, 0.5); + Gdk::RGBA yellow; + yellow.set_rgba(1.0, 1.0, 0.2); + double factor = 0.5; + yellow.set_red(normal_color.get_red() + factor * (yellow.get_red() - normal_color.get_red())); + yellow.set_green(normal_color.get_green() + factor * (yellow.get_green() - normal_color.get_green())); + yellow.set_blue(normal_color.get_blue() + factor * (yellow.get_blue() - normal_color.get_blue())); + Gdk::RGBA green; + green.set_rgba(0.0, 1.0, 0.0); + factor = 0.4; + green.set_red(normal_color.get_red() + factor * (green.get_red() - normal_color.get_red())); + green.set_green(normal_color.get_green() + factor * (green.get_green() - normal_color.get_green())); + green.set_blue(normal_color.get_blue() + factor * (green.get_blue() - normal_color.get_blue())); + + do { + Gtk::TreeNodeChildren children(it->second.row ? it->second.row.children() : tree_store->children()); + if (!children) + return; - dispatcher.post([this, dir_path = std::move(dir_path), include_parent_paths, status = std::move(status)] { - auto it = directories.find(dir_path.string()); - if (it == directories.end()) - return; - - auto normal_color = get_style_context()->get_color(Gtk::StateFlags::STATE_FLAG_NORMAL); - Gdk::RGBA gray; - gray.set_rgba(0.5, 0.5, 0.5); - Gdk::RGBA yellow; - yellow.set_rgba(1.0, 1.0, 0.2); - double factor = 0.5; - yellow.set_red(normal_color.get_red() + factor * (yellow.get_red() - normal_color.get_red())); - yellow.set_green(normal_color.get_green() + factor * (yellow.get_green() - normal_color.get_green())); - yellow.set_blue(normal_color.get_blue() + factor * (yellow.get_blue() - normal_color.get_blue())); - Gdk::RGBA green; - green.set_rgba(0.0, 1.0, 0.0); - factor = 0.4; - green.set_red(normal_color.get_red() + factor * (green.get_red() - normal_color.get_red())); - green.set_green(normal_color.get_green() + factor * (green.get_green() - normal_color.get_green())); - green.set_blue(normal_color.get_blue() + factor * (green.get_blue() - normal_color.get_blue())); - - do { - Gtk::TreeNodeChildren children(it->second.row ? it->second.row.children() : tree_store->children()); - if (!children) - return; - - for (auto &child: children) { - auto name = Glib::Markup::escape_text(child.get_value(column_record.name)); - auto path = child.get_value(column_record.path); - Gdk::RGBA *color; - if (status.modified.find(path.generic_string()) != status.modified.end()) - color = &yellow; - else if (status.added.find(path.generic_string()) != status.added.end()) - color = &green; - else - color = &normal_color; - - std::stringstream ss; - ss << '#' << std::setfill('0') << std::hex; - ss << std::setw(2) << std::hex << (color->get_red_u() >> 8); - ss << std::setw(2) << std::hex << (color->get_green_u() >> 8); - ss << std::setw(2) << std::hex << (color->get_blue_u() >> 8); - child.set_value(column_record.markup, - "<span foreground=\"" + ss.str() + "\">" + name + "</span>"); - - auto type = child.get_value(column_record.type); - if (type == PathType::UNKNOWN) - child.set_value(column_record.markup, - "<i>" + child.get_value(column_record.markup) + "</i>"); - } - - if (!include_parent_paths) - break; - - auto path = boost::filesystem::path(it->first); - if (boost::filesystem::exists(path / ".git")) - break; - if (path == path.root_directory()) - break; - auto parent_path = boost::filesystem::path(it->first).parent_path(); - it = directories.find(parent_path.string()); - } while (it != directories.end()); - }); - }); - git_status_thread.detach(); - } + for (auto &child: children) { + auto name = Glib::Markup::escape_text(child.get_value(column_record.name)); + auto path = child.get_value(column_record.path); + Gdk::RGBA *color; + if (status.modified.find(path.generic_string()) != status.modified.end()) + color = &yellow; + else if (status.added.find(path.generic_string()) != status.added.end()) + color = &green; + else + color = &normal_color; + + std::stringstream ss; + ss << '#' << std::setfill('0') << std::hex; + ss << std::setw(2) << std::hex << (color->get_red_u() >> 8); + ss << std::setw(2) << std::hex << (color->get_green_u() >> 8); + ss << std::setw(2) << std::hex << (color->get_blue_u() >> 8); + child.set_value(column_record.markup, + "<span foreground=\"" + ss.str() + "\">" + name + "</span>"); + + auto type = child.get_value(column_record.type); + if (type == PathType::UNKNOWN) + child.set_value(column_record.markup, + "<i>" + child.get_value(column_record.markup) + "</i>"); + } + + if (!include_parent_paths) + break; + + auto path = boost::filesystem::path(it->first); + if (boost::filesystem::exists(path / ".git")) + break; + if (path == path.root_directory()) + break; + auto parent_path = boost::filesystem::path(it->first).parent_path(); + it = directories.find(parent_path.string()); + } while (it != directories.end()); + }); + }); + git_status_thread.detach(); + } } diff --git a/src/directories.h b/src/directories.h index 6caaea48..1d5fb428 100644 --- a/src/directories.h +++ b/src/directories.h @@ -13,96 +13,96 @@ #include "dispatcher.h" class Directories : public Gtk::ListViewText { - class DirectoryData { - public: - Gtk::TreeModel::Row row; - Glib::RefPtr<Gio::FileMonitor> monitor; - std::shared_ptr<Git::Repository> repository; - std::shared_ptr<sigc::connection> connection; - }; + class DirectoryData { + public: + Gtk::TreeModel::Row row; + Glib::RefPtr<Gio::FileMonitor> monitor; + std::shared_ptr<Git::Repository> repository; + std::shared_ptr<sigc::connection> connection; + }; - enum class PathType { - KNOWN, UNKNOWN - }; + enum class PathType { + KNOWN, UNKNOWN + }; - class TreeStore : public Gtk::TreeStore { - protected: - TreeStore() {} + class TreeStore : public Gtk::TreeStore { + protected: + TreeStore() {} - bool row_drop_possible_vfunc(const Gtk::TreeModel::Path &path, - const Gtk::SelectionData &selection_data) const override; + bool row_drop_possible_vfunc(const Gtk::TreeModel::Path &path, + const Gtk::SelectionData &selection_data) const override; - bool drag_data_received_vfunc(const TreeModel::Path &path, const Gtk::SelectionData &selection_data) override; + bool drag_data_received_vfunc(const TreeModel::Path &path, const Gtk::SelectionData &selection_data) override; - bool drag_data_delete_vfunc(const Gtk::TreeModel::Path &path) override; + bool drag_data_delete_vfunc(const Gtk::TreeModel::Path &path) override; + public: + class ColumnRecord : public Gtk::TreeModel::ColumnRecord { public: - class ColumnRecord : public Gtk::TreeModel::ColumnRecord { - public: - ColumnRecord() { - add(id); - add(name); - add(markup); - add(path); - add(type); - } - - Gtk::TreeModelColumn<std::string> id; - Gtk::TreeModelColumn<std::string> name; - Gtk::TreeModelColumn<Glib::ustring> markup; - Gtk::TreeModelColumn<boost::filesystem::path> path; - Gtk::TreeModelColumn<PathType> type; - }; - - static Glib::RefPtr<TreeStore> create() { return Glib::RefPtr<TreeStore>(new TreeStore()); } + ColumnRecord() { + add(id); + add(name); + add(markup); + add(path); + add(type); + } + + Gtk::TreeModelColumn<std::string> id; + Gtk::TreeModelColumn<std::string> name; + Gtk::TreeModelColumn<Glib::ustring> markup; + Gtk::TreeModelColumn<boost::filesystem::path> path; + Gtk::TreeModelColumn<PathType> type; }; - Directories(); + static Glib::RefPtr<TreeStore> create() { return Glib::RefPtr<TreeStore>(new TreeStore()); } + }; + + Directories(); public: - static Directories &get() { - static Directories singleton; - return singleton; - } + static Directories &get() { + static Directories singleton; + return singleton; + } - ~Directories(); + ~Directories(); - void open(const boost::filesystem::path &dir_path = ""); + void open(const boost::filesystem::path &dir_path = ""); - void update(); + void update(); - void on_save_file(boost::filesystem::path file_path); + void on_save_file(boost::filesystem::path file_path); - void select(const boost::filesystem::path &path); + void select(const boost::filesystem::path &path); - boost::filesystem::path path; + boost::filesystem::path path; protected: - bool on_button_press_event(GdkEventButton *event) override; + bool on_button_press_event(GdkEventButton *event) override; private: - void add_or_update_path(const boost::filesystem::path &dir_path, const Gtk::TreeModel::Row &row, - bool include_parent_paths); + void add_or_update_path(const boost::filesystem::path &dir_path, const Gtk::TreeModel::Row &row, + bool include_parent_paths); - void remove_path(const boost::filesystem::path &dir_path); + void remove_path(const boost::filesystem::path &dir_path); - void colorize_path(const boost::filesystem::path &dir_path, bool include_parent_paths); + void colorize_path(const boost::filesystem::path &dir_path, bool include_parent_paths); - Glib::RefPtr<Gtk::TreeStore> tree_store; - TreeStore::ColumnRecord column_record; + Glib::RefPtr<Gtk::TreeStore> tree_store; + TreeStore::ColumnRecord column_record; - std::unordered_map<std::string, DirectoryData> directories; + std::unordered_map<std::string, DirectoryData> directories; - Dispatcher dispatcher; + Dispatcher dispatcher; - Gtk::Menu menu; - Gtk::MenuItem menu_item_new_file; - Gtk::MenuItem menu_item_new_folder; - Gtk::SeparatorMenuItem menu_item_separator; - Gtk::MenuItem menu_item_rename; - Gtk::MenuItem menu_item_delete; - Gtk::Menu menu_root; - Gtk::MenuItem menu_root_item_new_file; - Gtk::MenuItem menu_root_item_new_folder; - boost::filesystem::path menu_popup_row_path; + Gtk::Menu menu; + Gtk::MenuItem menu_item_new_file; + Gtk::MenuItem menu_item_new_folder; + Gtk::SeparatorMenuItem menu_item_separator; + Gtk::MenuItem menu_item_rename; + Gtk::MenuItem menu_item_delete; + Gtk::Menu menu_root; + Gtk::MenuItem menu_root_item_new_file; + Gtk::MenuItem menu_root_item_new_folder; + boost::filesystem::path menu_popup_row_path; }; diff --git a/src/dispatcher.cc b/src/dispatcher.cc index 1a0651de..e4e7f1f4 100644 --- a/src/dispatcher.cc +++ b/src/dispatcher.cc @@ -2,30 +2,30 @@ #include <vector> Dispatcher::Dispatcher() { - connection = dispatcher.connect([this] { - std::vector<std::list<std::function<void()>>::iterator> its; - { - std::unique_lock<std::mutex> lock(functions_mutex); - if (functions.empty()) - return; - its.reserve(functions.size()); - for (auto it = functions.begin(); it != functions.end(); ++it) - its.emplace_back(it); - } - for (auto &it: its) - (*it)(); - { - std::unique_lock<std::mutex> lock(functions_mutex); - for (auto &it: its) - functions.erase(it); - } - }); + connection = dispatcher.connect([this] { + std::vector<std::list<std::function<void()>>::iterator> its; + { + std::unique_lock<std::mutex> lock(functions_mutex); + if (functions.empty()) + return; + its.reserve(functions.size()); + for (auto it = functions.begin(); it != functions.end(); ++it) + its.emplace_back(it); + } + for (auto &it: its) + (*it)(); + { + std::unique_lock<std::mutex> lock(functions_mutex); + for (auto &it: its) + functions.erase(it); + } + }); } Dispatcher::~Dispatcher() { - disconnect(); + disconnect(); } void Dispatcher::disconnect() { - connection.disconnect(); + connection.disconnect(); } diff --git a/src/dispatcher.h b/src/dispatcher.h index 6fbab177..99302cf4 100644 --- a/src/dispatcher.h +++ b/src/dispatcher.h @@ -7,23 +7,23 @@ class Dispatcher { private: - std::list<std::function<void()>> functions; - std::mutex functions_mutex; - Glib::Dispatcher dispatcher; - sigc::connection connection; + std::list<std::function<void()>> functions; + std::mutex functions_mutex; + Glib::Dispatcher dispatcher; + sigc::connection connection; public: - Dispatcher(); + Dispatcher(); - ~Dispatcher(); + ~Dispatcher(); - template<typename T> - void post(T &&function) { - { - std::unique_lock<std::mutex> lock(functions_mutex); - functions.emplace_back(std::forward<T>(function)); - } - dispatcher(); + template<typename T> + void post(T &&function) { + { + std::unique_lock<std::mutex> lock(functions_mutex); + functions.emplace_back(std::forward<T>(function)); } + dispatcher(); + } - void disconnect(); + void disconnect(); }; diff --git a/src/documentation_cppreference.cc b/src/documentation_cppreference.cc index 7dc5fbe0..74019264 100644 --- a/src/documentation_cppreference.cc +++ b/src/documentation_cppreference.cc @@ -4,7 +4,7 @@ std::string Documentation::CppReference::get_url(const std::string symbol) noexcept { // Copied from http://upload.cppreference.com/mwiki/images/d/df/html_book_20170409.zip/reference/cppreference-export-ns0,4,8,10.xml // Using raw string instead of map to reduce compile time -const static std::string symbol_urls = R"(size_t c/types/size_t + const static std::string symbol_urls = R"(size_t c/types/size_t ptrdiff_t c/types/ptrdiff_t nullptr_t c/types/nullptr_t NULL c/types/NULL @@ -12174,33 +12174,34 @@ std::experimental::filesystem::is_symlink cpp/experimental/fs/is_symlink std::experimental::filesystem::status_known cpp/experimental/fs/status_known"} )"; -class SymbolToUrl { -public: -SymbolToUrl(const std::string &symbol_urls) { -size_t symbol_start=0; -size_t symbol_end=std::string::npos; -size_t url_start=std::string::npos; -for (size_t c=0;c<symbol_urls.size();++c) { -auto &chr=symbol_urls[c]; -if (chr=='\t') { -symbol_end=c; -url_start=c+1; -} -else if (chr=='\n') { -if (symbol_end!=std::string::npos && url_start!=std::string::npos) -map.emplace(symbol_urls.substr(symbol_start, symbol_end-symbol_start), symbol_urls.substr(url_start, c-url_start)); -symbol_start=c+1; -symbol_end=std::string::npos; -url_start=std::string::npos; -} -} -} -std::unordered_map<std::string, std::string> map; -}; + class SymbolToUrl { + public: + SymbolToUrl(const std::string &symbol_urls) { + size_t symbol_start = 0; + size_t symbol_end = std::string::npos; + size_t url_start = std::string::npos; + for (size_t c = 0; c < symbol_urls.size(); ++c) { + auto &chr = symbol_urls[c]; + if (chr == '\t') { + symbol_end = c; + url_start = c + 1; + } else if (chr == '\n') { + if (symbol_end != std::string::npos && url_start != std::string::npos) + map.emplace(symbol_urls.substr(symbol_start, symbol_end - symbol_start), + symbol_urls.substr(url_start, c - url_start)); + symbol_start = c + 1; + symbol_end = std::string::npos; + url_start = std::string::npos; + } + } + } + + std::unordered_map<std::string, std::string> map; + }; -static SymbolToUrl symbol_to_url(symbol_urls); -auto it=symbol_to_url.map.find(symbol); -if (it==symbol_to_url.map.end()) -return std::string(); -return "http://en.cppreference.com/w/"+it->second; + static SymbolToUrl symbol_to_url(symbol_urls); + auto it = symbol_to_url.map.find(symbol); + if (it == symbol_to_url.map.end()) + return std::string(); + return "http://en.cppreference.com/w/" + it->second; } diff --git a/src/documentation_cppreference.h b/src/documentation_cppreference.h index db9bf26e..b8bfad34 100644 --- a/src/documentation_cppreference.h +++ b/src/documentation_cppreference.h @@ -3,8 +3,8 @@ #include <string> namespace Documentation { - class CppReference { - public: - static std::string get_url(const std::string symbol) noexcept; - }; + class CppReference { + public: + static std::string get_url(const std::string symbol) noexcept; + }; } diff --git a/src/entrybox.cc b/src/entrybox.cc index 5a81ef1e..635537a5 100644 --- a/src/entrybox.cc +++ b/src/entrybox.cc @@ -4,99 +4,99 @@ std::unordered_map<std::string, std::vector<std::string> > EntryBox::entry_histo EntryBox::Entry::Entry(const std::string &content, std::function<void(const std::string &content)> on_activate, unsigned width_chars) : Gtk::Entry(), on_activate(on_activate) { - set_max_length(0); - set_width_chars(width_chars); - set_text(content); - selected_history = 0; - signal_activate().connect([this]() { - if (this->on_activate) { - auto &history = EntryBox::entry_histories[get_placeholder_text()]; - auto text = get_text(); - if (history.size() == 0 || (history.size() > 0 && *history.begin() != text)) - history.emplace(history.begin(), text); - selected_history = 0; - this->on_activate(text); - } - }); - signal_key_press_event().connect([this](GdkEventKey *key) { - if (key->keyval == GDK_KEY_Up || key->keyval == GDK_KEY_KP_Up) { - auto &history = entry_histories[get_placeholder_text()]; - if (history.size() > 0) { - selected_history++; - if (selected_history >= history.size()) - selected_history = history.size() - 1; - set_text(history[selected_history]); - set_position(-1); - } - } - if (key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down) { - auto &history = entry_histories[get_placeholder_text()]; - if (history.size() > 0) { - if (selected_history != 0) - selected_history--; - set_text(history[selected_history]); - set_position(-1); - } - } - return false; - }); + set_max_length(0); + set_width_chars(width_chars); + set_text(content); + selected_history = 0; + signal_activate().connect([this]() { + if (this->on_activate) { + auto &history = EntryBox::entry_histories[get_placeholder_text()]; + auto text = get_text(); + if (history.size() == 0 || (history.size() > 0 && *history.begin() != text)) + history.emplace(history.begin(), text); + selected_history = 0; + this->on_activate(text); + } + }); + signal_key_press_event().connect([this](GdkEventKey *key) { + if (key->keyval == GDK_KEY_Up || key->keyval == GDK_KEY_KP_Up) { + auto &history = entry_histories[get_placeholder_text()]; + if (history.size() > 0) { + selected_history++; + if (selected_history >= history.size()) + selected_history = history.size() - 1; + set_text(history[selected_history]); + set_position(-1); + } + } + if (key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down) { + auto &history = entry_histories[get_placeholder_text()]; + if (history.size() > 0) { + if (selected_history != 0) + selected_history--; + set_text(history[selected_history]); + set_position(-1); + } + } + return false; + }); } EntryBox::Button::Button(const std::string &label, std::function<void()> on_activate) : Gtk::Button(label), on_activate(on_activate) { - set_focus_on_click(false); - signal_clicked().connect([this]() { - if (this->on_activate) - this->on_activate(); - }); + set_focus_on_click(false); + signal_clicked().connect([this]() { + if (this->on_activate) + this->on_activate(); + }); } EntryBox::ToggleButton::ToggleButton(const std::string &label, std::function<void()> on_activate) : Gtk::ToggleButton( - label), on_activate(on_activate) { - set_focus_on_click(false); - signal_clicked().connect([this]() { - if (this->on_activate) - this->on_activate(); - }); + label), on_activate(on_activate) { + set_focus_on_click(false); + signal_clicked().connect([this]() { + if (this->on_activate) + this->on_activate(); + }); } EntryBox::Label::Label(std::function<void(int state, const std::string &message)> update) - : Gtk::Label(), update(update) { - if (this->update) - this->update(-1, ""); + : Gtk::Label(), update(update) { + if (this->update) + this->update(-1, ""); } EntryBox::EntryBox() : Gtk::Box(Gtk::ORIENTATION_VERTICAL), upper_box(Gtk::ORIENTATION_HORIZONTAL), lower_box(Gtk::ORIENTATION_HORIZONTAL) { - pack_start(upper_box, Gtk::PACK_SHRINK); - pack_start(lower_box, Gtk::PACK_SHRINK); - this->set_focus_chain({&lower_box}); + pack_start(upper_box, Gtk::PACK_SHRINK); + pack_start(lower_box, Gtk::PACK_SHRINK); + this->set_focus_chain({&lower_box}); } void EntryBox::clear() { - Gtk::Box::hide(); - entries.clear(); - buttons.clear(); - toggle_buttons.clear(); - labels.clear(); + Gtk::Box::hide(); + entries.clear(); + buttons.clear(); + toggle_buttons.clear(); + labels.clear(); } void EntryBox::show() { - std::vector<Gtk::Widget *> focus_chain; - for (auto &entry: entries) { - lower_box.pack_start(entry, Gtk::PACK_SHRINK); - focus_chain.emplace_back(&entry); - } - for (auto &button: buttons) - lower_box.pack_start(button, Gtk::PACK_SHRINK); - for (auto &toggle_button: toggle_buttons) - lower_box.pack_start(toggle_button, Gtk::PACK_SHRINK); - for (auto &label: labels) - upper_box.pack_start(label, Gtk::PACK_SHRINK); - lower_box.set_focus_chain(focus_chain); - show_all(); - if (entries.size() > 0) { - entries.begin()->grab_focus(); - entries.begin()->select_region(0, entries.begin()->get_text_length()); - } + std::vector<Gtk::Widget *> focus_chain; + for (auto &entry: entries) { + lower_box.pack_start(entry, Gtk::PACK_SHRINK); + focus_chain.emplace_back(&entry); + } + for (auto &button: buttons) + lower_box.pack_start(button, Gtk::PACK_SHRINK); + for (auto &toggle_button: toggle_buttons) + lower_box.pack_start(toggle_button, Gtk::PACK_SHRINK); + for (auto &label: labels) + upper_box.pack_start(label, Gtk::PACK_SHRINK); + lower_box.set_focus_chain(focus_chain); + show_all(); + if (entries.size() > 0) { + entries.begin()->grab_focus(); + entries.begin()->select_region(0, entries.begin()->get_text_length()); + } } diff --git a/src/entrybox.h b/src/entrybox.h index 107ba085..54d8e59f 100644 --- a/src/entrybox.h +++ b/src/entrybox.h @@ -9,60 +9,60 @@ class EntryBox : public Gtk::Box { public: - class Entry : public Gtk::Entry { - public: - Entry(const std::string &content = "", std::function<void(const std::string &content)> on_activate = nullptr, - unsigned width_chars = -1); + class Entry : public Gtk::Entry { + public: + Entry(const std::string &content = "", std::function<void(const std::string &content)> on_activate = nullptr, + unsigned width_chars = -1); - std::function<void(const std::string &content)> on_activate; - private: - size_t selected_history; - }; + std::function<void(const std::string &content)> on_activate; + private: + size_t selected_history; + }; - class Button : public Gtk::Button { - public: - Button(const std::string &label, std::function<void()> on_activate = nullptr); + class Button : public Gtk::Button { + public: + Button(const std::string &label, std::function<void()> on_activate = nullptr); - std::function<void()> on_activate; - }; + std::function<void()> on_activate; + }; - class ToggleButton : public Gtk::ToggleButton { - public: - ToggleButton(const std::string &label, std::function<void()> on_activate = nullptr); + class ToggleButton : public Gtk::ToggleButton { + public: + ToggleButton(const std::string &label, std::function<void()> on_activate = nullptr); - std::function<void()> on_activate; - }; + std::function<void()> on_activate; + }; - class Label : public Gtk::Label { - public: - Label(std::function<void(int state, const std::string &message)> update = nullptr); + class Label : public Gtk::Label { + public: + Label(std::function<void(int state, const std::string &message)> update = nullptr); - std::function<void(int state, const std::string &message)> update; - }; + std::function<void(int state, const std::string &message)> update; + }; private: - EntryBox(); + EntryBox(); public: - static EntryBox &get() { - static EntryBox singleton; - return singleton; - } + static EntryBox &get() { + static EntryBox singleton; + return singleton; + } - Gtk::Box upper_box; - Gtk::Box lower_box; + Gtk::Box upper_box; + Gtk::Box lower_box; - void clear(); + void clear(); - void hide() { clear(); } + void hide() { clear(); } - void show(); + void show(); - std::list<Entry> entries; - std::list<Button> buttons; - std::list<ToggleButton> toggle_buttons; - std::list<Label> labels; + std::list<Entry> entries; + std::list<Button> buttons; + std::list<ToggleButton> toggle_buttons; + std::list<Label> labels; private: - static std::unordered_map<std::string, std::vector<std::string> > entry_histories; + static std::unordered_map<std::string, std::vector<std::string> > entry_histories; }; diff --git a/src/filesystem.cc b/src/filesystem.cc index a7289137..475423c5 100644 --- a/src/filesystem.cc +++ b/src/filesystem.cc @@ -7,243 +7,243 @@ //Only use on small files std::string filesystem::read(const std::string &path) { - std::stringstream ss; - std::ifstream input(path, std::ofstream::binary); - if (input) { - ss << input.rdbuf(); - input.close(); - } - return ss.str(); + std::stringstream ss; + std::ifstream input(path, std::ofstream::binary); + if (input) { + ss << input.rdbuf(); + input.close(); + } + return ss.str(); } //Only use on small files std::vector<std::string> filesystem::read_lines(const std::string &path) { - std::vector<std::string> res; - std::ifstream input(path, std::ofstream::binary); - if (input) { - do { res.emplace_back(); } while (getline(input, res.back())); - } - input.close(); - return res; + std::vector<std::string> res; + std::ifstream input(path, std::ofstream::binary); + if (input) { + do { res.emplace_back(); } while (getline(input, res.back())); + } + input.close(); + return res; } //Only use on small files bool filesystem::write(const std::string &path, const std::string &new_content) { - std::ofstream output(path, std::ofstream::binary); - if (output) - output << new_content; - else - return false; - output.close(); - return true; + std::ofstream output(path, std::ofstream::binary); + if (output) + output << new_content; + else + return false; + output.close(); + return true; } std::string filesystem::escape_argument(const std::string &argument) { - auto escaped = argument; - for (size_t pos = 0; pos < escaped.size(); ++pos) { - if (escaped[pos] == ' ' || escaped[pos] == '(' || escaped[pos] == ')' || escaped[pos] == '\'' || - escaped[pos] == '"') { - escaped.insert(pos, "\\"); - ++pos; - } + auto escaped = argument; + for (size_t pos = 0; pos < escaped.size(); ++pos) { + if (escaped[pos] == ' ' || escaped[pos] == '(' || escaped[pos] == ')' || escaped[pos] == '\'' || + escaped[pos] == '"') { + escaped.insert(pos, "\\"); + ++pos; } - return escaped; + } + return escaped; } std::string filesystem::unescape_argument(const std::string &argument) { - auto unescaped = argument; - - if (unescaped.size() >= 2) { - if ((unescaped[0] == '\'' && unescaped[unescaped.size() - 1] == '\'') || - (unescaped[0] == '"' && unescaped[unescaped.size() - 1] == '"')) { - char quotation_mark = unescaped[0]; - unescaped = unescaped.substr(1, unescaped.size() - 2); - size_t backslash_count = 0; - for (size_t pos = 0; pos < unescaped.size(); ++pos) { - if (backslash_count % 2 == 1 && (unescaped[pos] == '\\' || unescaped[pos] == quotation_mark)) { - unescaped.erase(pos - 1, 1); - --pos; - backslash_count = 0; - } else if (unescaped[pos] == '\\') - ++backslash_count; - else - backslash_count = 0; - } - return unescaped; - } - } - - size_t backslash_count = 0; - for (size_t pos = 0; pos < unescaped.size(); ++pos) { - if (backslash_count % 2 == 1 && - (unescaped[pos] == '\\' || unescaped[pos] == ' ' || unescaped[pos] == '(' || unescaped[pos] == ')' || - unescaped[pos] == '\'' || unescaped[pos] == '"')) { - unescaped.erase(pos - 1, 1); - --pos; - backslash_count = 0; + auto unescaped = argument; + + if (unescaped.size() >= 2) { + if ((unescaped[0] == '\'' && unescaped[unescaped.size() - 1] == '\'') || + (unescaped[0] == '"' && unescaped[unescaped.size() - 1] == '"')) { + char quotation_mark = unescaped[0]; + unescaped = unescaped.substr(1, unescaped.size() - 2); + size_t backslash_count = 0; + for (size_t pos = 0; pos < unescaped.size(); ++pos) { + if (backslash_count % 2 == 1 && (unescaped[pos] == '\\' || unescaped[pos] == quotation_mark)) { + unescaped.erase(pos - 1, 1); + --pos; + backslash_count = 0; } else if (unescaped[pos] == '\\') - ++backslash_count; + ++backslash_count; else - backslash_count = 0; + backslash_count = 0; + } + return unescaped; } - return unescaped; + } + + size_t backslash_count = 0; + for (size_t pos = 0; pos < unescaped.size(); ++pos) { + if (backslash_count % 2 == 1 && + (unescaped[pos] == '\\' || unescaped[pos] == ' ' || unescaped[pos] == '(' || unescaped[pos] == ')' || + unescaped[pos] == '\'' || unescaped[pos] == '"')) { + unescaped.erase(pos - 1, 1); + --pos; + backslash_count = 0; + } else if (unescaped[pos] == '\\') + ++backslash_count; + else + backslash_count = 0; + } + return unescaped; } boost::filesystem::path filesystem::get_home_path() noexcept { - std::vector<std::string> environment_variables = {"HOME", "AppData"}; - char *ptr = nullptr; - for (auto &variable : environment_variables) { - ptr = std::getenv(variable.c_str()); - boost::system::error_code ec; - if (ptr != nullptr && boost::filesystem::exists(ptr, ec)) - return ptr; - } - return boost::filesystem::path(); + std::vector<std::string> environment_variables = {"HOME", "AppData"}; + char *ptr = nullptr; + for (auto &variable : environment_variables) { + ptr = std::getenv(variable.c_str()); + boost::system::error_code ec; + if (ptr != nullptr && boost::filesystem::exists(ptr, ec)) + return ptr; + } + return boost::filesystem::path(); } boost::filesystem::path filesystem::get_short_path(const boost::filesystem::path &path) noexcept { #ifdef _WIN32 - return path; + return path; #else - static auto home_path=get_home_path(); - if(!home_path.empty()) { - auto relative_path=filesystem::get_relative_path(path, home_path); - if(!relative_path.empty()) - return "~"/relative_path; - } - return path; + static auto home_path=get_home_path(); + if(!home_path.empty()) { + auto relative_path=filesystem::get_relative_path(path, home_path); + if(!relative_path.empty()) + return "~"/relative_path; + } + return path; #endif } bool filesystem::file_in_path(const boost::filesystem::path &file_path, const boost::filesystem::path &path) { - if (std::distance(file_path.begin(), file_path.end()) < std::distance(path.begin(), path.end())) - return false; - return std::equal(path.begin(), path.end(), file_path.begin()); + if (std::distance(file_path.begin(), file_path.end()) < std::distance(path.begin(), path.end())) + return false; + return std::equal(path.begin(), path.end(), file_path.begin()); } boost::filesystem::path filesystem::find_file_in_path_parents(const std::string &file_name, const boost::filesystem::path &path) { - auto current_path = path; - while (true) { - auto test_path = current_path / file_name; - if (boost::filesystem::exists(test_path)) - return test_path; - if (current_path == current_path.root_directory()) - return boost::filesystem::path(); - current_path = current_path.parent_path(); - } + auto current_path = path; + while (true) { + auto test_path = current_path / file_name; + if (boost::filesystem::exists(test_path)) + return test_path; + if (current_path == current_path.root_directory()) + return boost::filesystem::path(); + current_path = current_path.parent_path(); + } } boost::filesystem::path filesystem::get_normal_path(const boost::filesystem::path &path) noexcept { - boost::filesystem::path normal_path; - - for (auto &e: path) { - if (e == ".") - continue; - else if (e == "..") { - auto parent_path = normal_path.parent_path(); - if (!parent_path.empty()) - normal_path = parent_path; - else - normal_path /= e; - } else if (e.empty()) - continue; - else - normal_path /= e; - } + boost::filesystem::path normal_path; + + for (auto &e: path) { + if (e == ".") + continue; + else if (e == "..") { + auto parent_path = normal_path.parent_path(); + if (!parent_path.empty()) + normal_path = parent_path; + else + normal_path /= e; + } else if (e.empty()) + continue; + else + normal_path /= e; + } - return normal_path; + return normal_path; } boost::filesystem::path filesystem::get_relative_path(const boost::filesystem::path &path, const boost::filesystem::path &base) noexcept { - boost::filesystem::path relative_path; - - if (std::distance(path.begin(), path.end()) < std::distance(base.begin(), base.end())) - return boost::filesystem::path(); - - auto base_it = base.begin(); - auto path_it = path.begin(); - while (path_it != path.end() && base_it != base.end()) { - if (*path_it != *base_it) - return boost::filesystem::path(); - ++path_it; - ++base_it; - } - for (; path_it != path.end(); ++path_it) - relative_path /= *path_it; + boost::filesystem::path relative_path; - return relative_path; + if (std::distance(path.begin(), path.end()) < std::distance(base.begin(), base.end())) + return boost::filesystem::path(); + + auto base_it = base.begin(); + auto path_it = path.begin(); + while (path_it != path.end() && base_it != base.end()) { + if (*path_it != *base_it) + return boost::filesystem::path(); + ++path_it; + ++base_it; + } + for (; path_it != path.end(); ++path_it) + relative_path /= *path_it; + + return relative_path; } boost::filesystem::path filesystem::get_executable(const boost::filesystem::path &executable_name) noexcept { #if defined(__APPLE__) || defined(_WIN32) - return executable_name; + return executable_name; #endif - static std::vector<boost::filesystem::path> bin_paths = {"/usr/bin", "/usr/local/bin"}; + static std::vector<boost::filesystem::path> bin_paths = {"/usr/bin", "/usr/local/bin"}; - try { - for (auto &path: bin_paths) { - if (boost::filesystem::exists(path / executable_name)) - return executable_name; - } + try { + for (auto &path: bin_paths) { + if (boost::filesystem::exists(path / executable_name)) + return executable_name; + } - auto executable_name_str = executable_name.string(); - for (auto &path: bin_paths) { - boost::filesystem::path executable; - for (boost::filesystem::directory_iterator it(path), end; it != end; ++it) { - auto it_path = it->path(); - auto it_path_filename_str = it_path.filename().string(); - if (!it_path_filename_str.empty() && - it_path_filename_str.compare(0, executable_name_str.size(), executable_name_str) == 0) { - if (it_path > executable && - ((it_path_filename_str.size() > executable_name_str.size() && - it_path_filename_str[executable_name_str.size()] >= '0' && - it_path_filename_str[executable_name_str.size()] <= '9') || - (it_path_filename_str.size() > executable_name_str.size() + 1 && - it_path_filename_str[executable_name_str.size()] == '-' && - it_path_filename_str[executable_name_str.size() + 1] >= '0' && - it_path_filename_str[executable_name_str.size() + 1] <= '9')) && - !boost::filesystem::is_directory(it_path)) - executable = it_path; - } - } - if (!executable.empty()) - return executable; + auto executable_name_str = executable_name.string(); + for (auto &path: bin_paths) { + boost::filesystem::path executable; + for (boost::filesystem::directory_iterator it(path), end; it != end; ++it) { + auto it_path = it->path(); + auto it_path_filename_str = it_path.filename().string(); + if (!it_path_filename_str.empty() && + it_path_filename_str.compare(0, executable_name_str.size(), executable_name_str) == 0) { + if (it_path > executable && + ((it_path_filename_str.size() > executable_name_str.size() && + it_path_filename_str[executable_name_str.size()] >= '0' && + it_path_filename_str[executable_name_str.size()] <= '9') || + (it_path_filename_str.size() > executable_name_str.size() + 1 && + it_path_filename_str[executable_name_str.size()] == '-' && + it_path_filename_str[executable_name_str.size() + 1] >= '0' && + it_path_filename_str[executable_name_str.size() + 1] <= '9')) && + !boost::filesystem::is_directory(it_path)) + executable = it_path; } + } + if (!executable.empty()) + return executable; } - catch (...) {} + } + catch (...) {} - return executable_name; + return executable_name; } // Based on https://stackoverflow.com/a/11295568 const std::vector<boost::filesystem::path> &filesystem::get_executable_search_paths() { - static std::vector<boost::filesystem::path> result; - if (!result.empty()) - return result; - - const std::string env = getenv("PATH"); - const char delimiter = ':'; - - size_t previous = 0; - size_t pos; - while ((pos = env.find(delimiter, previous)) != std::string::npos) { - result.emplace_back(env.substr(previous, pos - previous)); - previous = pos + 1; - } - result.emplace_back(env.substr(previous)); - + static std::vector<boost::filesystem::path> result; + if (!result.empty()) return result; + + const std::string env = getenv("PATH"); + const char delimiter = ':'; + + size_t previous = 0; + size_t pos; + while ((pos = env.find(delimiter, previous)) != std::string::npos) { + result.emplace_back(env.substr(previous, pos - previous)); + previous = pos + 1; + } + result.emplace_back(env.substr(previous)); + + return result; } boost::filesystem::path filesystem::find_executable(const std::string &executable_name) { - for (auto &path: get_executable_search_paths()) { - boost::system::error_code ec; - auto executable_path = path / executable_name; - if (boost::filesystem::exists(executable_path, ec)) - return executable_path; - } - return boost::filesystem::path(); + for (auto &path: get_executable_search_paths()) { + boost::system::error_code ec; + auto executable_path = path / executable_name; + if (boost::filesystem::exists(executable_path, ec)) + return executable_path; + } + return boost::filesystem::path(); } diff --git a/src/filesystem.h b/src/filesystem.h index e32a18a0..14cec226 100644 --- a/src/filesystem.h +++ b/src/filesystem.h @@ -6,50 +6,50 @@ class filesystem { public: - static std::string read(const std::string &path); + static std::string read(const std::string &path); - static std::string read(const boost::filesystem::path &path) { return read(path.string()); } + static std::string read(const boost::filesystem::path &path) { return read(path.string()); } - static std::vector<std::string> read_lines(const std::string &path); + static std::vector<std::string> read_lines(const std::string &path); - static std::vector<std::string> read_lines(const boost::filesystem::path &path) { - return read_lines(path.string()); - }; + static std::vector<std::string> read_lines(const boost::filesystem::path &path) { + return read_lines(path.string()); + }; - static bool write(const std::string &path, const std::string &new_content); + static bool write(const std::string &path, const std::string &new_content); - static bool write(const boost::filesystem::path &path, const std::string &new_content) { - return write(path.string(), new_content); - } + static bool write(const boost::filesystem::path &path, const std::string &new_content) { + return write(path.string(), new_content); + } - static bool write(const std::string &path) { return write(path, ""); }; + static bool write(const std::string &path) { return write(path, ""); }; - static bool write(const boost::filesystem::path &path) { return write(path, ""); }; + static bool write(const boost::filesystem::path &path) { return write(path, ""); }; - static std::string escape_argument(const std::string &argument); + static std::string escape_argument(const std::string &argument); - static std::string unescape_argument(const std::string &argument); + static std::string unescape_argument(const std::string &argument); - static boost::filesystem::path get_home_path() noexcept; + static boost::filesystem::path get_home_path() noexcept; - static boost::filesystem::path get_short_path(const boost::filesystem::path &path) noexcept; + static boost::filesystem::path get_short_path(const boost::filesystem::path &path) noexcept; - static bool file_in_path(const boost::filesystem::path &file_path, const boost::filesystem::path &path); + static bool file_in_path(const boost::filesystem::path &file_path, const boost::filesystem::path &path); - static boost::filesystem::path - find_file_in_path_parents(const std::string &file_name, const boost::filesystem::path &path); + static boost::filesystem::path + find_file_in_path_parents(const std::string &file_name, const boost::filesystem::path &path); - /// Return path with dot, dot-dot and directory separator elements removed - static boost::filesystem::path get_normal_path(const boost::filesystem::path &path) noexcept; + /// Return path with dot, dot-dot and directory separator elements removed + static boost::filesystem::path get_normal_path(const boost::filesystem::path &path) noexcept; - /// Returns empty path on failure - static boost::filesystem::path - get_relative_path(const boost::filesystem::path &path, const boost::filesystem::path &base) noexcept; + /// Returns empty path on failure + static boost::filesystem::path + get_relative_path(const boost::filesystem::path &path, const boost::filesystem::path &base) noexcept; - /// Return executable with latest version in filename on systems that is lacking executable_name symbolic link - static boost::filesystem::path get_executable(const boost::filesystem::path &executable_name) noexcept; + /// Return executable with latest version in filename on systems that is lacking executable_name symbolic link + static boost::filesystem::path get_executable(const boost::filesystem::path &executable_name) noexcept; - static const std::vector<boost::filesystem::path> &get_executable_search_paths(); + static const std::vector<boost::filesystem::path> &get_executable_search_paths(); - static boost::filesystem::path find_executable(const std::string &executable_name); + static boost::filesystem::path find_executable(const std::string &executable_name); }; diff --git a/src/git.cc b/src/git.cc index 100ad9a1..ad4321f4 100644 --- a/src/git.cc +++ b/src/git.cc @@ -1,317 +1,316 @@ #include "git.h" -#include <cstring> bool Git::initialized = false; std::mutex Git::mutex; std::string Git::Error::message() noexcept { - const git_error *last_error = giterr_last(); - if (last_error == nullptr) - return std::string(); - else - return last_error->message; + const git_error *last_error = giterr_last(); + if (last_error == nullptr) + return std::string(); + else + return last_error->message; } Git::Repository::Diff::Diff(const boost::filesystem::path &path, git_repository *repository) : repository(repository) { - blob = std::shared_ptr<git_blob>(nullptr, [](git_blob *blob) { - if (blob) git_blob_free(blob); - }); - Error error; - std::lock_guard<std::mutex> lock(mutex); - auto spec = "HEAD:" + path.generic_string(); - error.code = git_revparse_single(reinterpret_cast<git_object **>(&blob), repository, spec.c_str()); - if (error) - throw std::runtime_error(error.message()); + blob = std::shared_ptr<git_blob>(nullptr, [](git_blob *blob) { + if (blob) git_blob_free(blob); + }); + Error error; + std::lock_guard<std::mutex> lock(mutex); + auto spec = "HEAD:" + path.generic_string(); + error.code = git_revparse_single(reinterpret_cast<git_object **>(&blob), repository, spec.c_str()); + if (error) + throw std::runtime_error(error.message()); - git_diff_init_options(&options, GIT_DIFF_OPTIONS_VERSION); - options.context_lines = 0; + git_diff_init_options(&options, GIT_DIFF_OPTIONS_VERSION); + options.context_lines = 0; } //Based on https://github.com/atom/git-diff/blob/master/lib/git-diff-view.coffee int Git::Repository::Diff::hunk_cb(const git_diff_delta *delta, const git_diff_hunk *hunk, void *payload) noexcept { - auto lines = static_cast<Lines *>(payload); - auto start = hunk->new_start - 1; - auto end = hunk->new_start + hunk->new_lines - 1; - if (hunk->old_lines == 0 && hunk->new_lines > 0) - lines->added.emplace_back(start, end); - else if (hunk->new_lines == 0 && hunk->old_lines > 0) - lines->removed.emplace_back(start); - else - lines->modified.emplace_back(start, end); + auto lines = static_cast<Lines *>(payload); + auto start = hunk->new_start - 1; + auto end = hunk->new_start + hunk->new_lines - 1; + if (hunk->old_lines == 0 && hunk->new_lines > 0) + lines->added.emplace_back(start, end); + else if (hunk->new_lines == 0 && hunk->old_lines > 0) + lines->removed.emplace_back(start); + else + lines->modified.emplace_back(start, end); - return 0; + return 0; } Git::Repository::Diff::Lines Git::Repository::Diff::get_lines(const std::string &buffer) { - Lines lines; - Error error; - std::lock_guard<std::mutex> lock(mutex); - error.code = git_diff_blob_to_buffer(blob.get(), nullptr, buffer.c_str(), buffer.size(), nullptr, &options, nullptr, - nullptr, hunk_cb, nullptr, &lines); - if (error) - throw std::runtime_error(error.message()); - return lines; + Lines lines; + Error error; + std::lock_guard<std::mutex> lock(mutex); + error.code = git_diff_blob_to_buffer(blob.get(), nullptr, buffer.c_str(), buffer.size(), nullptr, &options, nullptr, + nullptr, hunk_cb, nullptr, &lines); + if (error) + throw std::runtime_error(error.message()); + return lines; } std::vector<Git::Repository::Diff::Hunk> Git::Repository::Diff::get_hunks(const std::string &old_buffer, const std::string &new_buffer) { - std::vector<Git::Repository::Diff::Hunk> hunks; - Error error; - git_diff_options options; - git_diff_init_options(&options, GIT_DIFF_OPTIONS_VERSION); - options.context_lines = 0; - error.code = git_diff_buffers(old_buffer.c_str(), old_buffer.size(), nullptr, new_buffer.c_str(), new_buffer.size(), - nullptr, &options, nullptr, nullptr, - [](const git_diff_delta *delta, const git_diff_hunk *hunk, void *payload) { - auto hunks = static_cast<std::vector<Git::Repository::Diff::Hunk> *>(payload); - hunks->emplace_back(hunk->old_start, hunk->old_lines, hunk->new_start, - hunk->new_lines); - return 0; - }, nullptr, &hunks); - if (error) - throw std::runtime_error(error.message()); - return hunks; + std::vector<Git::Repository::Diff::Hunk> hunks; + Error error; + git_diff_options options; + git_diff_init_options(&options, GIT_DIFF_OPTIONS_VERSION); + options.context_lines = 0; + error.code = git_diff_buffers(old_buffer.c_str(), old_buffer.size(), nullptr, new_buffer.c_str(), new_buffer.size(), + nullptr, &options, nullptr, nullptr, + [](const git_diff_delta *delta, const git_diff_hunk *hunk, void *payload) { + auto hunks = static_cast<std::vector<Git::Repository::Diff::Hunk> *>(payload); + hunks->emplace_back(hunk->old_start, hunk->old_lines, hunk->new_start, + hunk->new_lines); + return 0; + }, nullptr, &hunks); + if (error) + throw std::runtime_error(error.message()); + return hunks; } int Git::Repository::Diff::line_cb(const git_diff_delta *delta, const git_diff_hunk *hunk, const git_diff_line *line, void *payload) noexcept { - auto details = static_cast<std::pair<std::string, int> *>(payload); - auto line_nr = details->second; - auto start = hunk->new_start - 1; - auto end = hunk->new_start + hunk->new_lines - 1; - if (line_nr == start || (line_nr >= start && line_nr < end)) { - if (details->first.empty()) - details->first += std::string(hunk->header, hunk->header_len); - details->first += line->origin + std::string(line->content, line->content_len); - } - return 0; + auto details = static_cast<std::pair<std::string, int> *>(payload); + auto line_nr = details->second; + auto start = hunk->new_start - 1; + auto end = hunk->new_start + hunk->new_lines - 1; + if (line_nr == start || (line_nr >= start && line_nr < end)) { + if (details->first.empty()) + details->first += std::string(hunk->header, hunk->header_len); + details->first += line->origin + std::string(line->content, line->content_len); + } + return 0; } std::string Git::Repository::Diff::get_details(const std::string &buffer, int line_nr) { - std::pair<std::string, int> details; - details.second = line_nr; - Error error; - std::lock_guard<std::mutex> lock(mutex); - error.code = git_diff_blob_to_buffer(blob.get(), nullptr, buffer.c_str(), buffer.size(), nullptr, &options, nullptr, - nullptr, nullptr, line_cb, &details); - if (error) - throw std::runtime_error(error.message()); - return details.first; + std::pair<std::string, int> details; + details.second = line_nr; + Error error; + std::lock_guard<std::mutex> lock(mutex); + error.code = git_diff_blob_to_buffer(blob.get(), nullptr, buffer.c_str(), buffer.size(), nullptr, &options, nullptr, + nullptr, nullptr, line_cb, &details); + if (error) + throw std::runtime_error(error.message()); + return details.first; } Git::Repository::Repository(const boost::filesystem::path &path) { - git_repository *repository_ptr; - { - Error error; - std::lock_guard<std::mutex> lock(mutex); - auto path_str = path.generic_string(); - error.code = git_repository_open_ext(&repository_ptr, path_str.c_str(), 0, nullptr); - if (error) - throw std::runtime_error(error.message()); - } - repository = std::unique_ptr<git_repository, std::function<void(git_repository *)> >(repository_ptr, - [](git_repository *ptr) { - git_repository_free(ptr); - }); + git_repository *repository_ptr; + { + Error error; + std::lock_guard<std::mutex> lock(mutex); + auto path_str = path.generic_string(); + error.code = git_repository_open_ext(&repository_ptr, path_str.c_str(), 0, nullptr); + if (error) + throw std::runtime_error(error.message()); + } + repository = std::unique_ptr<git_repository, std::function<void(git_repository *)> >(repository_ptr, + [](git_repository *ptr) { + git_repository_free(ptr); + }); - work_path = get_work_path(); - if (work_path.empty()) - throw std::runtime_error("Could not find work path"); + work_path = get_work_path(); + if (work_path.empty()) + throw std::runtime_error("Could not find work path"); - auto git_directory = Gio::File::create_for_path(get_path().string()); - monitor = git_directory->monitor_directory(Gio::FileMonitorFlags::FILE_MONITOR_WATCH_MOVES); - monitor_changed_connection = monitor->signal_changed().connect([this](const Glib::RefPtr<Gio::File> &file, - const Glib::RefPtr<Gio::File> &, - Gio::FileMonitorEvent monitor_event) { - if (monitor_event != Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { - this->clear_saved_status(); - } - }, false); + auto git_directory = Gio::File::create_for_path(get_path().string()); + monitor = git_directory->monitor_directory(Gio::FileMonitorFlags::FILE_MONITOR_WATCH_MOVES); + monitor_changed_connection = monitor->signal_changed().connect([this](const Glib::RefPtr<Gio::File> &file, + const Glib::RefPtr<Gio::File> &, + Gio::FileMonitorEvent monitor_event) { + if (monitor_event != Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { + this->clear_saved_status(); + } + }, false); } Git::Repository::~Repository() { - monitor_changed_connection.disconnect(); + monitor_changed_connection.disconnect(); } std::string Git::Repository::status_string(STATUS status) noexcept { - switch (status) { - case STATUS::CURRENT: - return "current"; - case STATUS::NEW: - return "new"; - case STATUS::MODIFIED: - return "modified"; - case STATUS::DELETED: - return "deleted"; - case STATUS::RENAMED: - return "renamed"; - case STATUS::TYPECHANGE: - return "typechange"; - case STATUS::UNREADABLE: - return "unreadable"; - case STATUS::IGNORED: - return "ignored"; - case STATUS::CONFLICTED: - return "conflicted"; - default: - return ""; - } + switch (status) { + case STATUS::CURRENT: + return "current"; + case STATUS::NEW: + return "new"; + case STATUS::MODIFIED: + return "modified"; + case STATUS::DELETED: + return "deleted"; + case STATUS::RENAMED: + return "renamed"; + case STATUS::TYPECHANGE: + return "typechange"; + case STATUS::UNREADABLE: + return "unreadable"; + case STATUS::IGNORED: + return "ignored"; + case STATUS::CONFLICTED: + return "conflicted"; + default: + return ""; + } } int Git::Repository::status_callback(const char *path, unsigned int status_flags, void *data) noexcept { - auto callback = static_cast<std::function<void(const char *path, STATUS status)> *>(data); + auto callback = static_cast<std::function<void(const char *path, STATUS status)> *>(data); - STATUS status; - if ((status_flags & (GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_NEW)) > 0) - status = STATUS::NEW; - else if ((status_flags & (GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED)) > 0) - status = STATUS::MODIFIED; - else if ((status_flags & (GIT_STATUS_INDEX_DELETED | GIT_STATUS_WT_DELETED)) > 0) - status = STATUS::DELETED; - else if ((status_flags & (GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED)) > 0) - status = STATUS::RENAMED; - else if ((status_flags & (GIT_STATUS_INDEX_TYPECHANGE | GIT_STATUS_WT_TYPECHANGE)) > 0) - status = STATUS::TYPECHANGE; - else if ((status_flags & (GIT_STATUS_WT_UNREADABLE)) > 0) - status = STATUS::UNREADABLE; - else if ((status_flags & (GIT_STATUS_IGNORED)) > 0) - status = STATUS::IGNORED; - else if ((status_flags & (GIT_STATUS_CONFLICTED)) > 0) - status = STATUS::CONFLICTED; - else - status = STATUS::CURRENT; + STATUS status; + if ((status_flags & (GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_NEW)) > 0) + status = STATUS::NEW; + else if ((status_flags & (GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED)) > 0) + status = STATUS::MODIFIED; + else if ((status_flags & (GIT_STATUS_INDEX_DELETED | GIT_STATUS_WT_DELETED)) > 0) + status = STATUS::DELETED; + else if ((status_flags & (GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED)) > 0) + status = STATUS::RENAMED; + else if ((status_flags & (GIT_STATUS_INDEX_TYPECHANGE | GIT_STATUS_WT_TYPECHANGE)) > 0) + status = STATUS::TYPECHANGE; + else if ((status_flags & (GIT_STATUS_WT_UNREADABLE)) > 0) + status = STATUS::UNREADABLE; + else if ((status_flags & (GIT_STATUS_IGNORED)) > 0) + status = STATUS::IGNORED; + else if ((status_flags & (GIT_STATUS_CONFLICTED)) > 0) + status = STATUS::CONFLICTED; + else + status = STATUS::CURRENT; - if (*callback) - (*callback)(path, status); + if (*callback) + (*callback)(path, status); - return 0; + return 0; } Git::Repository::Status Git::Repository::get_status() { - { - std::unique_lock<std::mutex> lock(saved_status_mutex); - if (has_saved_status) - return saved_status; - } + { + std::unique_lock<std::mutex> lock(saved_status_mutex); + if (has_saved_status) + return saved_status; + } - Status status; - bool first = true; - std::unique_lock<std::mutex> status_saved_lock(saved_status_mutex, std::defer_lock); - std::function<void(const char *path, STATUS status)> callback = [this, &status, &first, &status_saved_lock]( - const char *path_cstr, STATUS status_enum) { - if (first) { - status_saved_lock.lock(); - first = false; - } - boost::filesystem::path rel_path(path_cstr); - do { - if (status_enum == STATUS::MODIFIED) - status.modified.emplace((work_path / rel_path).generic_string()); - if (status_enum == STATUS::NEW) - status.added.emplace((work_path / rel_path).generic_string()); - rel_path = rel_path.parent_path(); - } while (!rel_path.empty()); - }; - Error error; - std::lock_guard<std::mutex> lock(mutex); - error.code = git_status_foreach(repository.get(), status_callback, &callback); - if (error) - throw std::runtime_error(error.message()); - saved_status = status; - has_saved_status = true; - if (status_saved_lock) - status_saved_lock.unlock(); - return status; + Status status; + bool first = true; + std::unique_lock<std::mutex> status_saved_lock(saved_status_mutex, std::defer_lock); + std::function<void(const char *path, STATUS status)> callback = [this, &status, &first, &status_saved_lock]( + const char *path_cstr, STATUS status_enum) { + if (first) { + status_saved_lock.lock(); + first = false; + } + boost::filesystem::path rel_path(path_cstr); + do { + if (status_enum == STATUS::MODIFIED) + status.modified.emplace((work_path / rel_path).generic_string()); + if (status_enum == STATUS::NEW) + status.added.emplace((work_path / rel_path).generic_string()); + rel_path = rel_path.parent_path(); + } while (!rel_path.empty()); + }; + Error error; + std::lock_guard<std::mutex> lock(mutex); + error.code = git_status_foreach(repository.get(), status_callback, &callback); + if (error) + throw std::runtime_error(error.message()); + saved_status = status; + has_saved_status = true; + if (status_saved_lock) + status_saved_lock.unlock(); + return status; } void Git::Repository::clear_saved_status() { - std::unique_lock<std::mutex> lock(saved_status_mutex); - saved_status.added.clear(); - saved_status.modified.clear(); - has_saved_status = false; + std::unique_lock<std::mutex> lock(saved_status_mutex); + saved_status.added.clear(); + saved_status.modified.clear(); + has_saved_status = false; } boost::filesystem::path Git::Repository::get_work_path() noexcept { - std::lock_guard<std::mutex> lock(mutex); - return Git::path(git_repository_workdir(repository.get())); + std::lock_guard<std::mutex> lock(mutex); + return Git::path(git_repository_workdir(repository.get())); } boost::filesystem::path Git::Repository::get_path() noexcept { - std::lock_guard<std::mutex> lock(mutex); - return Git::path(git_repository_path(repository.get())); + std::lock_guard<std::mutex> lock(mutex); + return Git::path(git_repository_path(repository.get())); } boost::filesystem::path Git::Repository::get_root_path(const boost::filesystem::path &path) { - initialize(); - git_buf root = {0, 0, 0}; - { - Error error; - std::lock_guard<std::mutex> lock(mutex); - auto path_str = path.generic_string(); - error.code = git_repository_discover(&root, path_str.c_str(), 0, nullptr); - if (error) - throw std::runtime_error(error.message()); - } - auto root_path = Git::path(root.ptr, root.size); - git_buf_free(&root); - return root_path; + initialize(); + git_buf root = {0, 0, 0}; + { + Error error; + std::lock_guard<std::mutex> lock(mutex); + auto path_str = path.generic_string(); + error.code = git_repository_discover(&root, path_str.c_str(), 0, nullptr); + if (error) + throw std::runtime_error(error.message()); + } + auto root_path = Git::path(root.ptr, root.size); + git_buf_free(&root); + return root_path; } Git::Repository::Diff Git::Repository::get_diff(const boost::filesystem::path &path) { - return Diff(path, repository.get()); + return Diff(path, repository.get()); } std::string Git::Repository::get_branch() noexcept { - std::string branch; - git_reference *reference; - if (git_repository_head(&reference, repository.get()) == 0) { - if (auto reference_name_cstr = git_reference_name(reference)) { - std::string reference_name(reference_name_cstr); - size_t pos; - if ((pos = reference_name.rfind('/')) != std::string::npos) { - if (pos + 1 < reference_name.size()) - branch = reference_name.substr(pos + 1); - } else if ((pos = reference_name.rfind('\\')) != std::string::npos) { - if (pos + 1 < reference_name.size()) - branch = reference_name.substr(pos + 1); - } - } - git_reference_free(reference); + std::string branch; + git_reference *reference; + if (git_repository_head(&reference, repository.get()) == 0) { + if (auto reference_name_cstr = git_reference_name(reference)) { + std::string reference_name(reference_name_cstr); + size_t pos; + if ((pos = reference_name.rfind('/')) != std::string::npos) { + if (pos + 1 < reference_name.size()) + branch = reference_name.substr(pos + 1); + } else if ((pos = reference_name.rfind('\\')) != std::string::npos) { + if (pos + 1 < reference_name.size()) + branch = reference_name.substr(pos + 1); + } } - return branch; + git_reference_free(reference); + } + return branch; } void Git::initialize() noexcept { - std::lock_guard<std::mutex> lock(mutex); - if (!initialized) { - git_libgit2_init(); - initialized = true; - } + std::lock_guard<std::mutex> lock(mutex); + if (!initialized) { + git_libgit2_init(); + initialized = true; + } } std::shared_ptr<Git::Repository> Git::get_repository(const boost::filesystem::path &path) { - initialize(); - static std::unordered_map<std::string, std::weak_ptr<Git::Repository> > cache; - static std::mutex mutex; + initialize(); + static std::unordered_map<std::string, std::weak_ptr<Git::Repository> > cache; + static std::mutex mutex; - std::lock_guard<std::mutex> lock(mutex); - auto root_path = Repository::get_root_path(path).generic_string(); - auto it = cache.find(root_path); - if (it == cache.end()) - it = cache.emplace(root_path, std::weak_ptr<Git::Repository>()).first; - auto instance = it->second.lock(); - if (!instance) - it->second = instance = std::shared_ptr<Repository>(new Repository(root_path)); - return instance; + std::lock_guard<std::mutex> lock(mutex); + auto root_path = Repository::get_root_path(path).generic_string(); + auto it = cache.find(root_path); + if (it == cache.end()) + it = cache.emplace(root_path, std::weak_ptr<Git::Repository>()).first; + auto instance = it->second.lock(); + if (!instance) + it->second = instance = std::shared_ptr<Repository>(new Repository(root_path)); + return instance; } boost::filesystem::path Git::path(const char *cpath, size_t cpath_length) noexcept { - if (cpath == nullptr) - return boost::filesystem::path(); - if (cpath_length == static_cast<size_t>(-1)) - cpath_length = strlen(cpath); - if (cpath_length > 0 && (cpath[cpath_length - 1] == '/' || cpath[cpath_length - 1] == '\\')) - return std::string(cpath, cpath_length - 1); - else - return std::string(cpath, cpath_length); + if (cpath == nullptr) + return boost::filesystem::path(); + if (cpath_length == static_cast<size_t>(-1)) + cpath_length = strlen(cpath); + if (cpath_length > 0 && (cpath[cpath_length - 1] == '/' || cpath[cpath_length - 1] == '\\')) + return std::string(cpath, cpath_length - 1); + else + return std::string(cpath, cpath_length); } diff --git a/src/git.h b/src/git.h index 38545c92..0fa937a1 100644 --- a/src/git.h +++ b/src/git.h @@ -11,125 +11,125 @@ #include <boost/filesystem.hpp> class Git { - friend class Repository; + friend class Repository; public: - class Error { - friend class Git; + class Error { + friend class Git; - std::string message() noexcept; + std::string message() noexcept; - public: - int code = 0; + public: + int code = 0; - Error() {} + Error() {} - operator bool() noexcept { return code < 0; } - }; + operator bool() noexcept { return code < 0; } + }; - class Repository { + class Repository { + public: + class Diff { public: - class Diff { - public: - class Lines { - public: - std::vector<std::pair<int, int> > added; - std::vector<std::pair<int, int> > modified; - std::vector<int> removed; - }; - - class Hunk { - public: - Hunk(int old_start, int old_size, int new_start, int new_size) : old_lines(old_start, old_size), - new_lines(new_start, new_size) {} - - /// Start and size - std::pair<int, int> old_lines; - /// Start and size - std::pair<int, int> new_lines; - }; + class Lines { + public: + std::vector<std::pair<int, int> > added; + std::vector<std::pair<int, int> > modified; + std::vector<int> removed; + }; + + class Hunk { + public: + Hunk(int old_start, int old_size, int new_start, int new_size) : old_lines(old_start, old_size), + new_lines(new_start, new_size) {} + + /// Start and size + std::pair<int, int> old_lines; + /// Start and size + std::pair<int, int> new_lines; + }; - private: - friend class Repository; + private: + friend class Repository; - Diff(const boost::filesystem::path &path, git_repository *repository); + Diff(const boost::filesystem::path &path, git_repository *repository); - git_repository *repository; - std::shared_ptr<git_blob> blob; - git_diff_options options; + git_repository *repository; + std::shared_ptr<git_blob> blob; + git_diff_options options; - static int hunk_cb(const git_diff_delta *delta, const git_diff_hunk *hunk, void *payload) noexcept; + static int hunk_cb(const git_diff_delta *delta, const git_diff_hunk *hunk, void *payload) noexcept; - static int line_cb(const git_diff_delta *delta, const git_diff_hunk *hunk, const git_diff_line *line, - void *payload) noexcept; + static int line_cb(const git_diff_delta *delta, const git_diff_hunk *hunk, const git_diff_line *line, + void *payload) noexcept; - public: - Diff() : repository(nullptr), blob(nullptr) {} + public: + Diff() : repository(nullptr), blob(nullptr) {} - Lines get_lines(const std::string &buffer); + Lines get_lines(const std::string &buffer); - static std::vector<Hunk> get_hunks(const std::string &old_buffer, const std::string &new_buffer); + static std::vector<Hunk> get_hunks(const std::string &old_buffer, const std::string &new_buffer); - std::string get_details(const std::string &buffer, int line_nr); - }; + std::string get_details(const std::string &buffer, int line_nr); + }; - enum class STATUS { - CURRENT, NEW, MODIFIED, DELETED, RENAMED, TYPECHANGE, UNREADABLE, IGNORED, CONFLICTED - }; + enum class STATUS { + CURRENT, NEW, MODIFIED, DELETED, RENAMED, TYPECHANGE, UNREADABLE, IGNORED, CONFLICTED + }; - class Status { - public: - std::unordered_set<std::string> added; - std::unordered_set<std::string> modified; - }; + class Status { + public: + std::unordered_set<std::string> added; + std::unordered_set<std::string> modified; + }; - private: - friend class Git; + private: + friend class Git; - Repository(const boost::filesystem::path &path); + Repository(const boost::filesystem::path &path); - static int status_callback(const char *path, unsigned int status_flags, void *data) noexcept; + static int status_callback(const char *path, unsigned int status_flags, void *data) noexcept; - std::unique_ptr<git_repository, std::function<void(git_repository *)> > repository; + std::unique_ptr<git_repository, std::function<void(git_repository *)> > repository; - boost::filesystem::path work_path; - sigc::connection monitor_changed_connection; - Status saved_status; - bool has_saved_status = false; - std::mutex saved_status_mutex; - public: - ~Repository(); + boost::filesystem::path work_path; + sigc::connection monitor_changed_connection; + Status saved_status; + bool has_saved_status = false; + std::mutex saved_status_mutex; + public: + ~Repository(); - static std::string status_string(STATUS status) noexcept; + static std::string status_string(STATUS status) noexcept; - Status get_status(); + Status get_status(); - void clear_saved_status(); + void clear_saved_status(); - boost::filesystem::path get_work_path() noexcept; + boost::filesystem::path get_work_path() noexcept; - boost::filesystem::path get_path() noexcept; + boost::filesystem::path get_path() noexcept; - static boost::filesystem::path get_root_path(const boost::filesystem::path &path); + static boost::filesystem::path get_root_path(const boost::filesystem::path &path); - Diff get_diff(const boost::filesystem::path &path); + Diff get_diff(const boost::filesystem::path &path); - std::string get_branch() noexcept; + std::string get_branch() noexcept; - Glib::RefPtr<Gio::FileMonitor> monitor; - }; + Glib::RefPtr<Gio::FileMonitor> monitor; + }; private: - static bool initialized; + static bool initialized; - ///Mutex for thread safe operations - static std::mutex mutex; + ///Mutex for thread safe operations + static std::mutex mutex; - ///Call initialize in public static methods - static void initialize() noexcept; + ///Call initialize in public static methods + static void initialize() noexcept; - static boost::filesystem::path path(const char *cpath, size_t cpath_length = static_cast<size_t>(-1)) noexcept; + static boost::filesystem::path path(const char *cpath, size_t cpath_length = static_cast<size_t>(-1)) noexcept; public: - static std::shared_ptr<Repository> get_repository(const boost::filesystem::path &path); + static std::shared_ptr<Repository> get_repository(const boost::filesystem::path &path); }; diff --git a/src/info.cc b/src/info.cc index a4723a4c..95246334 100644 --- a/src/info.cc +++ b/src/info.cc @@ -1,36 +1,36 @@ #include "info.h" Info::Info() { - set_hexpand(false); - set_halign(Gtk::Align::ALIGN_END); + set_hexpand(false); + set_halign(Gtk::Align::ALIGN_END); - auto content_area = dynamic_cast<Gtk::Container *>(get_content_area()); - label.set_max_width_chars(40); - label.set_line_wrap(true); - content_area->add(label); + auto content_area = dynamic_cast<Gtk::Container *>(get_content_area()); + label.set_max_width_chars(40); + label.set_line_wrap(true); + content_area->add(label); - get_style_context()->add_class("juci_info"); + get_style_context()->add_class("juci_info"); - //Workaround from https://bugzilla.gnome.org/show_bug.cgi?id=710888 - //Issue described at the same issue report - //TODO: remove later - auto revealer = gtk_widget_get_template_child(GTK_WIDGET (gobj()), GTK_TYPE_INFO_BAR, "revealer"); - if (revealer) { - gtk_revealer_set_transition_type(GTK_REVEALER (revealer), GTK_REVEALER_TRANSITION_TYPE_NONE); - gtk_revealer_set_transition_duration(GTK_REVEALER (revealer), 0); - } + //Workaround from https://bugzilla.gnome.org/show_bug.cgi?id=710888 + //Issue described at the same issue report + //TODO: remove later + auto revealer = gtk_widget_get_template_child(GTK_WIDGET (gobj()), GTK_TYPE_INFO_BAR, "revealer"); + if (revealer) { + gtk_revealer_set_transition_type(GTK_REVEALER (revealer), GTK_REVEALER_TRANSITION_TYPE_NONE); + gtk_revealer_set_transition_duration(GTK_REVEALER (revealer), 0); + } } void Info::print(const std::string &text) { - timeout_connection.disconnect(); - //Timeout based on https://en.wikipedia.org/wiki/Words_per_minute - //(average_words_per_minute*average_letters_per_word)/60 => (228*4.5)/60 = 17.1 - double timeout = 1000.0 * std::max(3.0, 1.0 + text.size() / 17.1); - timeout_connection = Glib::signal_timeout().connect([this]() { - hide(); - return false; - }, timeout); + timeout_connection.disconnect(); + //Timeout based on https://en.wikipedia.org/wiki/Words_per_minute + //(average_words_per_minute*average_letters_per_word)/60 => (228*4.5)/60 = 17.1 + double timeout = 1000.0 * std::max(3.0, 1.0 + text.size() / 17.1); + timeout_connection = Glib::signal_timeout().connect([this]() { + hide(); + return false; + }, timeout); - label.set_text(text); - show(); + label.set_text(text); + show(); } diff --git a/src/info.h b/src/info.h index d1ad78ab..8ddae006 100644 --- a/src/info.h +++ b/src/info.h @@ -3,17 +3,17 @@ #include <gtkmm.h> class Info : public Gtk::InfoBar { - Info(); + Info(); public: - static Info &get() { - static Info instance; - return instance; - } + static Info &get() { + static Info instance; + return instance; + } - void print(const std::string &text); + void print(const std::string &text); private: - Gtk::Label label; - sigc::connection timeout_connection; + Gtk::Label label; + sigc::connection timeout_connection; }; diff --git a/src/juci.cc b/src/juci.cc index 29881803..a57babd7 100644 --- a/src/juci.cc +++ b/src/juci.cc @@ -3,7 +3,6 @@ #include "notebook.h" #include "directories.h" #include "menu.h" -#include "config.h" #include "terminal.h" #ifndef _WIN32 @@ -11,127 +10,127 @@ #endif int Application::on_command_line(const Glib::RefPtr<Gio::ApplicationCommandLine> &cmd) { - Glib::set_prgname("juci"); - Glib::OptionContext ctx("[PATH ...]"); - Glib::OptionGroup gtk_group(gtk_get_option_group(true)); - ctx.add_group(gtk_group); - int argc; - char **argv = cmd->get_arguments(argc); - ctx.parse(argc, argv); - if (argc >= 2) { - boost::system::error_code current_path_ec; - auto current_path = boost::filesystem::current_path(current_path_ec); - if (current_path_ec) - errors.emplace_back("Error: could not find current path\n"); - for (int c = 1; c < argc; c++) { - boost::filesystem::path path(argv[c]); - if (path.is_relative() && !current_path_ec) - path = current_path / path; - if (boost::filesystem::exists(path)) { - if (boost::filesystem::is_regular_file(path)) - files.emplace_back(path, 0); - else if (boost::filesystem::is_directory(path)) - directories.emplace_back(path); - } - //Open new file if parent path exists - else { - if (path.is_absolute() && boost::filesystem::is_directory(path.parent_path())) - files.emplace_back(path, 0); - else - errors.emplace_back("Error: could not create " + path.string() + ".\n"); - } - } + Glib::set_prgname("juci"); + Glib::OptionContext ctx("[PATH ...]"); + Glib::OptionGroup gtk_group(gtk_get_option_group(true)); + ctx.add_group(gtk_group); + int argc; + char **argv = cmd->get_arguments(argc); + ctx.parse(argc, argv); + if (argc >= 2) { + boost::system::error_code current_path_ec; + auto current_path = boost::filesystem::current_path(current_path_ec); + if (current_path_ec) + errors.emplace_back("Error: could not find current path\n"); + for (int c = 1; c < argc; c++) { + boost::filesystem::path path(argv[c]); + if (path.is_relative() && !current_path_ec) + path = current_path / path; + if (boost::filesystem::exists(path)) { + if (boost::filesystem::is_regular_file(path)) + files.emplace_back(path, 0); + else if (boost::filesystem::is_directory(path)) + directories.emplace_back(path); + } + //Open new file if parent path exists + else { + if (path.is_absolute() && boost::filesystem::is_directory(path.parent_path())) + files.emplace_back(path, 0); + else + errors.emplace_back("Error: could not create " + path.string() + ".\n"); + } } - activate(); - return 0; + } + activate(); + return 0; } void Application::on_activate() { - std::vector<std::pair<int, int>> file_offsets; - std::string current_file; - Window::get().load_session(directories, files, file_offsets, current_file, directories.empty() && files.empty()); - - Window::get().add_widgets(); - - add_window(Window::get()); - Window::get().show(); - - bool first_directory = true; - for (auto &directory: directories) { - if (first_directory) { - Directories::get().open(directory); - first_directory = false; - } else { - std::string files_in_directory; - for (auto it = files.begin(); it != files.end();) { - if (it->first.generic_string().compare(0, directory.generic_string().size() + 1, - directory.generic_string() + '/') == 0) { - files_in_directory += " " + it->first.string(); - it = files.erase(it); - } else - it++; - } - std::thread another_juci_app([directory, files_in_directory]() { - Terminal::get().async_print("Executing: juci " + directory.string() + files_in_directory + "\n"); - Terminal::get().process("juci " + directory.string() + files_in_directory, "", false); - }); - another_juci_app.detach(); - } - } + std::vector<std::pair<int, int>> file_offsets; + std::string current_file; + Window::get().load_session(directories, files, file_offsets, current_file, directories.empty() && files.empty()); - for (size_t i = 0; i < files.size(); ++i) { - Notebook::get().open(files[i].first, files[i].second); - if (i < file_offsets.size()) { - if (auto view = Notebook::get().get_current_view()) { - view->place_cursor_at_line_offset(file_offsets[i].first, file_offsets[i].second); - view->hide_tooltips(); - } - } - } + Window::get().add_widgets(); - for (auto &error: errors) - Terminal::get().print(error, true); - - if (!current_file.empty()) { - Notebook::get().open(current_file); - if (auto view = Notebook::get().get_current_view()) { - auto iter = view->get_buffer()->get_insert()->get_iter(); - // To update cursor history - view->place_cursor_at_line_offset(iter.get_line(), iter.get_line_offset()); - view->hide_tooltips(); - } + add_window(Window::get()); + Window::get().show(); + + bool first_directory = true; + for (auto &directory: directories) { + if (first_directory) { + Directories::get().open(directory); + first_directory = false; + } else { + std::string files_in_directory; + for (auto it = files.begin(); it != files.end();) { + if (it->first.generic_string().compare(0, directory.generic_string().size() + 1, + directory.generic_string() + '/') == 0) { + files_in_directory += " " + it->first.string(); + it = files.erase(it); + } else + it++; + } + std::thread another_juci_app([directory, files_in_directory]() { + Terminal::get().async_print("Executing: juci " + directory.string() + files_in_directory + "\n"); + Terminal::get().process("juci " + directory.string() + files_in_directory, "", false); + }); + another_juci_app.detach(); } + } + + for (size_t i = 0; i < files.size(); ++i) { + Notebook::get().open(files[i].first, files[i].second); + if (i < file_offsets.size()) { + if (auto view = Notebook::get().get_current_view()) { + view->place_cursor_at_line_offset(file_offsets[i].first, file_offsets[i].second); + view->hide_tooltips(); + } + } + } + + for (auto &error: errors) + Terminal::get().print(error, true); + + if (!current_file.empty()) { + Notebook::get().open(current_file); + if (auto view = Notebook::get().get_current_view()) { + auto iter = view->get_buffer()->get_insert()->get_iter(); + // To update cursor history + view->place_cursor_at_line_offset(iter.get_line(), iter.get_line_offset()); + view->hide_tooltips(); + } + } - while (Gtk::Main::events_pending()) - Gtk::Main::iteration(false); - for (auto view: Notebook::get().get_views()) - view->scroll_to(view->get_buffer()->get_insert(), 0.0, 1.0, 0.5); + while (Gtk::Main::events_pending()) + Gtk::Main::iteration(false); + for (auto view: Notebook::get().get_views()) + view->scroll_to(view->get_buffer()->get_insert(), 0.0, 1.0, 0.5); } void Application::on_startup() { - Gtk::Application::on_startup(); + Gtk::Application::on_startup(); - Menu::get().build(); + Menu::get().build(); - if (!Menu::get().juci_menu || !Menu::get().window_menu) { - std::cerr << "Menu not found." << std::endl; - } else { - set_app_menu(Menu::get().juci_menu); - set_menubar(Menu::get().window_menu); - } + if (!Menu::get().juci_menu || !Menu::get().window_menu) { + std::cerr << "Menu not found." << std::endl; + } else { + set_app_menu(Menu::get().juci_menu); + set_menubar(Menu::get().window_menu); + } } Application::Application() : Gtk::Application("no.sout.juci", Gio::APPLICATION_NON_UNIQUE | Gio::APPLICATION_HANDLES_COMMAND_LINE) { - Glib::set_application_name("juCi++"); + Glib::set_application_name("juCi++"); - //Gtk::MessageDialog without buttons caused text to be selected, this prevents that - Gtk::Settings::get_default()->property_gtk_label_select_on_focus() = false; + //Gtk::MessageDialog without buttons caused text to be selected, this prevents that + Gtk::Settings::get_default()->property_gtk_label_select_on_focus() = false; } int main(int argc, char *argv[]) { #ifndef _WIN32 - signal(SIGPIPE, SIG_IGN); // Do not terminate application when writing to a process fails + signal(SIGPIPE, SIG_IGN); // Do not terminate application when writing to a process fails #endif - return Application().run(argc, argv); + return Application().run(argc, argv); } diff --git a/src/juci.h b/src/juci.h index 0d267496..fe68b971 100644 --- a/src/juci.h +++ b/src/juci.h @@ -30,16 +30,16 @@ class Application : public Gtk::Application { public: - Application(); + Application(); - int on_command_line(const Glib::RefPtr<Gio::ApplicationCommandLine> &cmd) override; + int on_command_line(const Glib::RefPtr<Gio::ApplicationCommandLine> &cmd) override; - void on_activate() override; + void on_activate() override; - void on_startup() override; + void on_startup() override; private: - std::vector<boost::filesystem::path> directories; - std::vector<std::pair<boost::filesystem::path, size_t>> files; - std::vector<std::string> errors; + std::vector<boost::filesystem::path> directories; + std::vector<std::pair<boost::filesystem::path, size_t>> files; + std::vector<std::string> errors; }; diff --git a/src/menu.cc b/src/menu.cc index 3a0e16dd..6634d4a3 100644 --- a/src/menu.cc +++ b/src/menu.cc @@ -478,39 +478,39 @@ const Glib::ustring menu_xml = R"RAW(<interface> )RAW"; void Menu::add_action(const std::string &name, std::function<void()> action) { - auto g_application = g_application_get_default(); - auto gio_application = Glib::wrap(g_application, true); - auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application); + auto g_application = g_application_get_default(); + auto gio_application = Glib::wrap(g_application, true); + auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application); - actions[name] = application->add_action(name, action); + actions[name] = application->add_action(name, action); } void Menu::set_keys() { - auto g_application = g_application_get_default(); - auto gio_application = Glib::wrap(g_application, true); - auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application); + auto g_application = g_application_get_default(); + auto gio_application = Glib::wrap(g_application, true); + auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application); - for (auto &key: Config::get().menu.keys) { - if (key.second.size() > 0 && actions.find(key.first) != actions.end()) - application->set_accel_for_action("app." + key.first, key.second); - } + for (auto &key: Config::get().menu.keys) { + if (key.second.size() > 0 && actions.find(key.first) != actions.end()) + application->set_accel_for_action("app." + key.first, key.second); + } } void Menu::build() { - try { - builder = Gtk::Builder::create_from_string(menu_xml); - auto object = builder->get_object("juci-menu"); - juci_menu = Glib::RefPtr<Gio::Menu>::cast_dynamic(object); - object = builder->get_object("window-menu"); - window_menu = Glib::RefPtr<Gio::Menu>::cast_dynamic(object); - object = builder->get_object("right-click-line-menu"); - auto ptr = Glib::RefPtr<Gio::Menu>::cast_dynamic(object); - right_click_line_menu = std::make_unique<Gtk::Menu>(ptr); - object = builder->get_object("right-click-selected-menu"); - ptr = Glib::RefPtr<Gio::Menu>::cast_dynamic(object); - right_click_selected_menu = std::make_unique<Gtk::Menu>(ptr); - } - catch (const Glib::Error &ex) { - std::cerr << "building menu failed: " << ex.what(); - } + try { + builder = Gtk::Builder::create_from_string(menu_xml); + auto object = builder->get_object("juci-menu"); + juci_menu = Glib::RefPtr<Gio::Menu>::cast_dynamic(object); + object = builder->get_object("window-menu"); + window_menu = Glib::RefPtr<Gio::Menu>::cast_dynamic(object); + object = builder->get_object("right-click-line-menu"); + auto ptr = Glib::RefPtr<Gio::Menu>::cast_dynamic(object); + right_click_line_menu = std::make_unique<Gtk::Menu>(ptr); + object = builder->get_object("right-click-selected-menu"); + ptr = Glib::RefPtr<Gio::Menu>::cast_dynamic(object); + right_click_selected_menu = std::make_unique<Gtk::Menu>(ptr); + } + catch (const Glib::Error &ex) { + std::cerr << "building menu failed: " << ex.what(); + } } diff --git a/src/menu.h b/src/menu.h index 9951b373..324575f4 100644 --- a/src/menu.h +++ b/src/menu.h @@ -6,27 +6,27 @@ #include <gtkmm.h> class Menu { - Menu() {} + Menu() {} public: - static Menu &get() { - static Menu singleton; - return singleton; - } + static Menu &get() { + static Menu singleton; + return singleton; + } - void add_action(const std::string &name, std::function<void()> action); + void add_action(const std::string &name, std::function<void()> action); - std::unordered_map<std::string, Glib::RefPtr<Gio::SimpleAction> > actions; + std::unordered_map<std::string, Glib::RefPtr<Gio::SimpleAction> > actions; - void set_keys(); + void set_keys(); - void build(); + void build(); - Glib::RefPtr<Gio::Menu> juci_menu; - Glib::RefPtr<Gio::Menu> window_menu; - std::unique_ptr<Gtk::Menu> right_click_line_menu; - std::unique_ptr<Gtk::Menu> right_click_selected_menu; - std::function<void()> toggle_menu_items = [] {}; + Glib::RefPtr<Gio::Menu> juci_menu; + Glib::RefPtr<Gio::Menu> window_menu; + std::unique_ptr<Gtk::Menu> right_click_line_menu; + std::unique_ptr<Gtk::Menu> right_click_selected_menu; + std::function<void()> toggle_menu_items = [] {}; private: - Glib::RefPtr<Gtk::Builder> builder; + Glib::RefPtr<Gtk::Builder> builder; }; diff --git a/src/meson.cc b/src/meson.cc index 3bc43006..7ea854fa 100644 --- a/src/meson.cc +++ b/src/meson.cc @@ -5,78 +5,80 @@ #include "dialogs.h" #include "config.h" -Meson::Meson(const boost::filesystem::path &path): BuildSystemBase(path, "meson.build") {} +Meson::Meson(const boost::filesystem::path &path) : BuildSystemBase(path, "meson.build") {} bool Meson::update_default_build(const boost::filesystem::path &default_build_path, bool force) { - if (get_project_path().empty() || !boost::filesystem::exists(get_project_path() / "meson.build") || default_build_path.empty()) - return false; + if (get_project_path().empty() || !boost::filesystem::exists(get_project_path() / "meson.build") || + default_build_path.empty()) + return false; - if (!create_build_directory(default_build_path)) return false; + if (!create_build_directory(default_build_path)) return false; - auto compile_commands_path = default_build_path / "compile_commands.json"; - bool compile_commands_exists = boost::filesystem::exists(compile_commands_path); - if (!force && compile_commands_exists) - return true; + auto compile_commands_path = default_build_path / "compile_commands.json"; + bool compile_commands_exists = boost::filesystem::exists(compile_commands_path); + if (!force && compile_commands_exists) + return true; - Dialog::Message message("Creating/updating default build"); - auto exit_status = Terminal::get().process( - Config::get().project.meson.command + ' ' + (compile_commands_exists ? "--internal regenerate " : "") + - filesystem::escape_argument(get_project_path().string()), default_build_path); - message.hide(); - if (exit_status == EXIT_SUCCESS) - return true; - return false; + Dialog::Message message("Creating/updating default build"); + auto exit_status = Terminal::get().process( + Config::get().project.meson.command + ' ' + (compile_commands_exists ? "--internal regenerate " : "") + + filesystem::escape_argument(get_project_path().string()), default_build_path); + message.hide(); + if (exit_status == EXIT_SUCCESS) + return true; + return false; } bool Meson::update_debug_build(const boost::filesystem::path &debug_build_path, bool force) { - if (get_project_path().empty() || !boost::filesystem::exists(get_project_path() / "meson.build") || debug_build_path.empty()) - return false; + if (get_project_path().empty() || !boost::filesystem::exists(get_project_path() / "meson.build") || + debug_build_path.empty()) + return false; - if (!create_build_directory(debug_build_path)) return false; + if (!create_build_directory(debug_build_path)) return false; - bool compile_commands_exists = boost::filesystem::exists(debug_build_path / "compile_commands.json"); - if (!force && compile_commands_exists) - return true; + bool compile_commands_exists = boost::filesystem::exists(debug_build_path / "compile_commands.json"); + if (!force && compile_commands_exists) + return true; - Dialog::Message message("Creating/updating debug build"); - auto exit_status = Terminal::get().process( - Config::get().project.meson.command + ' ' + (compile_commands_exists ? "--internal regenerate " : "") + - "--buildtype debug " + filesystem::escape_argument(get_project_path().string()), debug_build_path); - message.hide(); - if (exit_status == EXIT_SUCCESS) - return true; - return false; + Dialog::Message message("Creating/updating debug build"); + auto exit_status = Terminal::get().process( + Config::get().project.meson.command + ' ' + (compile_commands_exists ? "--internal regenerate " : "") + + "--buildtype debug " + filesystem::escape_argument(get_project_path().string()), debug_build_path); + message.hide(); + if (exit_status == EXIT_SUCCESS) + return true; + return false; } boost::filesystem::path Meson::get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) { - CompileCommands compile_commands(build_path); + CompileCommands compile_commands(build_path); - size_t best_match_size = -1; - boost::filesystem::path best_match_executable; - for (auto &command: compile_commands.commands) { - auto command_file = filesystem::get_normal_path(command.file); - auto values = command.parameter_values("-o"); - if (!values.empty()) { - size_t pos; - if ((pos = values[0].find("@")) != std::string::npos) { - if (pos + 1 < values[0].size() && values[0].compare(pos + 1, 3, "exe") == 0) { - auto executable = build_path / values[0].substr(0, pos); - if (command_file == file_path) - return executable; - auto command_file_directory = command_file.parent_path(); - if (filesystem::file_in_path(file_path, command_file_directory)) { - auto size = static_cast<size_t>(std::distance(command_file_directory.begin(), - command_file_directory.end())); - if (best_match_size == static_cast<size_t>(-1) || best_match_size < size) { - best_match_size = size; - best_match_executable = executable; - } - } - } + size_t best_match_size = -1; + boost::filesystem::path best_match_executable; + for (auto &command: compile_commands.commands) { + auto command_file = filesystem::get_normal_path(command.file); + auto values = command.parameter_values("-o"); + if (!values.empty()) { + size_t pos; + if ((pos = values[0].find("@")) != std::string::npos) { + if (pos + 1 < values[0].size() && values[0].compare(pos + 1, 3, "exe") == 0) { + auto executable = build_path / values[0].substr(0, pos); + if (command_file == file_path) + return executable; + auto command_file_directory = command_file.parent_path(); + if (filesystem::file_in_path(file_path, command_file_directory)) { + auto size = static_cast<size_t>(std::distance(command_file_directory.begin(), + command_file_directory.end())); + if (best_match_size == static_cast<size_t>(-1) || best_match_size < size) { + best_match_size = size; + best_match_executable = executable; } + } } + } } + } - return best_match_executable; + return best_match_executable; } diff --git a/src/meson.h b/src/meson.h index d22625d3..cd31dbfa 100644 --- a/src/meson.h +++ b/src/meson.h @@ -1,15 +1,16 @@ #pragma once #include "buildsystem.h" + class Meson : public BuildSystemBase { public: - Meson(const boost::filesystem::path &path); + Meson(const boost::filesystem::path &path); - bool update_default_build(const boost::filesystem::path &default_build_path, bool force) override; + bool update_default_build(const boost::filesystem::path &default_build_path, bool force) override; - bool update_debug_build(const boost::filesystem::path &debug_build_path, bool force) override; + bool update_debug_build(const boost::filesystem::path &debug_build_path, bool force) override; - boost::filesystem::path - get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) override; + boost::filesystem::path + get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) override; }; diff --git a/src/notebook.cc b/src/notebook.cc index a177968a..43473d1e 100644 --- a/src/notebook.cc +++ b/src/notebook.cc @@ -11,673 +11,673 @@ #include "gtksourceview-3.0/gtksourceview/gtksourcemap.h" Notebook::TabLabel::TabLabel(std::function<void()> on_close) { - set_can_focus(false); - - auto button = Gtk::manage(new Gtk::Button()); - auto hbox = Gtk::manage(new Gtk::Box()); - - hbox->set_can_focus(false); - 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); - - hbox->pack_start(label, Gtk::PACK_SHRINK); - hbox->pack_end(*button, Gtk::PACK_SHRINK); - add(*hbox); - - button->signal_clicked().connect(on_close); - signal_button_press_event().connect([on_close](GdkEventButton *event) { - if (event->button == GDK_BUTTON_MIDDLE) { - on_close(); - return true; - } - return false; - }); + set_can_focus(false); + + auto button = Gtk::manage(new Gtk::Button()); + auto hbox = Gtk::manage(new Gtk::Box()); + + hbox->set_can_focus(false); + 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); + + hbox->pack_start(label, Gtk::PACK_SHRINK); + hbox->pack_end(*button, Gtk::PACK_SHRINK); + add(*hbox); + + button->signal_clicked().connect(on_close); + signal_button_press_event().connect([on_close](GdkEventButton *event) { + if (event->button == GDK_BUTTON_MIDDLE) { + on_close(); + return true; + } + return false; + }); - show_all(); + show_all(); } Notebook::Notebook() : Gtk::Paned(), notebooks(2) { - for (auto ¬ebook: notebooks) { - notebook.get_style_context()->add_class("juci_notebook"); - notebook.set_scrollable(); - notebook.set_group_name("source_notebooks"); - notebook.signal_switch_page().connect([this](Gtk::Widget *widget, guint) { - auto hbox = dynamic_cast<Gtk::Box *>(widget); - for (size_t c = 0; c < hboxes.size(); ++c) { - if (hboxes[c].get() == hbox) { - focus_view(source_views[c]); - set_current_view(source_views[c]); - break; - } - } - last_index = -1; - }); - notebook.signal_page_added().connect([this](Gtk::Widget *widget, guint) { - auto hbox = dynamic_cast<Gtk::Box *>(widget); - for (size_t c = 0; c < hboxes.size(); ++c) { - if (hboxes[c].get() == hbox) { - focus_view(source_views[c]); - set_current_view(source_views[c]); - break; - } - } - }); - } - pack1(notebooks[0], true, true); + for (auto ¬ebook: notebooks) { + notebook.get_style_context()->add_class("juci_notebook"); + notebook.set_scrollable(); + notebook.set_group_name("source_notebooks"); + notebook.signal_switch_page().connect([this](Gtk::Widget *widget, guint) { + auto hbox = dynamic_cast<Gtk::Box *>(widget); + for (size_t c = 0; c < hboxes.size(); ++c) { + if (hboxes[c].get() == hbox) { + focus_view(source_views[c]); + set_current_view(source_views[c]); + break; + } + } + last_index = -1; + }); + notebook.signal_page_added().connect([this](Gtk::Widget *widget, guint) { + auto hbox = dynamic_cast<Gtk::Box *>(widget); + for (size_t c = 0; c < hboxes.size(); ++c) { + if (hboxes[c].get() == hbox) { + focus_view(source_views[c]); + set_current_view(source_views[c]); + break; + } + } + }); + } + pack1(notebooks[0], true, true); } size_t Notebook::size() { - return source_views.size(); + return source_views.size(); } Source::View *Notebook::get_view(size_t index) { - if (index >= size()) - return nullptr; - return source_views[index]; + if (index >= size()) + return nullptr; + return source_views[index]; } Source::View *Notebook::get_current_view() { - if (intermediate_view) { - for (auto view: source_views) { - if (view == intermediate_view) - return view; - } - } + if (intermediate_view) { for (auto view: source_views) { - if (view == current_view) - return view; - } - //In case there exist a tab that has not yet received focus again in a different notebook - for (int notebook_index = 0; notebook_index < 2; ++notebook_index) { - auto page = notebooks[notebook_index].get_current_page(); - if (page >= 0) - return get_view(notebook_index, page); + if (view == intermediate_view) + return view; } - return nullptr; + } + for (auto view: source_views) { + if (view == current_view) + return view; + } + //In case there exist a tab that has not yet received focus again in a different notebook + for (int notebook_index = 0; notebook_index < 2; ++notebook_index) { + auto page = notebooks[notebook_index].get_current_page(); + if (page >= 0) + return get_view(notebook_index, page); + } + return nullptr; } std::vector<Source::View *> &Notebook::get_views() { - return source_views; + return source_views; } void Notebook::open(const boost::filesystem::path &file_path_, size_t notebook_index) { - auto file_path = filesystem::get_normal_path(file_path_); - - if (notebook_index == 1 && !split) - toggle_split(); - - // Use canonical path to follow symbolic links - boost::system::error_code ec; - auto canonical_file_path = boost::filesystem::canonical(file_path, ec); - if (ec) - canonical_file_path = file_path; - for (size_t c = 0; c < size(); c++) { - if (canonical_file_path == source_views[c]->canonical_file_path) { - auto notebook_page = get_notebook_page(c); - notebooks[notebook_page.first].set_current_page(notebook_page.second); - focus_view(source_views[c]); - return; - } + auto file_path = filesystem::get_normal_path(file_path_); + + if (notebook_index == 1 && !split) + toggle_split(); + + // Use canonical path to follow symbolic links + boost::system::error_code ec; + auto canonical_file_path = boost::filesystem::canonical(file_path, ec); + if (ec) + canonical_file_path = file_path; + for (size_t c = 0; c < size(); c++) { + if (canonical_file_path == source_views[c]->canonical_file_path) { + auto notebook_page = get_notebook_page(c); + notebooks[notebook_page.first].set_current_page(notebook_page.second); + focus_view(source_views[c]); + return; } + } - if (boost::filesystem::exists(file_path)) { - std::ifstream can_read(file_path.string()); - if (!can_read) { - Terminal::get().print("Error: could not open " + file_path.string() + "\n", true); - return; - } - can_read.close(); + if (boost::filesystem::exists(file_path)) { + std::ifstream can_read(file_path.string()); + if (!can_read) { + Terminal::get().print("Error: could not open " + file_path.string() + "\n", true); + return; } + can_read.close(); + } - auto last_view = get_current_view(); + auto last_view = get_current_view(); - auto language = Source::guess_language(file_path); + auto language = Source::guess_language(file_path); - std::string language_protocol_language_id; - if (language) { - language_protocol_language_id = language->get_id(); - if (language_protocol_language_id == "js") { - if (file_path.extension() == ".ts") - language_protocol_language_id = "typescript"; - else - language_protocol_language_id = "javascript"; - } + std::string language_protocol_language_id; + if (language) { + language_protocol_language_id = language->get_id(); + if (language_protocol_language_id == "js") { + if (file_path.extension() == ".ts") + language_protocol_language_id = "typescript"; + else + language_protocol_language_id = "javascript"; } - - if (language && (language->get_id() == "chdr" || language->get_id() == "cpphdr" || language->get_id() == "c" || - language->get_id() == "cpp" || language->get_id() == "objc")) - source_views.emplace_back(new Source::ClangView(file_path, language)); - else if (language && !language_protocol_language_id.empty() && - !filesystem::find_executable(language_protocol_language_id + "-language-server").empty()) - source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id)); - else - source_views.emplace_back(new Source::GenericView(file_path, language)); - - auto source_view = source_views.back(); - - source_view->scroll_to_cursor_delayed = [this](Source::BaseView *view, bool center, bool show_tooltips) { - while (Gtk::Main::events_pending()) - Gtk::Main::iteration(false); - if (get_current_view() == view) { - if (center) - view->scroll_to(view->get_buffer()->get_insert(), 0.0, 1.0, 0.5); - else - view->scroll_to(view->get_buffer()->get_insert()); - if (!show_tooltips) - view->hide_tooltips(); + } + + if (language && (language->get_id() == "chdr" || language->get_id() == "cpphdr" || language->get_id() == "c" || + language->get_id() == "cpp" || language->get_id() == "objc")) + source_views.emplace_back(new Source::ClangView(file_path, language)); + else if (language && !language_protocol_language_id.empty() && + !filesystem::find_executable(language_protocol_language_id + "-language-server").empty()) + source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id)); + else + source_views.emplace_back(new Source::GenericView(file_path, language)); + + auto source_view = source_views.back(); + + source_view->scroll_to_cursor_delayed = [this](Source::BaseView *view, bool center, bool show_tooltips) { + while (Gtk::Main::events_pending()) + Gtk::Main::iteration(false); + if (get_current_view() == view) { + if (center) + view->scroll_to(view->get_buffer()->get_insert(), 0.0, 1.0, 0.5); + else + view->scroll_to(view->get_buffer()->get_insert()); + if (!show_tooltips) + view->hide_tooltips(); + } + }; + source_view->update_status_location = [this](Source::BaseView *view) { + if (get_current_view() == view) { + auto iter = view->get_buffer()->get_insert()->get_iter(); + status_location.set_text( + " " + std::to_string(iter.get_line() + 1) + ":" + std::to_string(iter.get_line_offset() + 1)); + } + }; + source_view->update_status_file_path = [this](Source::BaseView *view) { + if (get_current_view() == view) + status_file_path.set_text(' ' + filesystem::get_short_path(view->file_path).string()); + }; + source_view->update_status_branch = [this](Source::BaseView *view) { + if (get_current_view() == view) { + if (!view->status_branch.empty()) + status_branch.set_text(" (" + view->status_branch + ")"); + else + status_branch.set_text(""); + } + }; + source_view->update_status_diagnostics = [this](Source::BaseView *view) { + if (get_current_view() == view) { + std::string diagnostic_info; + + auto num_warnings = std::get<0>(view->status_diagnostics); + auto num_errors = std::get<1>(view->status_diagnostics); + auto num_fix_its = std::get<2>(view->status_diagnostics); + if (num_warnings > 0 || num_errors > 0 || num_fix_its > 0) { + auto normal_color = get_style_context()->get_color(Gtk::StateFlags::STATE_FLAG_NORMAL); + Gdk::RGBA yellow; + yellow.set_rgba(1.0, 1.0, 0.2); + double factor = 0.5; + yellow.set_red(normal_color.get_red() + factor * (yellow.get_red() - normal_color.get_red())); + yellow.set_green(normal_color.get_green() + factor * (yellow.get_green() - normal_color.get_green())); + yellow.set_blue(normal_color.get_blue() + factor * (yellow.get_blue() - normal_color.get_blue())); + Gdk::RGBA red; + red.set_rgba(1.0, 0.0, 0.0); + factor = 0.5; + red.set_red(normal_color.get_red() + factor * (red.get_red() - normal_color.get_red())); + red.set_green(normal_color.get_green() + factor * (red.get_green() - normal_color.get_green())); + red.set_blue(normal_color.get_blue() + factor * (red.get_blue() - normal_color.get_blue())); + Gdk::RGBA green; + green.set_rgba(0.0, 1.0, 0.0); + factor = 0.4; + green.set_red(normal_color.get_red() + factor * (green.get_red() - normal_color.get_red())); + green.set_green(normal_color.get_green() + factor * (green.get_green() - normal_color.get_green())); + green.set_blue(normal_color.get_blue() + factor * (green.get_blue() - normal_color.get_blue())); + + std::stringstream yellow_ss, red_ss, green_ss; + yellow_ss << std::hex << std::setfill('0') << std::setw(2) << (int) (yellow.get_red_u() >> 8) + << std::setw(2) << (int) (yellow.get_green_u() >> 8) << std::setw(2) + << (int) (yellow.get_blue_u() >> 8); + red_ss << std::hex << std::setfill('0') << std::setw(2) << (int) (red.get_red_u() >> 8) << std::setw(2) + << (int) (red.get_green_u() >> 8) << std::setw(2) << (int) (red.get_blue_u() >> 8); + green_ss << std::hex << std::setfill('0') << std::setw(2) << (int) (green.get_red_u() >> 8) + << std::setw(2) << (int) (green.get_green_u() >> 8) << std::setw(2) + << (int) (green.get_blue_u() >> 8); + if (num_warnings > 0) { + diagnostic_info += "<span color='#" + yellow_ss.str() + "'>"; + diagnostic_info += std::to_string(num_warnings) + " warning"; + if (num_warnings > 1) + diagnostic_info += 's'; + diagnostic_info += "</span>"; } - }; - source_view->update_status_location = [this](Source::BaseView *view) { - if (get_current_view() == view) { - auto iter = view->get_buffer()->get_insert()->get_iter(); - status_location.set_text( - " " + std::to_string(iter.get_line() + 1) + ":" + std::to_string(iter.get_line_offset() + 1)); + if (num_errors > 0) { + if (num_warnings > 0) + diagnostic_info += ", "; + diagnostic_info += "<span color='#" + red_ss.str() + "'>"; + diagnostic_info += std::to_string(num_errors) + " error"; + if (num_errors > 1) + diagnostic_info += 's'; + diagnostic_info += "</span>"; } - }; - source_view->update_status_file_path = [this](Source::BaseView *view) { - if (get_current_view() == view) - status_file_path.set_text(' ' + filesystem::get_short_path(view->file_path).string()); - }; - source_view->update_status_branch = [this](Source::BaseView *view) { - if (get_current_view() == view) { - if (!view->status_branch.empty()) - status_branch.set_text(" (" + view->status_branch + ")"); - else - status_branch.set_text(""); + if (num_fix_its > 0) { + if (num_warnings > 0 || num_errors > 0) + diagnostic_info += ", "; + diagnostic_info += "<span color='#" + green_ss.str() + "'>"; + diagnostic_info += std::to_string(num_fix_its) + " fix it"; + if (num_fix_its > 1) + diagnostic_info += 's'; + diagnostic_info += "</span>"; } - }; - source_view->update_status_diagnostics = [this](Source::BaseView *view) { - if (get_current_view() == view) { - std::string diagnostic_info; - - auto num_warnings = std::get<0>(view->status_diagnostics); - auto num_errors = std::get<1>(view->status_diagnostics); - auto num_fix_its = std::get<2>(view->status_diagnostics); - if (num_warnings > 0 || num_errors > 0 || num_fix_its > 0) { - auto normal_color = get_style_context()->get_color(Gtk::StateFlags::STATE_FLAG_NORMAL); - Gdk::RGBA yellow; - yellow.set_rgba(1.0, 1.0, 0.2); - double factor = 0.5; - yellow.set_red(normal_color.get_red() + factor * (yellow.get_red() - normal_color.get_red())); - yellow.set_green(normal_color.get_green() + factor * (yellow.get_green() - normal_color.get_green())); - yellow.set_blue(normal_color.get_blue() + factor * (yellow.get_blue() - normal_color.get_blue())); - Gdk::RGBA red; - red.set_rgba(1.0, 0.0, 0.0); - factor = 0.5; - red.set_red(normal_color.get_red() + factor * (red.get_red() - normal_color.get_red())); - red.set_green(normal_color.get_green() + factor * (red.get_green() - normal_color.get_green())); - red.set_blue(normal_color.get_blue() + factor * (red.get_blue() - normal_color.get_blue())); - Gdk::RGBA green; - green.set_rgba(0.0, 1.0, 0.0); - factor = 0.4; - green.set_red(normal_color.get_red() + factor * (green.get_red() - normal_color.get_red())); - green.set_green(normal_color.get_green() + factor * (green.get_green() - normal_color.get_green())); - green.set_blue(normal_color.get_blue() + factor * (green.get_blue() - normal_color.get_blue())); - - std::stringstream yellow_ss, red_ss, green_ss; - yellow_ss << std::hex << std::setfill('0') << std::setw(2) << (int) (yellow.get_red_u() >> 8) - << std::setw(2) << (int) (yellow.get_green_u() >> 8) << std::setw(2) - << (int) (yellow.get_blue_u() >> 8); - red_ss << std::hex << std::setfill('0') << std::setw(2) << (int) (red.get_red_u() >> 8) << std::setw(2) - << (int) (red.get_green_u() >> 8) << std::setw(2) << (int) (red.get_blue_u() >> 8); - green_ss << std::hex << std::setfill('0') << std::setw(2) << (int) (green.get_red_u() >> 8) - << std::setw(2) << (int) (green.get_green_u() >> 8) << std::setw(2) - << (int) (green.get_blue_u() >> 8); - if (num_warnings > 0) { - diagnostic_info += "<span color='#" + yellow_ss.str() + "'>"; - diagnostic_info += std::to_string(num_warnings) + " warning"; - if (num_warnings > 1) - diagnostic_info += 's'; - diagnostic_info += "</span>"; - } - if (num_errors > 0) { - if (num_warnings > 0) - diagnostic_info += ", "; - diagnostic_info += "<span color='#" + red_ss.str() + "'>"; - diagnostic_info += std::to_string(num_errors) + " error"; - if (num_errors > 1) - diagnostic_info += 's'; - diagnostic_info += "</span>"; - } - if (num_fix_its > 0) { - if (num_warnings > 0 || num_errors > 0) - diagnostic_info += ", "; - diagnostic_info += "<span color='#" + green_ss.str() + "'>"; - diagnostic_info += std::to_string(num_fix_its) + " fix it"; - if (num_fix_its > 1) - diagnostic_info += 's'; - diagnostic_info += "</span>"; - } - } - status_diagnostics.set_markup(diagnostic_info); - } - }; - source_view->update_status_state = [this](Source::BaseView *view) { - if (get_current_view() == view) - status_state.set_text(view->status_state + " "); - }; - - scrolled_windows.emplace_back(new Gtk::ScrolledWindow()); - hboxes.emplace_back(new Gtk::Box()); - scrolled_windows.back()->add(*source_view); - hboxes.back()->pack_start(*scrolled_windows.back()); - - source_maps.emplace_back(Glib::wrap(gtk_source_map_new())); - gtk_source_map_set_view(GTK_SOURCE_MAP(source_maps.back()->gobj()), source_view->gobj()); - - configure(source_views.size() - 1); - - //Set up tab label - tab_labels.emplace_back(new TabLabel([this, source_view]() { - auto index = get_index(source_view); - if (index != static_cast<size_t>(-1)) - close(index); - })); - source_view->update_tab_label = [this](Source::BaseView *view) { - std::string title = view->file_path.filename().string(); - if (view->get_buffer()->get_modified()) - title += '*'; - else - title += ' '; - for (size_t c = 0; c < size(); ++c) { - if (source_views[c] == view) { - auto &tab_label = tab_labels.at(c); - tab_label->label.set_text(title); - tab_label->set_tooltip_text(filesystem::get_short_path(view->file_path).string()); - return; - } + } + status_diagnostics.set_markup(diagnostic_info); + } + }; + source_view->update_status_state = [this](Source::BaseView *view) { + if (get_current_view() == view) + status_state.set_text(view->status_state + " "); + }; + + scrolled_windows.emplace_back(new Gtk::ScrolledWindow()); + hboxes.emplace_back(new Gtk::Box()); + scrolled_windows.back()->add(*source_view); + hboxes.back()->pack_start(*scrolled_windows.back()); + + source_maps.emplace_back(Glib::wrap(gtk_source_map_new())); + gtk_source_map_set_view(GTK_SOURCE_MAP(source_maps.back()->gobj()), source_view->gobj()); + + configure(source_views.size() - 1); + + //Set up tab label + tab_labels.emplace_back(new TabLabel([this, source_view]() { + auto index = get_index(source_view); + if (index != static_cast<size_t>(-1)) + close(index); + })); + source_view->update_tab_label = [this](Source::BaseView *view) { + std::string title = view->file_path.filename().string(); + if (view->get_buffer()->get_modified()) + title += '*'; + else + title += ' '; + for (size_t c = 0; c < size(); ++c) { + if (source_views[c] == view) { + auto &tab_label = tab_labels.at(c); + tab_label->label.set_text(title); + tab_label->set_tooltip_text(filesystem::get_short_path(view->file_path).string()); + return; + } + } + }; + source_view->update_tab_label(source_view); + + //Add star on tab label when the page is not saved: + source_view->get_buffer()->signal_modified_changed().connect([source_view]() { + if (source_view->update_tab_label) + source_view->update_tab_label(source_view); + }); + + //Cursor history + auto update_cursor_locations = [this, source_view](const Gtk::TextBuffer::iterator &iter) { + bool mark_moved = false; + if (current_cursor_location != static_cast<size_t>(-1)) { + auto &cursor_location = cursor_locations.at(current_cursor_location); + if (cursor_location.view == source_view && + abs(cursor_location.mark->get_iter().get_line() - iter.get_line()) <= 2) { + source_view->get_buffer()->move_mark(cursor_location.mark, iter); + mark_moved = true; + } + } + if (!mark_moved) { + if (current_cursor_location != static_cast<size_t>(-1)) { + for (auto it = cursor_locations.begin() + current_cursor_location + 1; it != cursor_locations.end();) { + it->view->get_buffer()->delete_mark(it->mark); + it = cursor_locations.erase(it); } - }; - source_view->update_tab_label(source_view); - - //Add star on tab label when the page is not saved: - source_view->get_buffer()->signal_modified_changed().connect([source_view]() { - if (source_view->update_tab_label) - source_view->update_tab_label(source_view); - }); + } + cursor_locations.emplace_back(source_view, source_view->get_buffer()->create_mark(iter)); + current_cursor_location = cursor_locations.size() - 1; + } - //Cursor history - auto update_cursor_locations = [this, source_view](const Gtk::TextBuffer::iterator &iter) { - bool mark_moved = false; - if (current_cursor_location != static_cast<size_t>(-1)) { - auto &cursor_location = cursor_locations.at(current_cursor_location); - if (cursor_location.view == source_view && - abs(cursor_location.mark->get_iter().get_line() - iter.get_line()) <= 2) { - source_view->get_buffer()->move_mark(cursor_location.mark, iter); - mark_moved = true; - } - } - if (!mark_moved) { - if (current_cursor_location != static_cast<size_t>(-1)) { - for (auto it = cursor_locations.begin() + current_cursor_location + 1; it != cursor_locations.end();) { - it->view->get_buffer()->delete_mark(it->mark); - it = cursor_locations.erase(it); - } - } - cursor_locations.emplace_back(source_view, source_view->get_buffer()->create_mark(iter)); - current_cursor_location = cursor_locations.size() - 1; + // Combine adjacent cursor histories that are similar + if (!cursor_locations.empty()) { + size_t cursor_locations_index = 1; + auto last_it = cursor_locations.begin(); + for (auto it = cursor_locations.begin() + 1; it != cursor_locations.end();) { + if (last_it->view == it->view && + abs(last_it->mark->get_iter().get_line() - it->mark->get_iter().get_line()) <= 2) { + last_it->view->get_buffer()->delete_mark(last_it->mark); + last_it->mark = it->mark; + it = cursor_locations.erase(it); + if (current_cursor_location != static_cast<size_t>(-1) && + current_cursor_location > cursor_locations_index) + --current_cursor_location; + } else { + ++it; + ++last_it; + ++cursor_locations_index; } + } + } - // Combine adjacent cursor histories that are similar - if (!cursor_locations.empty()) { - size_t cursor_locations_index = 1; - auto last_it = cursor_locations.begin(); - for (auto it = cursor_locations.begin() + 1; it != cursor_locations.end();) { - if (last_it->view == it->view && - abs(last_it->mark->get_iter().get_line() - it->mark->get_iter().get_line()) <= 2) { - last_it->view->get_buffer()->delete_mark(last_it->mark); - last_it->mark = it->mark; - it = cursor_locations.erase(it); - if (current_cursor_location != static_cast<size_t>(-1) && - current_cursor_location > cursor_locations_index) - --current_cursor_location; - } else { - ++it; - ++last_it; - ++cursor_locations_index; - } - } - } + // Remove start of cache if cache limit is exceeded + while (cursor_locations.size() > 10) { + cursor_locations.begin()->view->get_buffer()->delete_mark(cursor_locations.begin()->mark); + cursor_locations.erase(cursor_locations.begin()); + if (current_cursor_location != static_cast<size_t>(-1)) + --current_cursor_location; + } - // Remove start of cache if cache limit is exceeded - while (cursor_locations.size() > 10) { - cursor_locations.begin()->view->get_buffer()->delete_mark(cursor_locations.begin()->mark); - cursor_locations.erase(cursor_locations.begin()); - if (current_cursor_location != static_cast<size_t>(-1)) - --current_cursor_location; + if (current_cursor_location >= cursor_locations.size()) + current_cursor_location = cursor_locations.size() - 1; + }; + source_view->get_buffer()->signal_mark_set().connect( + [this, update_cursor_locations](const Gtk::TextBuffer::iterator &iter, + const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { + if (mark->get_name() == "insert") { + if (disable_next_update_cursor_locations) { + disable_next_update_cursor_locations = false; + return; + } + update_cursor_locations(iter); } - - if (current_cursor_location >= cursor_locations.size()) - current_cursor_location = cursor_locations.size() - 1; - }; - source_view->get_buffer()->signal_mark_set().connect( - [this, update_cursor_locations](const Gtk::TextBuffer::iterator &iter, - const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { - if (mark->get_name() == "insert") { - if (disable_next_update_cursor_locations) { - disable_next_update_cursor_locations = false; - return; - } - update_cursor_locations(iter); - } - }); - source_view->get_buffer()->signal_changed().connect([source_view, update_cursor_locations] { - update_cursor_locations(source_view->get_buffer()->get_insert()->get_iter()); - }); + }); + source_view->get_buffer()->signal_changed().connect([source_view, update_cursor_locations] { + update_cursor_locations(source_view->get_buffer()->get_insert()->get_iter()); + }); #ifdef JUCI_ENABLE_DEBUG - if(dynamic_cast<Source::ClangView*>(source_view) || (source_view->language && source_view->language->get_id()=="rust")) { - source_view->toggle_breakpoint=[source_view](int line_nr) { - if(source_view->get_source_buffer()->get_source_marks_at_line(line_nr, "debug_breakpoint").size()>0) { - auto start_iter=source_view->get_buffer()->get_iter_at_line(line_nr); - auto end_iter=source_view->get_iter_at_line_end(line_nr); - source_view->get_source_buffer()->remove_source_marks(start_iter, end_iter, "debug_breakpoint"); - source_view->get_source_buffer()->remove_source_marks(start_iter, end_iter, "debug_breakpoint_and_stop"); - if(Project::current && Project::debugging) - Project::current->debug_remove_breakpoint(source_view->file_path, line_nr+1, source_view->get_buffer()->get_line_count()+1); - } - else { - auto iter=source_view->get_buffer()->get_iter_at_line(line_nr); - source_view->get_source_buffer()->create_source_mark("debug_breakpoint", iter); - if(source_view->get_source_buffer()->get_source_marks_at_line(line_nr, "debug_stop").size()>0) - source_view->get_source_buffer()->create_source_mark("debug_breakpoint_and_stop", iter); - if(Project::current && Project::debugging) - Project::current->debug_add_breakpoint(source_view->file_path, line_nr+1); - } - }; - } + if(dynamic_cast<Source::ClangView*>(source_view) || (source_view->language && source_view->language->get_id()=="rust")) { + source_view->toggle_breakpoint=[source_view](int line_nr) { + if(source_view->get_source_buffer()->get_source_marks_at_line(line_nr, "debug_breakpoint").size()>0) { + auto start_iter=source_view->get_buffer()->get_iter_at_line(line_nr); + auto end_iter=source_view->get_iter_at_line_end(line_nr); + source_view->get_source_buffer()->remove_source_marks(start_iter, end_iter, "debug_breakpoint"); + source_view->get_source_buffer()->remove_source_marks(start_iter, end_iter, "debug_breakpoint_and_stop"); + if(Project::current && Project::debugging) + Project::current->debug_remove_breakpoint(source_view->file_path, line_nr+1, source_view->get_buffer()->get_line_count()+1); + } + else { + auto iter=source_view->get_buffer()->get_iter_at_line(line_nr); + source_view->get_source_buffer()->create_source_mark("debug_breakpoint", iter); + if(source_view->get_source_buffer()->get_source_marks_at_line(line_nr, "debug_stop").size()>0) + source_view->get_source_buffer()->create_source_mark("debug_breakpoint_and_stop", iter); + if(Project::current && Project::debugging) + Project::current->debug_add_breakpoint(source_view->file_path, line_nr+1); + } + }; + } #endif - source_view->signal_focus_in_event().connect([this, source_view](GdkEventFocus *) { - set_current_view(source_view); - return false; - }); - - if (notebook_index == static_cast<size_t>(-1)) { - if (!split) - notebook_index = 0; - else if (notebooks[0].get_n_pages() == 0) - notebook_index = 0; - else if (notebooks[1].get_n_pages() == 0) - notebook_index = 1; - else if (last_view) - notebook_index = get_notebook_page(get_index(last_view)).first; - } - auto ¬ebook = notebooks[notebook_index]; - - notebook.append_page(*hboxes.back(), *tab_labels.back()); - - notebook.set_tab_reorderable(*hboxes.back(), true); - notebook.set_tab_detachable(*hboxes.back(), true); - show_all_children(); - - notebook.set_current_page(notebook.get_n_pages() - 1); - last_index = -1; - if (last_view) { - auto index = get_index(last_view); - auto notebook_page = get_notebook_page(index); - if (notebook_page.first == notebook_index) - last_index = index; - } - - set_focus_child(*source_views.back()); - focus_view(source_view); + source_view->signal_focus_in_event().connect([this, source_view](GdkEventFocus *) { + set_current_view(source_view); + return false; + }); + + if (notebook_index == static_cast<size_t>(-1)) { + if (!split) + notebook_index = 0; + else if (notebooks[0].get_n_pages() == 0) + notebook_index = 0; + else if (notebooks[1].get_n_pages() == 0) + notebook_index = 1; + else if (last_view) + notebook_index = get_notebook_page(get_index(last_view)).first; + } + auto ¬ebook = notebooks[notebook_index]; + + notebook.append_page(*hboxes.back(), *tab_labels.back()); + + notebook.set_tab_reorderable(*hboxes.back(), true); + notebook.set_tab_detachable(*hboxes.back(), true); + show_all_children(); + + notebook.set_current_page(notebook.get_n_pages() - 1); + last_index = -1; + if (last_view) { + auto index = get_index(last_view); + auto notebook_page = get_notebook_page(index); + if (notebook_page.first == notebook_index) + last_index = index; + } + + set_focus_child(*source_views.back()); + focus_view(source_view); } void Notebook::configure(size_t index) { - auto source_font_description = Pango::FontDescription(Config::get().source.font); - auto source_map_font_desc = Pango::FontDescription( - static_cast<std::string>(source_font_description.get_family()) + " " + Config::get().source.map_font_size); - source_maps.at(index)->override_font(source_map_font_desc); - if (Config::get().source.show_map) { - if (hboxes.at(index)->get_children().size() == 1) - hboxes.at(index)->pack_end(*source_maps.at(index), Gtk::PACK_SHRINK); - } else if (hboxes.at(index)->get_children().size() == 2) - hboxes.at(index)->remove(*source_maps.at(index)); + auto source_font_description = Pango::FontDescription(Config::get().source.font); + auto source_map_font_desc = Pango::FontDescription( + static_cast<std::string>(source_font_description.get_family()) + " " + Config::get().source.map_font_size); + source_maps.at(index)->override_font(source_map_font_desc); + if (Config::get().source.show_map) { + if (hboxes.at(index)->get_children().size() == 1) + hboxes.at(index)->pack_end(*source_maps.at(index), Gtk::PACK_SHRINK); + } else if (hboxes.at(index)->get_children().size() == 2) + hboxes.at(index)->remove(*source_maps.at(index)); } bool Notebook::save(size_t index) { - if (!source_views[index]->save()) - return false; - Project::on_save(index); - return true; + if (!source_views[index]->save()) + return false; + Project::on_save(index); + return true; } bool Notebook::save_current() { - if (auto view = get_current_view()) - return save(get_index(view)); - return false; + if (auto view = get_current_view()) + return save(get_index(view)); + return false; } bool Notebook::close(size_t index) { - if (auto view = get_view(index)) { - if (view->get_buffer()->get_modified()) { - if (!save_modified_dialog(index)) - return false; - } - if (view == get_current_view()) { - bool focused = false; - if (last_index != static_cast<size_t>(-1)) { - auto notebook_page = get_notebook_page(last_index); - if (notebook_page.first == get_notebook_page(get_index(view)).first) { - focus_view(source_views[last_index]); - notebooks[notebook_page.first].set_current_page(notebook_page.second); - last_index = -1; - focused = true; - } - } - if (!focused) { - auto notebook_page = get_notebook_page(get_index(view)); - if (notebook_page.second > 0) - focus_view(get_view(notebook_page.first, notebook_page.second - 1)); - else { - size_t notebook_index = notebook_page.first == 0 ? 1 : 0; - if (notebooks[notebook_index].get_n_pages() > 0) - focus_view(get_view(notebook_index, notebooks[notebook_index].get_current_page())); - else - set_current_view(nullptr); - } - } - } else if (index == last_index) - last_index = -1; - else if (index < last_index && last_index != static_cast<size_t>(-1)) - last_index--; - - auto notebook_page = get_notebook_page(index); - notebooks[notebook_page.first].remove_page(notebook_page.second); - source_maps.erase(source_maps.begin() + index); - - if (on_close_page) - on_close_page(view); - - delete_cursor_locations(view); - - SelectionDialog::get() = nullptr; - CompletionDialog::get() = nullptr; - - if (auto clang_view = dynamic_cast<Source::ClangView *>(view)) - clang_view->async_delete(); - else - delete view; - source_views.erase(source_views.begin() + index); - scrolled_windows.erase(scrolled_windows.begin() + index); - hboxes.erase(hboxes.begin() + index); - tab_labels.erase(tab_labels.begin() + index); + if (auto view = get_view(index)) { + if (view->get_buffer()->get_modified()) { + if (!save_modified_dialog(index)) + return false; } - return true; + if (view == get_current_view()) { + bool focused = false; + if (last_index != static_cast<size_t>(-1)) { + auto notebook_page = get_notebook_page(last_index); + if (notebook_page.first == get_notebook_page(get_index(view)).first) { + focus_view(source_views[last_index]); + notebooks[notebook_page.first].set_current_page(notebook_page.second); + last_index = -1; + focused = true; + } + } + if (!focused) { + auto notebook_page = get_notebook_page(get_index(view)); + if (notebook_page.second > 0) + focus_view(get_view(notebook_page.first, notebook_page.second - 1)); + else { + size_t notebook_index = notebook_page.first == 0 ? 1 : 0; + if (notebooks[notebook_index].get_n_pages() > 0) + focus_view(get_view(notebook_index, notebooks[notebook_index].get_current_page())); + else + set_current_view(nullptr); + } + } + } else if (index == last_index) + last_index = -1; + else if (index < last_index && last_index != static_cast<size_t>(-1)) + last_index--; + + auto notebook_page = get_notebook_page(index); + notebooks[notebook_page.first].remove_page(notebook_page.second); + source_maps.erase(source_maps.begin() + index); + + if (on_close_page) + on_close_page(view); + + delete_cursor_locations(view); + + SelectionDialog::get() = nullptr; + CompletionDialog::get() = nullptr; + + if (auto clang_view = dynamic_cast<Source::ClangView *>(view)) + clang_view->async_delete(); + else + delete view; + source_views.erase(source_views.begin() + index); + scrolled_windows.erase(scrolled_windows.begin() + index); + hboxes.erase(hboxes.begin() + index); + tab_labels.erase(tab_labels.begin() + index); + } + return true; } void Notebook::delete_cursor_locations(Source::View *view) { - size_t cursor_locations_index = 0; - for (auto it = cursor_locations.begin(); it != cursor_locations.end();) { - if (it->view == view) { - view->get_buffer()->delete_mark(it->mark); - it = cursor_locations.erase(it); - if (current_cursor_location != static_cast<size_t>(-1) && current_cursor_location > cursor_locations_index) - --current_cursor_location; - } else { - ++it; - ++cursor_locations_index; - } + size_t cursor_locations_index = 0; + for (auto it = cursor_locations.begin(); it != cursor_locations.end();) { + if (it->view == view) { + view->get_buffer()->delete_mark(it->mark); + it = cursor_locations.erase(it); + if (current_cursor_location != static_cast<size_t>(-1) && current_cursor_location > cursor_locations_index) + --current_cursor_location; + } else { + ++it; + ++cursor_locations_index; } - if (current_cursor_location >= cursor_locations.size()) - current_cursor_location = cursor_locations.size() - 1; + } + if (current_cursor_location >= cursor_locations.size()) + current_cursor_location = cursor_locations.size() - 1; } bool Notebook::close_current() { - return close(get_index(get_current_view())); + return close(get_index(get_current_view())); } void Notebook::next() { - if (auto view = get_current_view()) { - auto notebook_page = get_notebook_page(get_index(view)); - int page = notebook_page.second + 1; - if (page >= notebooks[notebook_page.first].get_n_pages()) - notebooks[notebook_page.first].set_current_page(0); - else - notebooks[notebook_page.first].set_current_page(page); - } + if (auto view = get_current_view()) { + auto notebook_page = get_notebook_page(get_index(view)); + int page = notebook_page.second + 1; + if (page >= notebooks[notebook_page.first].get_n_pages()) + notebooks[notebook_page.first].set_current_page(0); + else + notebooks[notebook_page.first].set_current_page(page); + } } void Notebook::previous() { - if (auto view = get_current_view()) { - auto notebook_page = get_notebook_page(get_index(view)); - int page = notebook_page.second - 1; - if (page < 0) - notebooks[notebook_page.first].set_current_page(notebooks[notebook_page.first].get_n_pages() - 1); - else - notebooks[notebook_page.first].set_current_page(page); - } + if (auto view = get_current_view()) { + auto notebook_page = get_notebook_page(get_index(view)); + int page = notebook_page.second - 1; + if (page < 0) + notebooks[notebook_page.first].set_current_page(notebooks[notebook_page.first].get_n_pages() - 1); + else + notebooks[notebook_page.first].set_current_page(page); + } } void Notebook::toggle_split() { - if (!split) { - pack2(notebooks[1], true, true); - set_position(get_width() / 2); - show_all(); - //Make sure the position is correct - //TODO: report bug to gtk if it is not fixed in gtk3.22 - Glib::signal_timeout().connect([this] { - set_position(get_width() / 2); - return false; - }, 200); - } else { - for (size_t c = size() - 1; c != static_cast<size_t>(-1); --c) { - auto notebook_index = get_notebook_page(c).first; - if (notebook_index == 1 && !close(c)) - return; - } - remove(notebooks[1]); + if (!split) { + pack2(notebooks[1], true, true); + set_position(get_width() / 2); + show_all(); + //Make sure the position is correct + //TODO: report bug to gtk if it is not fixed in gtk3.22 + Glib::signal_timeout().connect([this] { + set_position(get_width() / 2); + return false; + }, 200); + } else { + for (size_t c = size() - 1; c != static_cast<size_t>(-1); --c) { + auto notebook_index = get_notebook_page(c).first; + if (notebook_index == 1 && !close(c)) + return; } - split = !split; + remove(notebooks[1]); + } + split = !split; } void Notebook::toggle_tabs() { - //Show / Hide tabs for each notebook. - for (auto ¬ebook : Notebook::notebooks) - notebook.set_show_tabs(!notebook.get_show_tabs()); + //Show / Hide tabs for each notebook. + for (auto ¬ebook : Notebook::notebooks) + notebook.set_show_tabs(!notebook.get_show_tabs()); } boost::filesystem::path Notebook::get_current_folder() { - if (!Directories::get().path.empty()) - return Directories::get().path; - else if (auto view = get_current_view()) - return view->file_path.parent_path(); - else - return boost::filesystem::path(); + if (!Directories::get().path.empty()) + return Directories::get().path; + else if (auto view = get_current_view()) + return view->file_path.parent_path(); + else + return boost::filesystem::path(); } std::vector<std::pair<size_t, Source::View *>> Notebook::get_notebook_views() { - std::vector<std::pair<size_t, Source::View *>> notebook_views; - for (size_t notebook_index = 0; notebook_index < notebooks.size(); ++notebook_index) { - for (int page = 0; page < notebooks[notebook_index].get_n_pages(); ++page) { - if (auto view = get_view(notebook_index, page)) - notebook_views.emplace_back(notebook_index, view); - } + std::vector<std::pair<size_t, Source::View *>> notebook_views; + for (size_t notebook_index = 0; notebook_index < notebooks.size(); ++notebook_index) { + for (int page = 0; page < notebooks[notebook_index].get_n_pages(); ++page) { + if (auto view = get_view(notebook_index, page)) + notebook_views.emplace_back(notebook_index, view); } - return notebook_views; + } + return notebook_views; } void Notebook::update_status(Source::BaseView *view) { - if (view->update_status_location) - view->update_status_location(view); - if (view->update_status_file_path) - view->update_status_file_path(view); - if (view->update_status_branch) - view->update_status_branch(view); - if (view->update_status_diagnostics) - view->update_status_diagnostics(view); - if (view->update_status_state) - view->update_status_state(view); + if (view->update_status_location) + view->update_status_location(view); + if (view->update_status_file_path) + view->update_status_file_path(view); + if (view->update_status_branch) + view->update_status_branch(view); + if (view->update_status_diagnostics) + view->update_status_diagnostics(view); + if (view->update_status_state) + view->update_status_state(view); } void Notebook::clear_status() { - status_location.set_text(""); - status_file_path.set_text(""); - status_branch.set_text(""); - status_diagnostics.set_text(""); - status_state.set_text(""); + status_location.set_text(""); + status_file_path.set_text(""); + status_branch.set_text(""); + status_diagnostics.set_text(""); + status_state.set_text(""); } size_t Notebook::get_index(Source::View *view) { - for (size_t c = 0; c < size(); ++c) { - if (source_views[c] == view) - return c; - } - return -1; + for (size_t c = 0; c < size(); ++c) { + if (source_views[c] == view) + return c; + } + return -1; } Source::View *Notebook::get_view(size_t notebook_index, int page) { - if (notebook_index == static_cast<size_t>(-1) || notebook_index >= notebooks.size() || - page < 0 || page >= notebooks[notebook_index].get_n_pages()) - return nullptr; - auto hbox = dynamic_cast<Gtk::Box *>(notebooks[notebook_index].get_nth_page(page)); - auto scrolled_window = dynamic_cast<Gtk::ScrolledWindow *>(hbox->get_children()[0]); - return dynamic_cast<Source::View *>(scrolled_window->get_children()[0]); + if (notebook_index == static_cast<size_t>(-1) || notebook_index >= notebooks.size() || + page < 0 || page >= notebooks[notebook_index].get_n_pages()) + return nullptr; + auto hbox = dynamic_cast<Gtk::Box *>(notebooks[notebook_index].get_nth_page(page)); + auto scrolled_window = dynamic_cast<Gtk::ScrolledWindow *>(hbox->get_children()[0]); + return dynamic_cast<Source::View *>(scrolled_window->get_children()[0]); } void Notebook::focus_view(Source::View *view) { - intermediate_view = view; - view->grab_focus(); + intermediate_view = view; + view->grab_focus(); } std::pair<size_t, int> Notebook::get_notebook_page(size_t index) { - if (index >= hboxes.size()) - return {-1, -1}; - for (size_t c = 0; c < notebooks.size(); ++c) { - auto page_num = notebooks[c].page_num(*hboxes[index]); - if (page_num >= 0) - return {c, page_num}; - } + if (index >= hboxes.size()) return {-1, -1}; + for (size_t c = 0; c < notebooks.size(); ++c) { + auto page_num = notebooks[c].page_num(*hboxes[index]); + if (page_num >= 0) + return {c, page_num}; + } + return {-1, -1}; } void Notebook::set_current_view(Source::View *view) { - intermediate_view = nullptr; - if (current_view != view) { - if (auto view = get_current_view()) { - view->hide_tooltips(); - view->hide_dialogs(); - } - current_view = view; - if (view && on_change_page) - on_change_page(view); + intermediate_view = nullptr; + if (current_view != view) { + if (auto view = get_current_view()) { + view->hide_tooltips(); + view->hide_dialogs(); } + current_view = view; + if (view && on_change_page) + on_change_page(view); + } } bool Notebook::save_modified_dialog(size_t index) { - Gtk::MessageDialog dialog(*static_cast<Gtk::Window *>(get_toplevel()), "Save file!", false, Gtk::MESSAGE_QUESTION, - Gtk::BUTTONS_YES_NO); - dialog.set_default_response(Gtk::RESPONSE_YES); - dialog.set_secondary_text("Do you want to save: " + get_view(index)->file_path.string() + " ?"); - int result = dialog.run(); - if (result == Gtk::RESPONSE_YES) { - return save(index); - } else if (result == Gtk::RESPONSE_NO) { - return true; - } else { - return false; - } + Gtk::MessageDialog dialog(*static_cast<Gtk::Window *>(get_toplevel()), "Save file!", false, Gtk::MESSAGE_QUESTION, + Gtk::BUTTONS_YES_NO); + dialog.set_default_response(Gtk::RESPONSE_YES); + dialog.set_secondary_text("Do you want to save: " + get_view(index)->file_path.string() + " ?"); + int result = dialog.run(); + if (result == Gtk::RESPONSE_YES) { + return save(index); + } else if (result == Gtk::RESPONSE_NO) { + return true; + } else { + return false; + } } diff --git a/src/notebook.h b/src/notebook.h index 9bf1cdc4..47c89bfb 100644 --- a/src/notebook.h +++ b/src/notebook.h @@ -8,106 +8,106 @@ #include <sigc++/sigc++.h> class Notebook : public Gtk::Paned { - class TabLabel : public Gtk::EventBox { - public: - TabLabel(std::function<void()> on_close); + class TabLabel : public Gtk::EventBox { + public: + TabLabel(std::function<void()> on_close); - Gtk::Label label; - }; + Gtk::Label label; + }; - class CursorLocation { - public: - CursorLocation(Source::View *view, Glib::RefPtr<Gtk::TextBuffer::Mark> mark) : view(view), mark(mark) {} + class CursorLocation { + public: + CursorLocation(Source::View *view, Glib::RefPtr<Gtk::TextBuffer::Mark> mark) : view(view), mark(mark) {} - Source::View *view; - Glib::RefPtr<Gtk::TextBuffer::Mark> mark; - }; + Source::View *view; + Glib::RefPtr<Gtk::TextBuffer::Mark> mark; + }; private: - Notebook(); + Notebook(); public: - static Notebook &get() { - static Notebook singleton; - return singleton; - } + static Notebook &get() { + static Notebook singleton; + return singleton; + } - size_t size(); + size_t size(); - Source::View *get_view(size_t index); + Source::View *get_view(size_t index); - Source::View *get_current_view(); + Source::View *get_current_view(); - std::vector<Source::View *> &get_views(); + std::vector<Source::View *> &get_views(); - void open(const boost::filesystem::path &file_path, size_t notebook_index = -1); + void open(const boost::filesystem::path &file_path, size_t notebook_index = -1); - void configure(size_t index); + void configure(size_t index); - bool save(size_t index); + bool save(size_t index); - bool save_current(); + bool save_current(); - bool close(size_t index); + bool close(size_t index); - bool close_current(); + bool close_current(); - void next(); + void next(); - void previous(); + void previous(); - void toggle_split(); + void toggle_split(); - /// Hide/Show tabs. - void toggle_tabs(); + /// Hide/Show tabs. + void toggle_tabs(); - boost::filesystem::path get_current_folder(); + boost::filesystem::path get_current_folder(); - std::vector<std::pair<size_t, Source::View *>> get_notebook_views(); + std::vector<std::pair<size_t, Source::View *>> get_notebook_views(); - Gtk::Label status_location; - Gtk::Label status_file_path; - Gtk::Label status_branch; - Gtk::Label status_diagnostics; - Gtk::Label status_state; + Gtk::Label status_location; + Gtk::Label status_file_path; + Gtk::Label status_branch; + Gtk::Label status_diagnostics; + Gtk::Label status_state; - void update_status(Source::BaseView *view); + void update_status(Source::BaseView *view); - void clear_status(); + void clear_status(); - std::function<void(Source::View *)> on_change_page; - std::function<void(Source::View *)> on_close_page; + std::function<void(Source::View *)> on_change_page; + std::function<void(Source::View *)> on_close_page; - /// Cursor history - std::vector<CursorLocation> cursor_locations; - size_t current_cursor_location = -1; - bool disable_next_update_cursor_locations = false; + /// Cursor history + std::vector<CursorLocation> cursor_locations; + size_t current_cursor_location = -1; + bool disable_next_update_cursor_locations = false; - void delete_cursor_locations(Source::View *view); + void delete_cursor_locations(Source::View *view); private: - size_t get_index(Source::View *view); + size_t get_index(Source::View *view); - Source::View *get_view(size_t notebook_index, int page); + Source::View *get_view(size_t notebook_index, int page); - void focus_view(Source::View *view); + void focus_view(Source::View *view); - std::pair<size_t, int> get_notebook_page(size_t index); + std::pair<size_t, int> get_notebook_page(size_t index); - std::vector<Gtk::Notebook> notebooks; - std::vector<Source::View *> source_views; //Is NOT freed in destructor, this is intended for quick program exit. - std::vector<std::unique_ptr<Gtk::Widget> > source_maps; - std::vector<std::unique_ptr<Gtk::ScrolledWindow> > scrolled_windows; - std::vector<std::unique_ptr<Gtk::Box> > hboxes; - std::vector<std::unique_ptr<TabLabel> > tab_labels; + std::vector<Gtk::Notebook> notebooks; + std::vector<Source::View *> source_views; //Is NOT freed in destructor, this is intended for quick program exit. + std::vector<std::unique_ptr<Gtk::Widget> > source_maps; + std::vector<std::unique_ptr<Gtk::ScrolledWindow> > scrolled_windows; + std::vector<std::unique_ptr<Gtk::Box> > hboxes; + std::vector<std::unique_ptr<TabLabel> > tab_labels; - bool split = false; - size_t last_index = -1; + bool split = false; + size_t last_index = -1; - void set_current_view(Source::View *view); + void set_current_view(Source::View *view); - Source::View *current_view = nullptr; - Source::View *intermediate_view = nullptr; + Source::View *current_view = nullptr; + Source::View *intermediate_view = nullptr; - bool save_modified_dialog(size_t index); + bool save_modified_dialog(size_t index); }; diff --git a/src/project.cc b/src/project.cc index 61976041..e1bb2e61 100644 --- a/src/project.cc +++ b/src/project.cc @@ -30,231 +30,231 @@ std::shared_ptr<Project::Base> Project::current; std::unique_ptr<Project::DebugOptions> Project::Base::debug_options; Gtk::Label &Project::debug_status_label() { - static Gtk::Label label; - return label; + static Gtk::Label label; + return label; } void Project::save_files(const boost::filesystem::path &path) { - for (size_t c = 0; c < Notebook::get().size(); c++) { - auto view = Notebook::get().get_view(c); - if (view->get_buffer()->get_modified()) { - if (filesystem::file_in_path(view->file_path, path)) - Notebook::get().save(c); - } + for (size_t c = 0; c < Notebook::get().size(); c++) { + auto view = Notebook::get().get_view(c); + if (view->get_buffer()->get_modified()) { + if (filesystem::file_in_path(view->file_path, path)) + Notebook::get().save(c); } + } } void Project::on_save(size_t index) { - auto view = Notebook::get().get_view(index); - if (!view) - return; - boost::filesystem::path build_path; - if (view->language && view->language->get_id() == "cmake") { - if (view->file_path.filename() == "CMakeLists.txt") - build_path = view->file_path; - else - build_path = filesystem::find_file_in_path_parents("CMakeLists.txt", view->file_path.parent_path()); - } else if (view->language && view->language->get_id() == "meson") { - if (view->file_path.filename() == "meson.build") - build_path = view->file_path; - else - build_path = filesystem::find_file_in_path_parents("meson.build", view->file_path.parent_path()); - } + auto view = Notebook::get().get_view(index); + if (!view) + return; + boost::filesystem::path build_path; + if (view->language && view->language->get_id() == "cmake") { + if (view->file_path.filename() == "CMakeLists.txt") + build_path = view->file_path; + else + build_path = filesystem::find_file_in_path_parents("CMakeLists.txt", view->file_path.parent_path()); + } else if (view->language && view->language->get_id() == "meson") { + if (view->file_path.filename() == "meson.build") + build_path = view->file_path; + else + build_path = filesystem::find_file_in_path_parents("meson.build", view->file_path.parent_path()); + } - if (!build_path.empty()) { - auto build = Build::create(build_path); - if (dynamic_cast<CMakeBuild *>(build.get()) || dynamic_cast<MesonBuild *>(build.get())) { - build->update_default(true); - Usages::Clang::erase_all_caches_for_project(build->project_path, build->get_default_path()); - boost::system::error_code ec; - if (boost::filesystem::exists(build->get_debug_path()), ec) - build->update_debug(true); - - for (size_t c = 0; c < Notebook::get().size(); c++) { - auto source_view = Notebook::get().get_view(c); - if (auto source_clang_view = dynamic_cast<Source::ClangView *>(source_view)) { - if (filesystem::file_in_path(source_clang_view->file_path, build->project_path)) - source_clang_view->full_reparse_needed = true; - } - } + if (!build_path.empty()) { + auto build = Build::create(build_path); + if (dynamic_cast<CMakeBuild *>(build.get()) || dynamic_cast<MesonBuild *>(build.get())) { + build->update_default(true); + Usages::Clang::erase_all_caches_for_project(build->project_path, build->get_default_path()); + boost::system::error_code ec; + if (boost::filesystem::exists(build->get_debug_path()), ec) + build->update_debug(true); + + for (size_t c = 0; c < Notebook::get().size(); c++) { + auto source_view = Notebook::get().get_view(c); + if (auto source_clang_view = dynamic_cast<Source::ClangView *>(source_view)) { + if (filesystem::file_in_path(source_clang_view->file_path, build->project_path)) + source_clang_view->full_reparse_needed = true; } + } } + } } void Project::debug_update_status(const std::string &new_debug_status) { - debug_status = new_debug_status; - if (debug_status.empty()) - debug_status_label().set_text(""); - else - debug_status_label().set_text(debug_status); - debug_activate_menu_items(); + debug_status = new_debug_status; + if (debug_status.empty()) + debug_status_label().set_text(""); + else + debug_status_label().set_text(debug_status); + debug_activate_menu_items(); } void Project::debug_activate_menu_items() { - auto &menu = Menu::get(); - auto view = Notebook::get().get_current_view(); - 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_toggle_breakpoint"]->set_enabled(view && view->toggle_breakpoint); - menu.actions["debug_goto_stop"]->set_enabled(!debug_status.empty()); + auto &menu = Menu::get(); + auto view = Notebook::get().get_current_view(); + 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_toggle_breakpoint"]->set_enabled(view && view->toggle_breakpoint); + menu.actions["debug_goto_stop"]->set_enabled(!debug_status.empty()); } void Project::debug_update_stop() { - if (!debug_last_stop_file_path.empty()) { - for (size_t 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"); - view->get_source_buffer()->remove_source_marks(view->get_buffer()->begin(), view->get_buffer()->end(), - "debug_breakpoint_and_stop"); - break; - } - } - } - //Add debug stop source mark - debug_last_stop_file_path.clear(); + if (!debug_last_stop_file_path.empty()) { for (size_t 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 < view->get_buffer()->get_line_count()) { - auto iter = view->get_buffer()->get_iter_at_line(debug_stop.second.first); - view->get_source_buffer()->create_source_mark("debug_stop", iter); - if (view->get_source_buffer()->get_source_marks_at_iter(iter, "debug_breakpoint").size() > 0) - view->get_source_buffer()->create_source_mark("debug_breakpoint_and_stop", iter); - debug_last_stop_file_path = debug_stop.first; - } - break; - } + 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"); + view->get_source_buffer()->remove_source_marks(view->get_buffer()->begin(), view->get_buffer()->end(), + "debug_breakpoint_and_stop"); + break; + } } + } + //Add debug stop source mark + debug_last_stop_file_path.clear(); + for (size_t 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 < view->get_buffer()->get_line_count()) { + auto iter = view->get_buffer()->get_iter_at_line(debug_stop.second.first); + view->get_source_buffer()->create_source_mark("debug_stop", iter); + if (view->get_source_buffer()->get_source_marks_at_iter(iter, "debug_breakpoint").size() > 0) + view->get_source_buffer()->create_source_mark("debug_breakpoint_and_stop", iter); + debug_last_stop_file_path = debug_stop.first; + } + break; + } + } } std::shared_ptr<Project::Base> Project::create() { - std::unique_ptr<Project::Build> build; - - if (auto view = Notebook::get().get_current_view()) { - build = Build::create(view->file_path); - if (view->language) { - auto language_id = view->language->get_id(); - if (language_id == "markdown") - return std::shared_ptr<Project::Base>(new Project::Markdown(std::move(build))); - if (language_id == "python") - return std::shared_ptr<Project::Base>(new Project::Python(std::move(build))); - if (language_id == "js") - return std::shared_ptr<Project::Base>(new Project::JavaScript(std::move(build))); - if (language_id == "html") - return std::shared_ptr<Project::Base>(new Project::HTML(std::move(build))); - } - } else - build = Build::create(Directories::get().path); - - if (dynamic_cast<CMakeBuild *>(build.get()) || dynamic_cast<MesonBuild *>(build.get())) - return std::shared_ptr<Project::Base>(new Project::Clang(std::move(build))); - else if (dynamic_cast<CargoBuild *>(build.get())) - return std::shared_ptr<Project::Base>(new Project::Rust(std::move(build))); - else if (dynamic_cast<NpmBuild *>(build.get())) + std::unique_ptr<Project::Build> build; + + if (auto view = Notebook::get().get_current_view()) { + build = Build::create(view->file_path); + if (view->language) { + auto language_id = view->language->get_id(); + if (language_id == "markdown") + return std::shared_ptr<Project::Base>(new Project::Markdown(std::move(build))); + if (language_id == "python") + return std::shared_ptr<Project::Base>(new Project::Python(std::move(build))); + if (language_id == "js") return std::shared_ptr<Project::Base>(new Project::JavaScript(std::move(build))); - else - return std::shared_ptr<Project::Base>(new Project::Base(std::move(build))); + if (language_id == "html") + return std::shared_ptr<Project::Base>(new Project::HTML(std::move(build))); + } + } else + build = Build::create(Directories::get().path); + + if (dynamic_cast<CMakeBuild *>(build.get()) || dynamic_cast<MesonBuild *>(build.get())) + return std::shared_ptr<Project::Base>(new Project::Clang(std::move(build))); + else if (dynamic_cast<CargoBuild *>(build.get())) + return std::shared_ptr<Project::Base>(new Project::Rust(std::move(build))); + else if (dynamic_cast<NpmBuild *>(build.get())) + return std::shared_ptr<Project::Base>(new Project::JavaScript(std::move(build))); + else + return std::shared_ptr<Project::Base>(new Project::Base(std::move(build))); } std::pair<std::string, std::string> Project::Base::get_run_arguments() { - Info::get().print("Could not find a supported project"); - return {"", ""}; + Info::get().print("Could not find a supported project"); + return {"", ""}; } void Project::Base::compile() { - Info::get().print("Could not find a supported project"); + Info::get().print("Could not find a supported project"); } void Project::Base::compile_and_run() { - Info::get().print("Could not find a supported project"); + Info::get().print("Could not find a supported project"); } void Project::Base::recreate_build() { - Info::get().print("Could not find a supported project"); + Info::get().print("Could not find a supported project"); } void Project::Base::show_symbols() { - auto view = Notebook::get().get_current_view(); - - boost::filesystem::path search_path; - if (view) - search_path = view->file_path.parent_path(); - else if (!Directories::get().path.empty()) - search_path = Directories::get().path; - else { - boost::system::error_code ec; - search_path = boost::filesystem::current_path(ec); - if (ec) { - Terminal::get().print("Error: could not find current path\n", true); - return; - } + auto view = Notebook::get().get_current_view(); + + boost::filesystem::path search_path; + if (view) + search_path = view->file_path.parent_path(); + else if (!Directories::get().path.empty()) + search_path = Directories::get().path; + else { + boost::system::error_code ec; + search_path = boost::filesystem::current_path(ec); + if (ec) { + Terminal::get().print("Error: could not find current path\n", true); + return; } - auto pair = Ctags::get_result(search_path); + } + auto pair = Ctags::get_result(search_path); - auto path = std::move(pair.first); - auto stream = std::move(pair.second); - stream->seekg(0, std::ios::end); - if (stream->tellg() == 0) { - Info::get().print("No symbols found in current project"); - return; - } - stream->seekg(0, std::ios::beg); + auto path = std::move(pair.first); + auto stream = std::move(pair.second); + stream->seekg(0, std::ios::end); + if (stream->tellg() == 0) { + Info::get().print("No symbols found in current project"); + return; + } + stream->seekg(0, std::ios::beg); - if (view) { - auto dialog_iter = view->get_iter_for_dialog(); - SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); - } else - SelectionDialog::create(true, true); + if (view) { + auto dialog_iter = view->get_iter_for_dialog(); + SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); + } else + SelectionDialog::create(true, true); - std::vector<Source::Offset> rows; + std::vector<Source::Offset> rows; - std::string line; - while (std::getline(*stream, line)) { - auto location = Ctags::get_location(line, true); + std::string line; + while (std::getline(*stream, line)) { + auto location = Ctags::get_location(line, true); - std::string row = - location.file_path.string() + ":" + std::to_string(location.line + 1) + ": " + location.source; - rows.emplace_back(Source::Offset(location.line, location.index, location.file_path)); - SelectionDialog::get()->add_row(row); - } + std::string row = + location.file_path.string() + ":" + std::to_string(location.line + 1) + ": " + location.source; + rows.emplace_back(Source::Offset(location.line, location.index, location.file_path)); + SelectionDialog::get()->add_row(row); + } - if (rows.size() == 0) - return; - SelectionDialog::get()->on_select = [rows = std::move(rows), path = std::move(path)](unsigned int index, - const std::string &text, - bool hide_window) { - if (index >= rows.size()) - return; - auto offset = rows[index]; - auto full_path = path / offset.file_path; - if (!boost::filesystem::is_regular_file(full_path)) - return; - Notebook::get().open(full_path); - auto view = Notebook::get().get_current_view(); - view->place_cursor_at_line_index(offset.line, offset.index); - view->scroll_to_cursor_delayed(view, true, false); - view->hide_tooltips(); - }; - if (view) - view->hide_tooltips(); - SelectionDialog::get()->show(); + if (rows.size() == 0) + return; + SelectionDialog::get()->on_select = [rows = std::move(rows), path = std::move(path)](unsigned int index, + const std::string &text, + bool hide_window) { + if (index >= rows.size()) + return; + auto offset = rows[index]; + auto full_path = path / offset.file_path; + if (!boost::filesystem::is_regular_file(full_path)) + return; + Notebook::get().open(full_path); + auto view = Notebook::get().get_current_view(); + view->place_cursor_at_line_index(offset.line, offset.index); + view->scroll_to_cursor_delayed(view, true, false); + view->hide_tooltips(); + }; + if (view) + view->hide_tooltips(); + SelectionDialog::get()->show(); } std::pair<std::string, std::string> Project::Base::debug_get_run_arguments() { - Info::get().print("Could not find a supported project"); - return {"", ""}; + Info::get().print("Could not find a supported project"); + return {"", ""}; } void Project::Base::debug_start() { - Info::get().print("Could not find a supported project"); + Info::get().print("Could not find a supported project"); } #ifdef JUCI_ENABLE_DEBUG @@ -671,371 +671,371 @@ void Project::LLDB::debug_cancel() { #endif void Project::LanguageProtocol::show_symbols() { - if (build->project_path.empty()) { - Info::get().print("Could not find project folder"); - return; - } + if (build->project_path.empty()) { + Info::get().print("Could not find project folder"); + return; + } - auto language_id = get_language_id(); - auto executable_name = language_id + "-language-server"; - if (filesystem::find_executable(executable_name).empty()) { - Info::get().print("Executable " + executable_name + " not found"); - return; - } + auto language_id = get_language_id(); + auto executable_name = language_id + "-language-server"; + if (filesystem::find_executable(executable_name).empty()) { + Info::get().print("Executable " + executable_name + " not found"); + return; + } - auto project_path = std::make_shared<boost::filesystem::path>(build->project_path); + auto project_path = std::make_shared<boost::filesystem::path>(build->project_path); - auto client = ::LanguageProtocol::Client::get(*project_path, language_id); - auto capabilities = client->initialize(nullptr); + auto client = ::LanguageProtocol::Client::get(*project_path, language_id); + auto capabilities = client->initialize(nullptr); - if (!capabilities.workspace_symbol) { - Info::get().print("Language server does not support workspace/symbol"); - return; - } + if (!capabilities.workspace_symbol) { + Info::get().print("Language server does not support workspace/symbol"); + return; + } - auto view = Notebook::get().get_current_view(); - if (view) { - auto dialog_iter = view->get_iter_for_dialog(); - SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); - } else - SelectionDialog::create(true, true); - - SelectionDialog::get()->on_hide = [] { - SelectionDialog::get()->on_search_entry_changed = nullptr; // To delete client object - }; + auto view = Notebook::get().get_current_view(); + if (view) { + auto dialog_iter = view->get_iter_for_dialog(); + SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); + } else + SelectionDialog::create(true, true); - auto offsets = std::make_shared<std::vector<Source::Offset>>(); - SelectionDialog::get()->on_search_entry_changed = [client, project_path, offsets](const std::string &text) { - if (text.size() > 1) - return; - else { - offsets->clear(); - SelectionDialog::get()->erase_rows(); - if (text.empty()) - return; - } - std::vector<std::string> names; - std::promise<void> result_processed; - client->write_request(nullptr, "workspace/symbol", "\"query\":\"" + text + "\"", - [&result_processed, &names, offsets, project_path]( - const boost::property_tree::ptree &result, bool error) { - if (!error) { - for (auto it = result.begin(); it != result.end(); ++it) { - auto name = it->second.get<std::string>("name", ""); - if (!name.empty()) { - auto location_it = it->second.find("location"); - if (location_it != it->second.not_found()) { - auto file = location_it->second.get<std::string>("uri", ""); - if (file.size() > 7) { - file.erase(0, 7); - auto range_it = location_it->second.find("range"); - if (range_it != location_it->second.not_found()) { - auto start_it = range_it->second.find("start"); - if (start_it != range_it->second.not_found()) { - try { - offsets->emplace_back(Source::Offset( - start_it->second.get<unsigned>("line"), - start_it->second.get<unsigned>("character"), - file)); - names.emplace_back(name); - } - catch (...) {} - } - } - } - } + SelectionDialog::get()->on_hide = [] { + SelectionDialog::get()->on_search_entry_changed = nullptr; // To delete client object + }; + + auto offsets = std::make_shared<std::vector<Source::Offset>>(); + SelectionDialog::get()->on_search_entry_changed = [client, project_path, offsets](const std::string &text) { + if (text.size() > 1) + return; + else { + offsets->clear(); + SelectionDialog::get()->erase_rows(); + if (text.empty()) + return; + } + std::vector<std::string> names; + std::promise<void> result_processed; + client->write_request(nullptr, "workspace/symbol", "\"query\":\"" + text + "\"", + [&result_processed, &names, offsets, project_path]( + const boost::property_tree::ptree &result, bool error) { + if (!error) { + for (auto it = result.begin(); it != result.end(); ++it) { + auto name = it->second.get<std::string>("name", ""); + if (!name.empty()) { + auto location_it = it->second.find("location"); + if (location_it != it->second.not_found()) { + auto file = location_it->second.get<std::string>("uri", ""); + if (file.size() > 7) { + file.erase(0, 7); + auto range_it = location_it->second.find("range"); + if (range_it != location_it->second.not_found()) { + auto start_it = range_it->second.find("start"); + if (start_it != range_it->second.not_found()) { + try { + offsets->emplace_back(Source::Offset( + start_it->second.get<unsigned>("line"), + start_it->second.get<unsigned>("character"), + file)); + names.emplace_back(name); } + catch (...) {} + } } + } } - result_processed.set_value(); - }); - result_processed.get_future().get(); - for (size_t c = 0; c < offsets->size() && c < names.size(); ++c) - SelectionDialog::get()->add_row( - filesystem::get_relative_path((*offsets)[c].file_path, *project_path).string() + ':' + - std::to_string((*offsets)[c].line + 1) + ':' + std::to_string((*offsets)[c].index + 1) + ": " + - names[c]); - }; - - SelectionDialog::get()->on_select = [offsets](unsigned int index, const std::string &text, bool hide_window) { - auto &offset = (*offsets)[index]; - if (!boost::filesystem::is_regular_file(offset.file_path)) - return; - Notebook::get().open(offset.file_path); - auto view = Notebook::get().get_current_view(); - view->place_cursor_at_line_offset(offset.line, offset.index); - view->scroll_to_cursor_delayed(view, true, false); - view->hide_tooltips(); - }; - - if (view) - view->hide_tooltips(); - SelectionDialog::get()->show(); + } + } + } + result_processed.set_value(); + }); + result_processed.get_future().get(); + for (size_t c = 0; c < offsets->size() && c < names.size(); ++c) + SelectionDialog::get()->add_row( + filesystem::get_relative_path((*offsets)[c].file_path, *project_path).string() + ':' + + std::to_string((*offsets)[c].line + 1) + ':' + std::to_string((*offsets)[c].index + 1) + ": " + + names[c]); + }; + + SelectionDialog::get()->on_select = [offsets](unsigned int index, const std::string &text, bool hide_window) { + auto &offset = (*offsets)[index]; + if (!boost::filesystem::is_regular_file(offset.file_path)) + return; + Notebook::get().open(offset.file_path); + auto view = Notebook::get().get_current_view(); + view->place_cursor_at_line_offset(offset.line, offset.index); + view->scroll_to_cursor_delayed(view, true, false); + view->hide_tooltips(); + }; + + if (view) + view->hide_tooltips(); + SelectionDialog::get()->show(); } std::pair<std::string, std::string> Project::Clang::get_run_arguments() { - auto build_path = build->get_default_path(); - if (build_path.empty()) - return {"", ""}; - - auto project_path = build->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 view = Notebook::get().get_current_view(); - auto executable = build->get_executable(view ? view->file_path : Directories::get().path); - - if (!executable.empty()) - arguments = filesystem::escape_argument(filesystem::get_short_path(executable).string()); - else - arguments = filesystem::escape_argument(filesystem::get_short_path(build->get_default_path()).string()); - } + auto build_path = build->get_default_path(); + if (build_path.empty()) + return {"", ""}; + + auto project_path = build->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; - return {project_path, arguments}; + if (arguments.empty()) { + auto view = Notebook::get().get_current_view(); + auto executable = build->get_executable(view ? view->file_path : Directories::get().path); + + if (!executable.empty()) + arguments = filesystem::escape_argument(filesystem::get_short_path(executable).string()); + else + arguments = filesystem::escape_argument(filesystem::get_short_path(build->get_default_path()).string()); + } + + return {project_path, arguments}; } void Project::Clang::compile() { - auto default_build_path = build->get_default_path(); - if (default_build_path.empty() || !build->update_default()) - return; + auto default_build_path = build->get_default_path(); + if (default_build_path.empty() || !build->update_default()) + return; - compiling = true; + compiling = true; - if (Config::get().project.clear_terminal_on_compile) - Terminal::get().clear(); + if (Config::get().project.clear_terminal_on_compile) + Terminal::get().clear(); - Terminal::get().print("Compiling project " + filesystem::get_short_path(build->project_path).string() + "\n"); - Terminal::get().async_process(build->get_compile_command(), default_build_path, [](int exit_status) { - compiling = false; - }); + Terminal::get().print("Compiling project " + filesystem::get_short_path(build->project_path).string() + "\n"); + Terminal::get().async_process(build->get_compile_command(), default_build_path, [](int exit_status) { + compiling = false; + }); } void Project::Clang::compile_and_run() { - auto default_build_path = build->get_default_path(); - if (default_build_path.empty() || !build->update_default()) - return; + auto default_build_path = build->get_default_path(); + if (default_build_path.empty() || !build->update_default()) + return; - auto project_path = build->project_path; - - 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()) { - auto view = Notebook::get().get_current_view(); - auto executable = build->get_executable(view ? view->file_path : Directories::get().path); - if (executable.empty()) { - Terminal::get().print("Warning: could not find executable.\n"); - Terminal::get().print( - "Solution: either use Project Set Run Arguments, or open a source file within a directory where an executable is defined.\n", - true); - return; - } - arguments = filesystem::escape_argument(filesystem::get_short_path(executable).string()); + auto project_path = build->project_path; + + 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()) { + auto view = Notebook::get().get_current_view(); + auto executable = build->get_executable(view ? view->file_path : Directories::get().path); + if (executable.empty()) { + Terminal::get().print("Warning: could not find executable.\n"); + Terminal::get().print( + "Solution: either use Project Set Run Arguments, or open a source file within a directory where an executable is defined.\n", + true); + return; } + arguments = filesystem::escape_argument(filesystem::get_short_path(executable).string()); + } - compiling = true; - - if (Config::get().project.clear_terminal_on_compile) - Terminal::get().clear(); - - Terminal::get().print("Compiling and running " + arguments + "\n"); - Terminal::get().async_process(build->get_compile_command(), default_build_path, - [arguments, project_path](int exit_status) { - compiling = false; - if (exit_status == EXIT_SUCCESS) { - Terminal::get().async_process(arguments, project_path, - [arguments](int exit_status) { - Terminal::get().async_print( - arguments + " returned: " + - std::to_string(exit_status) + '\n'); - }); - } - }); + compiling = true; + + if (Config::get().project.clear_terminal_on_compile) + Terminal::get().clear(); + + Terminal::get().print("Compiling and running " + arguments + "\n"); + Terminal::get().async_process(build->get_compile_command(), default_build_path, + [arguments, project_path](int exit_status) { + compiling = false; + if (exit_status == EXIT_SUCCESS) { + Terminal::get().async_process(arguments, project_path, + [arguments](int exit_status) { + Terminal::get().async_print( + arguments + " returned: " + + std::to_string(exit_status) + '\n'); + }); + } + }); } void Project::Clang::recreate_build() { - if (build->project_path.empty()) - return; - auto default_build_path = build->get_default_path(); - if (default_build_path.empty()) - return; + if (build->project_path.empty()) + return; + auto default_build_path = build->get_default_path(); + if (default_build_path.empty()) + return; - auto debug_build_path = build->get_debug_path(); - bool has_default_build = boost::filesystem::exists(default_build_path); - bool has_debug_build = !debug_build_path.empty() && boost::filesystem::exists(debug_build_path); - - if (has_default_build || has_debug_build) { - Gtk::MessageDialog dialog(*static_cast<Gtk::Window *>(Notebook::get().get_toplevel()), "Recreate Build", false, - Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO); - dialog.set_default_response(Gtk::RESPONSE_NO); - std::string message = "Are you sure you want to recreate "; - if (has_default_build) - message += default_build_path.string(); - if (has_debug_build) { - if (has_default_build) - message += " and "; - message += debug_build_path.string(); - } - dialog.set_secondary_text(message + "?"); - if (dialog.run() != Gtk::RESPONSE_YES) - return; - Usages::Clang::erase_all_caches_for_project(build->project_path, default_build_path); - try { - if (has_default_build) - boost::filesystem::remove_all(default_build_path); - if (has_debug_build) - boost::filesystem::remove_all(debug_build_path); - } - catch (const std::exception &e) { - Terminal::get().print(std::string("Error: could not remove build: ") + e.what() + "\n", true); - return; - } + auto debug_build_path = build->get_debug_path(); + bool has_default_build = boost::filesystem::exists(default_build_path); + bool has_debug_build = !debug_build_path.empty() && boost::filesystem::exists(debug_build_path); + + if (has_default_build || has_debug_build) { + Gtk::MessageDialog dialog(*static_cast<Gtk::Window *>(Notebook::get().get_toplevel()), "Recreate Build", false, + Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO); + dialog.set_default_response(Gtk::RESPONSE_NO); + std::string message = "Are you sure you want to recreate "; + if (has_default_build) + message += default_build_path.string(); + if (has_debug_build) { + if (has_default_build) + message += " and "; + message += debug_build_path.string(); + } + dialog.set_secondary_text(message + "?"); + if (dialog.run() != Gtk::RESPONSE_YES) + return; + Usages::Clang::erase_all_caches_for_project(build->project_path, default_build_path); + try { + if (has_default_build) + boost::filesystem::remove_all(default_build_path); + if (has_debug_build) + boost::filesystem::remove_all(debug_build_path); + } + catch (const std::exception &e) { + Terminal::get().print(std::string("Error: could not remove build: ") + e.what() + "\n", true); + return; } + } - build->update_default(true); - if (has_debug_build) - build->update_debug(true); + build->update_default(true); + if (has_debug_build) + build->update_debug(true); - for (size_t c = 0; c < Notebook::get().size(); c++) { - auto source_view = Notebook::get().get_view(c); - if (auto source_clang_view = dynamic_cast<Source::ClangView *>(source_view)) { - if (filesystem::file_in_path(source_clang_view->file_path, build->project_path)) - source_clang_view->full_reparse_needed = true; - } + for (size_t c = 0; c < Notebook::get().size(); c++) { + auto source_view = Notebook::get().get_view(c); + if (auto source_clang_view = dynamic_cast<Source::ClangView *>(source_view)) { + if (filesystem::file_in_path(source_clang_view->file_path, build->project_path)) + source_clang_view->full_reparse_needed = true; } + } - if (auto view = Notebook::get().get_current_view()) { - if (view->full_reparse_needed) - view->full_reparse(); - } + if (auto view = Notebook::get().get_current_view()) { + if (view->full_reparse_needed) + view->full_reparse(); + } } Project::Markdown::~Markdown() { - if (!last_temp_path.empty()) { - boost::filesystem::remove(last_temp_path); - last_temp_path = boost::filesystem::path(); - } + 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(); - } + 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, "command -v grip"); - if (exit_status == 0) { - auto command = "grip -b " + filesystem::escape_argument( - filesystem::get_short_path(Notebook::get().get_current_view()->file_path).string()); - Terminal::get().print("Running: " + command + " in a quiet background process\n"); - Terminal::get().async_process(command, "", nullptr, true); - } else - Terminal::get().print("Warning: install grip to preview Markdown files\n"); + std::stringstream stdin_stream, stdout_stream; + auto exit_status = Terminal::get().process(stdin_stream, stdout_stream, "command -v grip"); + if (exit_status == 0) { + auto command = "grip -b " + filesystem::escape_argument( + filesystem::get_short_path(Notebook::get().get_current_view()->file_path).string()); + Terminal::get().print("Running: " + command + " in a quiet background process\n"); + Terminal::get().async_process(command, "", nullptr, true); + } else + Terminal::get().print("Warning: install grip to preview Markdown files\n"); } void Project::Python::compile_and_run() { - auto command = Config::get().project.python_command + ' ' + filesystem::escape_argument( - filesystem::get_short_path(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'); - }); + auto command = Config::get().project.python_command + ' ' + filesystem::escape_argument( + filesystem::get_short_path(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() { - std::string command; - boost::filesystem::path path; - if (!build->project_path.empty()) { - command = "npm start"; - path = build->project_path; - } else { - auto view = Notebook::get().get_current_view(); - if (!view) { - Info::get().print("No executable found"); - return; - } - command = "node --harmony " + filesystem::escape_argument(filesystem::get_short_path(view->file_path).string()); - path = view->file_path.parent_path(); + std::string command; + boost::filesystem::path path; + if (!build->project_path.empty()) { + command = "npm start"; + path = build->project_path; + } else { + auto view = Notebook::get().get_current_view(); + if (!view) { + Info::get().print("No executable found"); + return; } - Terminal::get().print("Running " + command + "\n"); - Terminal::get().async_process(command, path, [command](int exit_status) { - Terminal::get().async_print(command + " returned: " + std::to_string(exit_status) + '\n'); - }); + command = "node --harmony " + filesystem::escape_argument(filesystem::get_short_path(view->file_path).string()); + path = view->file_path.parent_path(); + } + Terminal::get().print("Running " + command + "\n"); + Terminal::get().async_process(command, 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(); + auto uri = Notebook::get().get_current_view()->file_path.string(); #ifdef __APPLE__ - Terminal::get().process("open "+filesystem::escape_argument(uri)); + Terminal::get().process("open "+filesystem::escape_argument(uri)); #else #ifdef __linux - uri="file://"+uri; + uri="file://"+uri; #endif - GError *error = nullptr; + GError *error = nullptr; #if GTK_VERSION_GE(3, 22) - gtk_show_uri_on_window(nullptr, uri.c_str(), GDK_CURRENT_TIME, &error); + gtk_show_uri_on_window(nullptr, uri.c_str(), GDK_CURRENT_TIME, &error); #else - gtk_show_uri(nullptr, uri.c_str(), GDK_CURRENT_TIME, &error); + gtk_show_uri(nullptr, uri.c_str(), GDK_CURRENT_TIME, &error); #endif - g_clear_error(&error); + g_clear_error(&error); #endif } std::pair<std::string, std::string> Project::Rust::get_run_arguments() { - auto project_path = build->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; + auto project_path = build->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()) - arguments = filesystem::get_short_path(build->get_executable(project_path)).string(); + if (arguments.empty()) + arguments = filesystem::get_short_path(build->get_executable(project_path)).string(); - return {project_path, arguments}; + return {project_path, arguments}; } void Project::Rust::compile() { - compiling = true; + compiling = true; - if (Config::get().project.clear_terminal_on_compile) - Terminal::get().clear(); + if (Config::get().project.clear_terminal_on_compile) + Terminal::get().clear(); - Terminal::get().print("Compiling project " + filesystem::get_short_path(build->project_path).string() + "\n"); + Terminal::get().print("Compiling project " + filesystem::get_short_path(build->project_path).string() + "\n"); - auto command = build->get_compile_command(); - Terminal::get().async_process(command, build->project_path, [](int exit_status) { - compiling = false; - }); + auto command = build->get_compile_command(); + Terminal::get().async_process(command, build->project_path, [](int exit_status) { + compiling = false; + }); } void Project::Rust::compile_and_run() { - compiling = true; - - if (Config::get().project.clear_terminal_on_compile) - Terminal::get().clear(); - - auto arguments = get_run_arguments().second; - Terminal::get().print("Compiling and running " + arguments + "\n"); - - auto self = this->shared_from_this(); - Terminal::get().async_process(build->get_compile_command(), build->project_path, - [self, arguments = std::move(arguments)](int exit_status) { - compiling = false; - if (exit_status == EXIT_SUCCESS) { - Terminal::get().async_process(arguments, self->build->project_path, - [arguments](int exit_status) { - Terminal::get().async_print( - arguments + " returned: " + - std::to_string(exit_status) + '\n'); - }); - } - }); + compiling = true; + + if (Config::get().project.clear_terminal_on_compile) + Terminal::get().clear(); + + auto arguments = get_run_arguments().second; + Terminal::get().print("Compiling and running " + arguments + "\n"); + + auto self = this->shared_from_this(); + Terminal::get().async_process(build->get_compile_command(), build->project_path, + [self, arguments = std::move(arguments)](int exit_status) { + compiling = false; + if (exit_status == EXIT_SUCCESS) { + Terminal::get().async_process(arguments, self->build->project_path, + [arguments](int exit_status) { + Terminal::get().async_print( + arguments + " returned: " + + std::to_string(exit_status) + '\n'); + }); + } + }); } diff --git a/src/project.h b/src/project.h index 82c100ce..5a8c020f 100644 --- a/src/project.h +++ b/src/project.h @@ -10,200 +10,200 @@ #include "project_build.h" namespace Project { - class DebugRunArguments { - public: - std::string arguments; - bool remote_enabled; - std::string remote_host_port; - }; + class DebugRunArguments { + public: + std::string arguments; + bool remote_enabled; + std::string remote_host_port; + }; - class DebugOptions : public Gtk::Popover { - public: - DebugOptions() : Gtk::Popover(), vbox(Gtk::Orientation::ORIENTATION_VERTICAL) { add(vbox); } + class DebugOptions : public Gtk::Popover { + public: + DebugOptions() : Gtk::Popover(), vbox(Gtk::Orientation::ORIENTATION_VERTICAL) { add(vbox); } - Gtk::Box vbox; - }; + Gtk::Box vbox; + }; - Gtk::Label &debug_status_label(); + Gtk::Label &debug_status_label(); - void save_files(const boost::filesystem::path &path); + void save_files(const boost::filesystem::path &path); - void on_save(size_t index); + void on_save(size_t index); - extern boost::filesystem::path debug_last_stop_file_path; - extern std::unordered_map<std::string, std::string> run_arguments; - extern std::unordered_map<std::string, DebugRunArguments> debug_run_arguments; - extern std::atomic<bool> compiling; - extern std::atomic<bool> debugging; - extern std::pair<boost::filesystem::path, std::pair<int, int> > debug_stop; - extern std::string debug_status; + extern boost::filesystem::path debug_last_stop_file_path; + extern std::unordered_map<std::string, std::string> run_arguments; + extern std::unordered_map<std::string, DebugRunArguments> debug_run_arguments; + extern std::atomic<bool> compiling; + extern std::atomic<bool> debugging; + extern std::pair<boost::filesystem::path, std::pair<int, int> > debug_stop; + extern std::string debug_status; - void debug_update_status(const std::string &new_debug_status); + void debug_update_status(const std::string &new_debug_status); - void debug_activate_menu_items(); + void debug_activate_menu_items(); - void debug_update_stop(); + void debug_update_stop(); - class Base : public std::enable_shared_from_this<Base> { - protected: - static std::unique_ptr<DebugOptions> debug_options; - public: - Base() {} + class Base : public std::enable_shared_from_this<Base> { + protected: + static std::unique_ptr<DebugOptions> debug_options; + public: + Base() {} - Base(std::unique_ptr<Build> &&build) : build(std::move(build)) {} + Base(std::unique_ptr<Build> &&build) : build(std::move(build)) {} - virtual ~Base() {} + virtual ~Base() {} - std::unique_ptr<Build> build; + std::unique_ptr<Build> build; - Dispatcher dispatcher; + Dispatcher dispatcher; - virtual std::pair<std::string, std::string> get_run_arguments(); + virtual std::pair<std::string, std::string> get_run_arguments(); - virtual void compile(); + virtual void compile(); - virtual void compile_and_run(); + virtual void compile_and_run(); - virtual void recreate_build(); + virtual void recreate_build(); - virtual void show_symbols(); + virtual void show_symbols(); - virtual std::pair<std::string, std::string> debug_get_run_arguments(); + virtual std::pair<std::string, std::string> debug_get_run_arguments(); - virtual Project::DebugOptions *debug_get_options() { return nullptr; } + virtual Project::DebugOptions *debug_get_options() { return nullptr; } - Tooltips debug_variable_tooltips; + Tooltips debug_variable_tooltips; - virtual void debug_start(); + virtual void debug_start(); - virtual void debug_continue() {} + virtual void debug_continue() {} - virtual void debug_stop() {} + virtual void debug_stop() {} - virtual void debug_kill() {} + virtual void debug_kill() {} - virtual void debug_step_over() {} + virtual void debug_step_over() {} - virtual void debug_step_into() {} + virtual void debug_step_into() {} - virtual void debug_step_out() {} + virtual void debug_step_out() {} - virtual void debug_backtrace() {} + virtual void debug_backtrace() {} - virtual void debug_show_variables() {} + virtual void debug_show_variables() {} - virtual void debug_run_command(const std::string &command) {} + 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_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 void debug_remove_breakpoint(const boost::filesystem::path &file_path, int line_nr, int line_count) {} - virtual bool debug_is_running() { return false; } + virtual bool debug_is_running() { return false; } - virtual void debug_write(const std::string &buffer) {} + virtual void debug_write(const std::string &buffer) {} - virtual void debug_cancel() {} - }; + virtual void debug_cancel() {} + }; - class LLDB : public virtual Base { - public: - LLDB() {} + class LLDB : public virtual Base { + public: + LLDB() {} - ~LLDB() { dispatcher.disconnect(); } + ~LLDB() { dispatcher.disconnect(); } #ifdef JUCI_ENABLE_DEBUG - std::pair<std::string, std::string> debug_get_run_arguments() override; - Project::DebugOptions *debug_get_options() 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_cancel() override; + std::pair<std::string, std::string> debug_get_run_arguments() override; + Project::DebugOptions *debug_get_options() 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_cancel() override; #endif - }; + }; - class LanguageProtocol : public virtual Base { - public: - LanguageProtocol() {} + class LanguageProtocol : public virtual Base { + public: + LanguageProtocol() {} - virtual std::string get_language_id()=0; + virtual std::string get_language_id()=0; - void show_symbols() override; - }; + void show_symbols() override; + }; - class Clang : public LLDB { - public: - Clang(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} + class Clang : public LLDB { + public: + Clang(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} - std::pair<std::string, std::string> get_run_arguments() override; + std::pair<std::string, std::string> get_run_arguments() override; - void compile() override; + void compile() override; - void compile_and_run() override; + void compile_and_run() override; - void recreate_build() override; - }; + void recreate_build() override; + }; - class Markdown : public Base { - public: - Markdown(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} + class Markdown : public Base { + public: + Markdown(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} - ~Markdown(); + ~Markdown(); - boost::filesystem::path last_temp_path; + boost::filesystem::path last_temp_path; - void compile_and_run() override; - }; + void compile_and_run() override; + }; - class Python : public LanguageProtocol { - public: - Python(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} + class Python : public LanguageProtocol { + public: + Python(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} - void compile_and_run() override; + void compile_and_run() override; - std::string get_language_id() override { return "python"; } - }; + std::string get_language_id() override { return "python"; } + }; - class JavaScript : public LanguageProtocol { - public: - JavaScript(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} + class JavaScript : public LanguageProtocol { + public: + JavaScript(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} - void compile_and_run() override; + void compile_and_run() override; - std::string get_language_id() override { return "javascript"; } - }; + std::string get_language_id() override { return "javascript"; } + }; - class HTML : public Base { - public: - HTML(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} + class HTML : public Base { + public: + HTML(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} - void compile_and_run() override; - }; + void compile_and_run() override; + }; - class Rust : public LLDB, public LanguageProtocol { - public: - Rust(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} + class Rust : public LLDB, public LanguageProtocol { + public: + Rust(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} - std::pair<std::string, std::string> get_run_arguments() override; + std::pair<std::string, std::string> get_run_arguments() override; - void compile() override; + void compile() override; - void compile_and_run() override; + void compile_and_run() override; - std::string get_language_id() override { return "rust"; } - }; + std::string get_language_id() override { return "rust"; } + }; - std::shared_ptr<Base> create(); + std::shared_ptr<Base> create(); - extern std::shared_ptr<Base> current; + extern std::shared_ptr<Base> current; }; diff --git a/src/project_build.cc b/src/project_build.cc index d3294916..a1f6f003 100644 --- a/src/project_build.cc +++ b/src/project_build.cc @@ -3,136 +3,136 @@ #include "filesystem.h" std::unique_ptr<Project::Build> Project::Build::create(const boost::filesystem::path &path) { - auto search_path = boost::filesystem::is_directory(path) ? path : path.parent_path(); - - while (true) { - if (boost::filesystem::exists(search_path / "CMakeLists.txt")) { - std::unique_ptr<Project::Build> build(new CMakeBuild(path)); - if (!build->project_path.empty()) - return build; - else - return std::make_unique<Project::Build>(); - } - - if (boost::filesystem::exists(search_path / "meson.build")) { - std::unique_ptr<Project::Build> build(new MesonBuild(path)); - if (!build->project_path.empty()) - return build; - } - - if (boost::filesystem::exists(search_path / "Cargo.toml")) { - std::unique_ptr<Project::Build> build(new CargoBuild()); - build->project_path = search_path; - return build; - } - - if (boost::filesystem::exists(search_path / "package.json")) { - std::unique_ptr<Project::Build> build(new NpmBuild()); - build->project_path = search_path; - return build; - } - - if (search_path == search_path.root_directory()) - break; - search_path = search_path.parent_path(); + auto search_path = boost::filesystem::is_directory(path) ? path : path.parent_path(); + + while (true) { + if (boost::filesystem::exists(search_path / "CMakeLists.txt")) { + std::unique_ptr<Project::Build> build(new CMakeBuild(path)); + if (!build->project_path.empty()) + return build; + else + return std::make_unique<Project::Build>(); } - return std::make_unique<Project::Build>(); -} + if (boost::filesystem::exists(search_path / "meson.build")) { + std::unique_ptr<Project::Build> build(new MesonBuild(path)); + if (!build->project_path.empty()) + return build; + } -boost::filesystem::path Project::Build::get_default_path() { - if (project_path.empty()) - return boost::filesystem::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; - auto default_build_path_string = default_build_path.string(); - auto path_filename_string = project_path.filename().string(); - while ((pos = default_build_path_string.find(path_variable_project_directory_name, pos)) != std::string::npos) { - default_build_path_string.replace(pos, path_variable_project_directory_name.size(), path_filename_string); - pos += path_filename_string.size(); + if (boost::filesystem::exists(search_path / "Cargo.toml")) { + std::unique_ptr<Project::Build> build(new CargoBuild()); + build->project_path = search_path; + return build; } - if (pos != 0) - default_build_path = default_build_path_string; - if (default_build_path.is_relative()) - default_build_path = project_path / default_build_path; + if (boost::filesystem::exists(search_path / "package.json")) { + std::unique_ptr<Project::Build> build(new NpmBuild()); + build->project_path = search_path; + return build; + } - return filesystem::get_normal_path(default_build_path); -} + if (search_path == search_path.root_directory()) + break; + search_path = search_path.parent_path(); + } -boost::filesystem::path Project::Build::get_debug_path() { - if (project_path.empty()) - return boost::filesystem::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; - auto debug_build_path_string = debug_build_path.string(); - auto path_filename_string = project_path.filename().string(); - while ((pos = debug_build_path_string.find(path_variable_project_directory_name, pos)) != std::string::npos) { - debug_build_path_string.replace(pos, path_variable_project_directory_name.size(), path_filename_string); - pos += path_filename_string.size(); - } - if (pos != 0) - debug_build_path = debug_build_path_string; - - const std::string path_variable_default_build_path = "<default_build_path>"; - pos = 0; - debug_build_path_string = debug_build_path.string(); - auto default_build_path = Config::get().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(); - } - if (pos != 0) - debug_build_path = debug_build_path_string; + return std::make_unique<Project::Build>(); +} - if (debug_build_path.is_relative()) - debug_build_path = project_path / debug_build_path; +boost::filesystem::path Project::Build::get_default_path() { + if (project_path.empty()) + return boost::filesystem::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; + auto default_build_path_string = default_build_path.string(); + auto path_filename_string = project_path.filename().string(); + while ((pos = default_build_path_string.find(path_variable_project_directory_name, pos)) != std::string::npos) { + default_build_path_string.replace(pos, path_variable_project_directory_name.size(), path_filename_string); + pos += path_filename_string.size(); + } + if (pos != 0) + default_build_path = default_build_path_string; + + if (default_build_path.is_relative()) + default_build_path = project_path / default_build_path; + + return filesystem::get_normal_path(default_build_path); +} - return filesystem::get_normal_path(debug_build_path); +boost::filesystem::path Project::Build::get_debug_path() { + if (project_path.empty()) + return boost::filesystem::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; + auto debug_build_path_string = debug_build_path.string(); + auto path_filename_string = project_path.filename().string(); + while ((pos = debug_build_path_string.find(path_variable_project_directory_name, pos)) != std::string::npos) { + debug_build_path_string.replace(pos, path_variable_project_directory_name.size(), path_filename_string); + pos += path_filename_string.size(); + } + if (pos != 0) + debug_build_path = debug_build_path_string; + + const std::string path_variable_default_build_path = "<default_build_path>"; + pos = 0; + debug_build_path_string = debug_build_path.string(); + auto default_build_path = Config::get().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(); + } + if (pos != 0) + debug_build_path = debug_build_path_string; + + if (debug_build_path.is_relative()) + debug_build_path = project_path / debug_build_path; + + return filesystem::get_normal_path(debug_build_path); } Project::CMakeBuild::CMakeBuild(const boost::filesystem::path &path) : Project::Build(), cmake(path) { - project_path = cmake.get_project_path(); + project_path = cmake.get_project_path(); } bool Project::CMakeBuild::update_default(bool force) { - return cmake.update_default_build(get_default_path(), force); + return cmake.update_default_build(get_default_path(), force); } bool Project::CMakeBuild::update_debug(bool force) { - return cmake.update_debug_build(get_debug_path(), force); + return cmake.update_debug_build(get_debug_path(), force); } std::string Project::CMakeBuild::get_compile_command() { - return Config::get().project.cmake.compile_command; + return Config::get().project.cmake.compile_command; } boost::filesystem::path Project::CMakeBuild::get_executable(const boost::filesystem::path &path) { - return cmake.get_executable(get_default_path(), path).string(); + return cmake.get_executable(get_default_path(), path).string(); } Project::MesonBuild::MesonBuild(const boost::filesystem::path &path) : Project::Build(), meson(path) { - project_path = meson.get_project_path(); + project_path = meson.get_project_path(); } bool Project::MesonBuild::update_default(bool force) { - return meson.update_default_build(get_default_path(), force); + return meson.update_default_build(get_default_path(), force); } bool Project::MesonBuild::update_debug(bool force) { - return meson.update_debug_build(get_debug_path(), force); + return meson.update_debug_build(get_debug_path(), force); } std::string Project::MesonBuild::get_compile_command() { - return Config::get().project.meson.compile_command; + return Config::get().project.meson.compile_command; } boost::filesystem::path Project::MesonBuild::get_executable(const boost::filesystem::path &path) { - return meson.get_executable(get_default_path(), path); + return meson.get_executable(get_default_path(), path); } diff --git a/src/project_build.h b/src/project_build.h index 8720ea30..2709de6e 100644 --- a/src/project_build.h +++ b/src/project_build.h @@ -5,75 +5,75 @@ #include "meson.h" namespace Project { - class Build { - public: - Build() {} + class Build { + public: + Build() {} - virtual ~Build() {} + virtual ~Build() {} - boost::filesystem::path project_path; + boost::filesystem::path project_path; - virtual boost::filesystem::path get_default_path(); + virtual boost::filesystem::path get_default_path(); - virtual bool update_default(bool force = false) { return false; } + virtual bool update_default(bool force = false) { return false; } - virtual boost::filesystem::path get_debug_path(); + virtual boost::filesystem::path get_debug_path(); - virtual bool update_debug(bool force = false) { return false; } + virtual bool update_debug(bool force = false) { return false; } - virtual std::string get_compile_command() { return std::string(); } + virtual std::string get_compile_command() { return std::string(); } - virtual boost::filesystem::path - get_executable(const boost::filesystem::path &path) { return boost::filesystem::path(); } + virtual boost::filesystem::path + get_executable(const boost::filesystem::path &path) { return boost::filesystem::path(); } - static std::unique_ptr<Build> create(const boost::filesystem::path &path); - }; + static std::unique_ptr<Build> create(const boost::filesystem::path &path); + }; - class CMakeBuild : public Build { - ::CMake cmake; - public: - CMakeBuild(const boost::filesystem::path &path); + class CMakeBuild : public Build { + ::CMake cmake; + public: + CMakeBuild(const boost::filesystem::path &path); - bool update_default(bool force = false) override; + bool update_default(bool force = false) override; - bool update_debug(bool force = false) override; + bool update_debug(bool force = false) override; - std::string get_compile_command() override; + std::string get_compile_command() override; - boost::filesystem::path get_executable(const boost::filesystem::path &path) override; - }; + boost::filesystem::path get_executable(const boost::filesystem::path &path) override; + }; - class MesonBuild : public Build { - Meson meson; - public: - MesonBuild(const boost::filesystem::path &path); + class MesonBuild : public Build { + Meson meson; + public: + MesonBuild(const boost::filesystem::path &path); - bool update_default(bool force = false) override; + bool update_default(bool force = false) override; - bool update_debug(bool force = false) override; + bool update_debug(bool force = false) override; - std::string get_compile_command() override; + std::string get_compile_command() override; - boost::filesystem::path get_executable(const boost::filesystem::path &path) override; - }; + boost::filesystem::path get_executable(const boost::filesystem::path &path) override; + }; - class CargoBuild : public Build { - public: - boost::filesystem::path get_default_path() override { return project_path / "target" / "debug"; } + class CargoBuild : public Build { + public: + boost::filesystem::path get_default_path() override { return project_path / "target" / "debug"; } - bool update_default(bool force = false) override { return true; } + bool update_default(bool force = false) override { return true; } - boost::filesystem::path get_debug_path() override { return get_default_path(); } + boost::filesystem::path get_debug_path() override { return get_default_path(); } - bool update_debug(bool force = false) override { return true; } + bool update_debug(bool force = false) override { return true; } - std::string get_compile_command() override { return "cargo build"; } + std::string get_compile_command() override { return "cargo build"; } - boost::filesystem::path get_executable(const boost::filesystem::path &path) override { - return get_debug_path() / project_path.filename(); - } - }; + boost::filesystem::path get_executable(const boost::filesystem::path &path) override { + return get_debug_path() / project_path.filename(); + } + }; - class NpmBuild : public Build { - }; + class NpmBuild : public Build { + }; } diff --git a/src/selection_dialog.cc b/src/selection_dialog.cc index 5b0461c3..3fc05217 100644 --- a/src/selection_dialog.cc +++ b/src/selection_dialog.cc @@ -2,198 +2,198 @@ #include <algorithm> SelectionDialogBase::ListViewText::ListViewText(bool use_markup) : Gtk::TreeView(), use_markup(use_markup) { - list_store = Gtk::ListStore::create(column_record); - set_model(list_store); - append_column("", cell_renderer); - if (use_markup) - get_column(0)->add_attribute(cell_renderer.property_markup(), column_record.text); - else - get_column(0)->add_attribute(cell_renderer.property_text(), column_record.text); - - get_selection()->set_mode(Gtk::SelectionMode::SELECTION_BROWSE); - set_enable_search(true); - set_headers_visible(false); - set_hscroll_policy(Gtk::ScrollablePolicy::SCROLL_NATURAL); - set_activate_on_single_click(true); - set_hover_selection(false); - set_rules_hint(true); + list_store = Gtk::ListStore::create(column_record); + set_model(list_store); + append_column("", cell_renderer); + if (use_markup) + get_column(0)->add_attribute(cell_renderer.property_markup(), column_record.text); + else + get_column(0)->add_attribute(cell_renderer.property_text(), column_record.text); + + get_selection()->set_mode(Gtk::SelectionMode::SELECTION_BROWSE); + set_enable_search(true); + set_headers_visible(false); + set_hscroll_policy(Gtk::ScrollablePolicy::SCROLL_NATURAL); + set_activate_on_single_click(true); + set_hover_selection(false); + set_rules_hint(true); } void SelectionDialogBase::ListViewText::append(const std::string &value) { - auto new_row = list_store->append(); - new_row->set_value(column_record.text, value); - new_row->set_value(column_record.index, size++); + auto new_row = list_store->append(); + new_row->set_value(column_record.text, value); + new_row->set_value(column_record.index, size++); } void SelectionDialogBase::ListViewText::erase_rows() { - list_store->clear(); - size = 0; + list_store->clear(); + size = 0; } void SelectionDialogBase::ListViewText::clear() { - unset_model(); - list_store.reset(); - size = 0; + unset_model(); + list_store.reset(); + size = 0; } SelectionDialogBase::SelectionDialogBase(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, bool show_search_entry, bool use_markup) : - start_mark(start_mark), text_view(text_view), window(Gtk::WindowType::WINDOW_POPUP), - vbox(Gtk::Orientation::ORIENTATION_VERTICAL), list_view_text(use_markup), show_search_entry(show_search_entry) { - auto g_application = g_application_get_default(); - auto gio_application = Glib::wrap(g_application, true); - auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application); - window.set_transient_for(*application->get_active_window()); + start_mark(start_mark), text_view(text_view), window(Gtk::WindowType::WINDOW_POPUP), + vbox(Gtk::Orientation::ORIENTATION_VERTICAL), list_view_text(use_markup), show_search_entry(show_search_entry) { + auto g_application = g_application_get_default(); + auto gio_application = Glib::wrap(g_application, true); + auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application); + window.set_transient_for(*application->get_active_window()); - window.set_type_hint(Gdk::WindowTypeHint::WINDOW_TYPE_HINT_COMBO); + window.set_type_hint(Gdk::WindowTypeHint::WINDOW_TYPE_HINT_COMBO); - search_entry.signal_changed().connect([this] { - if (on_search_entry_changed) - on_search_entry_changed(search_entry.get_text()); - }, false); + search_entry.signal_changed().connect([this] { + if (on_search_entry_changed) + on_search_entry_changed(search_entry.get_text()); + }, false); - list_view_text.set_search_entry(search_entry); + list_view_text.set_search_entry(search_entry); - window.set_default_size(0, 0); - window.property_decorated() = false; - window.set_skip_taskbar_hint(true); - - scrolled_window.set_policy(Gtk::PolicyType::POLICY_AUTOMATIC, Gtk::PolicyType::POLICY_AUTOMATIC); - - scrolled_window.add(list_view_text); - if (show_search_entry) - vbox.pack_start(search_entry, false, false); - vbox.pack_start(scrolled_window, true, true); - window.add(vbox); - - list_view_text.signal_realize().connect([this]() { - auto g_application = g_application_get_default(); - auto gio_application = Glib::wrap(g_application, true); - auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application); - auto application_window = application->get_active_window(); - - int row_width = 0, row_height; - Gdk::Rectangle rect; - list_view_text.get_cell_area( - list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin()), - *(list_view_text.get_column(0)), rect); - row_width = rect.get_width(); - row_height = rect.get_height(); - - row_width += rect.get_x() * 2; //TODO: Add correct margin x and y - row_height += rect.get_y() * 2; - - if (this->text_view && row_width > this->text_view->get_width() * 2 / 3) - row_width = this->text_view->get_width() * 2 / 3; - else if (row_width > application_window->get_width() / 2) - row_width = application_window->get_width() / 2; - else - scrolled_window.set_policy(Gtk::PolicyType::POLICY_NEVER, Gtk::PolicyType::POLICY_AUTOMATIC); - - int window_height = std::min(row_height * static_cast<int>(list_view_text.get_model()->children().size()), - row_height * 10); - if (this->show_search_entry) - window_height += search_entry.get_height(); - int window_width = row_width + 1; - window.resize(window_width, window_height); - - if (this->text_view) { - Gdk::Rectangle iter_rect; - this->text_view->get_iter_location(this->start_mark->get_iter(), iter_rect); - Gdk::Rectangle visible_rect; - this->text_view->get_visible_rect(visible_rect); - int buffer_x = std::max(iter_rect.get_x(), visible_rect.get_x()); - int buffer_y = iter_rect.get_y() + iter_rect.get_height(); - int window_x, window_y; - this->text_view->buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, buffer_x, buffer_y, - window_x, window_y); - int root_x, root_y; - this->text_view->get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->get_root_coords(window_x, window_y, - root_x, root_y); - window.move(root_x, root_y + 1); //TODO: replace 1 with some margin - } else { - int root_x, root_y; - application_window->get_position(root_x, root_y); - root_x += application_window->get_width() / 2 - window_width / 2; - root_y += application_window->get_height() / 2 - window_height / 2; - window.move(root_x, root_y); - } - }); + window.set_default_size(0, 0); + window.property_decorated() = false; + window.set_skip_taskbar_hint(true); - list_view_text.signal_cursor_changed().connect([this] { - cursor_changed(); - }); + scrolled_window.set_policy(Gtk::PolicyType::POLICY_AUTOMATIC, Gtk::PolicyType::POLICY_AUTOMATIC); + + scrolled_window.add(list_view_text); + if (show_search_entry) + vbox.pack_start(search_entry, false, false); + vbox.pack_start(scrolled_window, true, true); + window.add(vbox); + + list_view_text.signal_realize().connect([this]() { + auto g_application = g_application_get_default(); + auto gio_application = Glib::wrap(g_application, true); + auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application); + auto application_window = application->get_active_window(); + + int row_width = 0, row_height; + Gdk::Rectangle rect; + list_view_text.get_cell_area( + list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin()), + *(list_view_text.get_column(0)), rect); + row_width = rect.get_width(); + row_height = rect.get_height(); + + row_width += rect.get_x() * 2; //TODO: Add correct margin x and y + row_height += rect.get_y() * 2; + + if (this->text_view && row_width > this->text_view->get_width() * 2 / 3) + row_width = this->text_view->get_width() * 2 / 3; + else if (row_width > application_window->get_width() / 2) + row_width = application_window->get_width() / 2; + else + scrolled_window.set_policy(Gtk::PolicyType::POLICY_NEVER, Gtk::PolicyType::POLICY_AUTOMATIC); + + int window_height = std::min(row_height * static_cast<int>(list_view_text.get_model()->children().size()), + row_height * 10); + if (this->show_search_entry) + window_height += search_entry.get_height(); + int window_width = row_width + 1; + window.resize(window_width, window_height); + + if (this->text_view) { + Gdk::Rectangle iter_rect; + this->text_view->get_iter_location(this->start_mark->get_iter(), iter_rect); + Gdk::Rectangle visible_rect; + this->text_view->get_visible_rect(visible_rect); + int buffer_x = std::max(iter_rect.get_x(), visible_rect.get_x()); + int buffer_y = iter_rect.get_y() + iter_rect.get_height(); + int window_x, window_y; + this->text_view->buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, buffer_x, buffer_y, + window_x, window_y); + int root_x, root_y; + this->text_view->get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->get_root_coords(window_x, window_y, + root_x, root_y); + window.move(root_x, root_y + 1); //TODO: replace 1 with some margin + } else { + int root_x, root_y; + application_window->get_position(root_x, root_y); + root_x += application_window->get_width() / 2 - window_width / 2; + root_y += application_window->get_height() / 2 - window_height / 2; + window.move(root_x, root_y); + } + }); + + list_view_text.signal_cursor_changed().connect([this] { + cursor_changed(); + }); } SelectionDialogBase::~SelectionDialogBase() { - if (text_view) - text_view->get_buffer()->delete_mark(start_mark); + if (text_view) + text_view->get_buffer()->delete_mark(start_mark); } void SelectionDialogBase::cursor_changed() { - if (!is_visible()) - return; - auto it = list_view_text.get_selection()->get_selected(); - unsigned int index = static_cast<unsigned int>(-1); + if (!is_visible()) + return; + auto it = list_view_text.get_selection()->get_selected(); + unsigned int index = static_cast<unsigned int>(-1); + if (it) + index = it->get_value(list_view_text.column_record.index); + if (last_index == index) + return; + if (on_changed) { + std::string text; if (it) - index = it->get_value(list_view_text.column_record.index); - if (last_index == index) - return; - if (on_changed) { - std::string text; - if (it) - text = it->get_value(list_view_text.column_record.text); - on_changed(index, text); - } - last_index = index; + text = it->get_value(list_view_text.column_record.text); + on_changed(index, text); + } + last_index = index; } void SelectionDialogBase::add_row(const std::string &row) { - list_view_text.append(row); + list_view_text.append(row); } void SelectionDialogBase::erase_rows() { - list_view_text.erase_rows(); + list_view_text.erase_rows(); } void SelectionDialogBase::show() { - window.show_all(); - if (text_view) - text_view->grab_focus(); - - if (list_view_text.get_model()->children().size() > 0) { - if (!list_view_text.get_selection()->get_selected()) { - list_view_text.set_cursor( - list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); - cursor_changed(); - } else if (list_view_text.get_model()->children().begin() != list_view_text.get_selection()->get_selected()) { - while (Gtk::Main::events_pending()) - Gtk::Main::iteration(false); - if (is_visible()) - list_view_text.scroll_to_row( - list_view_text.get_model()->get_path(list_view_text.get_selection()->get_selected()), 0.5); - } + window.show_all(); + if (text_view) + text_view->grab_focus(); + + if (list_view_text.get_model()->children().size() > 0) { + if (!list_view_text.get_selection()->get_selected()) { + list_view_text.set_cursor( + list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); + cursor_changed(); + } else if (list_view_text.get_model()->children().begin() != list_view_text.get_selection()->get_selected()) { + while (Gtk::Main::events_pending()) + Gtk::Main::iteration(false); + if (is_visible()) + list_view_text.scroll_to_row( + list_view_text.get_model()->get_path(list_view_text.get_selection()->get_selected()), 0.5); } - if (on_show) - on_show(); + } + if (on_show) + on_show(); } void SelectionDialogBase::set_cursor_at_last_row() { - auto children = list_view_text.get_model()->children(); - if (children.size() > 0) { - list_view_text.set_cursor(list_view_text.get_model()->get_path(children[children.size() - 1])); - cursor_changed(); - } + auto children = list_view_text.get_model()->children(); + if (children.size() > 0) { + list_view_text.set_cursor(list_view_text.get_model()->get_path(children[children.size() - 1])); + cursor_changed(); + } } void SelectionDialogBase::hide() { - if (!is_visible()) - return; - window.hide(); - if (on_hide) - on_hide(); - list_view_text.clear(); - last_index = static_cast<unsigned int>(-1); + if (!is_visible()) + return; + window.hide(); + if (on_hide) + on_hide(); + list_view_text.clear(); + last_index = static_cast<unsigned int>(-1); } std::unique_ptr<SelectionDialog> SelectionDialog::instance; @@ -202,271 +202,271 @@ SelectionDialog::SelectionDialog(Gtk::TextView *text_view, Glib::RefPtr<Gtk::Tex bool show_search_entry, bool use_markup) : SelectionDialogBase(text_view, start_mark, show_search_entry, use_markup) { - auto search_key = std::make_shared<std::string>(); - auto filter_model = Gtk::TreeModelFilter::create(list_view_text.get_model()); - - filter_model->set_visible_func([this, search_key](const Gtk::TreeModel::const_iterator &iter) { - std::string row_lc; - iter->get_value(0, row_lc); - auto search_key_lc = *search_key; - std::transform(row_lc.begin(), row_lc.end(), row_lc.begin(), ::tolower); - std::transform(search_key_lc.begin(), search_key_lc.end(), search_key_lc.begin(), ::tolower); - if (list_view_text.use_markup) { - size_t pos = 0; - while ((pos = row_lc.find('<', pos)) != std::string::npos) { - auto pos2 = row_lc.find('>', pos + 1); - row_lc.erase(pos, pos2 - pos + 1); - } - search_key_lc = Glib::Markup::escape_text(search_key_lc); - } - if (row_lc.find(search_key_lc) != std::string::npos) - return true; - return false; - }); + auto search_key = std::make_shared<std::string>(); + auto filter_model = Gtk::TreeModelFilter::create(list_view_text.get_model()); + + filter_model->set_visible_func([this, search_key](const Gtk::TreeModel::const_iterator &iter) { + std::string row_lc; + iter->get_value(0, row_lc); + auto search_key_lc = *search_key; + std::transform(row_lc.begin(), row_lc.end(), row_lc.begin(), ::tolower); + std::transform(search_key_lc.begin(), search_key_lc.end(), search_key_lc.begin(), ::tolower); + if (list_view_text.use_markup) { + size_t pos = 0; + while ((pos = row_lc.find('<', pos)) != std::string::npos) { + auto pos2 = row_lc.find('>', pos + 1); + row_lc.erase(pos, pos2 - pos + 1); + } + search_key_lc = Glib::Markup::escape_text(search_key_lc); + } + if (row_lc.find(search_key_lc) != std::string::npos) + return true; + return false; + }); - list_view_text.set_model(filter_model); - - list_view_text.set_search_equal_func( - [](const Glib::RefPtr<Gtk::TreeModel> &model, int column, const Glib::ustring &key, - const Gtk::TreeModel::iterator &iter) { - return false; - }); - - search_entry.signal_changed().connect([this, search_key, filter_model]() { - *search_key = search_entry.get_text(); - filter_model->refilter(); - list_view_text.set_search_entry(search_entry); //TODO:Report the need of this to GTK's git (bug) - if (search_key->empty()) { - if (list_view_text.get_model()->children().size() > 0) - list_view_text.set_cursor( - list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); - } - }); + list_view_text.set_model(filter_model); - auto activate = [this]() { - auto it = list_view_text.get_selection()->get_selected(); - if (on_select && it) { - auto index = it->get_value(list_view_text.column_record.index); - auto text = it->get_value(list_view_text.column_record.text); - on_select(index, text, true); - } - hide(); - }; - search_entry.signal_activate().connect([activate]() { - activate(); - }); - list_view_text.signal_row_activated().connect([activate](const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *) { - activate(); - }); + list_view_text.set_search_equal_func( + [](const Glib::RefPtr<Gtk::TreeModel> &model, int column, const Glib::ustring &key, + const Gtk::TreeModel::iterator &iter) { + return false; + }); + + search_entry.signal_changed().connect([this, search_key, filter_model]() { + *search_key = search_entry.get_text(); + filter_model->refilter(); + list_view_text.set_search_entry(search_entry); //TODO:Report the need of this to GTK's git (bug) + if (search_key->empty()) { + if (list_view_text.get_model()->children().size() > 0) + list_view_text.set_cursor( + list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); + } + }); + + auto activate = [this]() { + auto it = list_view_text.get_selection()->get_selected(); + if (on_select && it) { + auto index = it->get_value(list_view_text.column_record.index); + auto text = it->get_value(list_view_text.column_record.text); + on_select(index, text, true); + } + hide(); + }; + search_entry.signal_activate().connect([activate]() { + activate(); + }); + list_view_text.signal_row_activated().connect([activate](const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *) { + activate(); + }); } bool SelectionDialog::on_key_press(GdkEventKey *key) { - if ((key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down) && - list_view_text.get_model()->children().size() > 0) { - auto it = list_view_text.get_selection()->get_selected(); - if (it) { - it++; - if (it) - list_view_text.set_cursor(list_view_text.get_model()->get_path(it)); - } - return true; - } else if ((key->keyval == GDK_KEY_Up || key->keyval == GDK_KEY_KP_Up) && - list_view_text.get_model()->children().size() > 0) { - auto it = list_view_text.get_selection()->get_selected(); - if (it) { - it--; - if (it) - list_view_text.set_cursor(list_view_text.get_model()->get_path(it)); - } - return true; - } else if (key->keyval == GDK_KEY_Return || key->keyval == GDK_KEY_KP_Enter || - key->keyval == GDK_KEY_ISO_Left_Tab || key->keyval == GDK_KEY_Tab) { - auto it = list_view_text.get_selection()->get_selected(); - auto column = list_view_text.get_column(0); - list_view_text.row_activated(list_view_text.get_model()->get_path(it), *column); - return true; - } else if (key->keyval == GDK_KEY_Escape) { - hide(); - return true; - } else if (key->keyval == GDK_KEY_Left || key->keyval == GDK_KEY_KP_Left || key->keyval == GDK_KEY_Right || - key->keyval == GDK_KEY_KP_Right) { - hide(); - return false; - } else if (show_search_entry) { -#ifdef __APPLE__ //OS X bug most likely: Gtk::Entry will not work if window is of type POPUP - int search_entry_length=search_entry.get_text_length(); - if(key->keyval==GDK_KEY_dead_tilde) { - search_entry.insert_text("~", 1, search_entry_length); - return true; - } - else if(key->keyval==GDK_KEY_dead_circumflex) { - search_entry.insert_text("^", 1, search_entry_length); - return true; - } - else if(key->is_modifier) - return true; - else if(key->keyval==GDK_KEY_BackSpace) { - auto length=search_entry.get_text_length(); - if(length>0) - search_entry.delete_text(length-1, length); - return true; - } - else { - gunichar unicode=gdk_keyval_to_unicode(key->keyval); - if(unicode>=32 && unicode!=126) { - auto ustr=Glib::ustring(1, unicode); - search_entry.insert_text(ustr, ustr.bytes(), search_entry_length); - return true; - } - } -#else - search_entry.on_key_press_event(key); - return true; -#endif + if ((key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down) && + list_view_text.get_model()->children().size() > 0) { + auto it = list_view_text.get_selection()->get_selected(); + if (it) { + it++; + if (it) + list_view_text.set_cursor(list_view_text.get_model()->get_path(it)); + } + return true; + } else if ((key->keyval == GDK_KEY_Up || key->keyval == GDK_KEY_KP_Up) && + list_view_text.get_model()->children().size() > 0) { + auto it = list_view_text.get_selection()->get_selected(); + if (it) { + it--; + if (it) + list_view_text.set_cursor(list_view_text.get_model()->get_path(it)); } + return true; + } else if (key->keyval == GDK_KEY_Return || key->keyval == GDK_KEY_KP_Enter || + key->keyval == GDK_KEY_ISO_Left_Tab || key->keyval == GDK_KEY_Tab) { + auto it = list_view_text.get_selection()->get_selected(); + auto column = list_view_text.get_column(0); + list_view_text.row_activated(list_view_text.get_model()->get_path(it), *column); + return true; + } else if (key->keyval == GDK_KEY_Escape) { + hide(); + return true; + } else if (key->keyval == GDK_KEY_Left || key->keyval == GDK_KEY_KP_Left || key->keyval == GDK_KEY_Right || + key->keyval == GDK_KEY_KP_Right) { hide(); return false; + } else if (show_search_entry) { +#ifdef __APPLE__ //OS X bug most likely: Gtk::Entry will not work if window is of type POPUP + int search_entry_length=search_entry.get_text_length(); + if(key->keyval==GDK_KEY_dead_tilde) { + search_entry.insert_text("~", 1, search_entry_length); + return true; + } + else if(key->keyval==GDK_KEY_dead_circumflex) { + search_entry.insert_text("^", 1, search_entry_length); + return true; + } + else if(key->is_modifier) + return true; + else if(key->keyval==GDK_KEY_BackSpace) { + auto length=search_entry.get_text_length(); + if(length>0) + search_entry.delete_text(length-1, length); + return true; + } + else { + gunichar unicode=gdk_keyval_to_unicode(key->keyval); + if(unicode>=32 && unicode!=126) { + auto ustr=Glib::ustring(1, unicode); + search_entry.insert_text(ustr, ustr.bytes(), search_entry_length); + return true; + } + } +#else + search_entry.on_key_press_event(key); + return true; +#endif + } + hide(); + return false; } std::unique_ptr<CompletionDialog> CompletionDialog::instance; CompletionDialog::CompletionDialog(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark) - : SelectionDialogBase(text_view, start_mark, false, false) { - show_offset = text_view->get_buffer()->get_insert()->get_iter().get_offset(); - - auto search_key = std::make_shared<std::string>(); - auto filter_model = Gtk::TreeModelFilter::create(list_view_text.get_model()); - if (show_offset == start_mark->get_iter().get_offset()) { - filter_model->set_visible_func([search_key](const Gtk::TreeModel::const_iterator &iter) { - std::string row_lc; - iter->get_value(0, row_lc); - auto search_key_lc = *search_key; - std::transform(row_lc.begin(), row_lc.end(), row_lc.begin(), ::tolower); - std::transform(search_key_lc.begin(), search_key_lc.end(), search_key_lc.begin(), ::tolower); - if (row_lc.find(search_key_lc) != std::string::npos) - return true; - return false; - }); - } else { - filter_model->set_visible_func([search_key](const Gtk::TreeModel::const_iterator &iter) { - std::string row; - iter->get_value(0, row); - if (row.find(*search_key) == 0) - return true; - return false; - }); - } - list_view_text.set_model(filter_model); - search_entry.signal_changed().connect([this, search_key, filter_model]() { - *search_key = search_entry.get_text(); - filter_model->refilter(); - list_view_text.set_search_entry(search_entry); //TODO:Report the need of this to GTK's git (bug) + : SelectionDialogBase(text_view, start_mark, false, false) { + show_offset = text_view->get_buffer()->get_insert()->get_iter().get_offset(); + + auto search_key = std::make_shared<std::string>(); + auto filter_model = Gtk::TreeModelFilter::create(list_view_text.get_model()); + if (show_offset == start_mark->get_iter().get_offset()) { + filter_model->set_visible_func([search_key](const Gtk::TreeModel::const_iterator &iter) { + std::string row_lc; + iter->get_value(0, row_lc); + auto search_key_lc = *search_key; + std::transform(row_lc.begin(), row_lc.end(), row_lc.begin(), ::tolower); + std::transform(search_key_lc.begin(), search_key_lc.end(), search_key_lc.begin(), ::tolower); + if (row_lc.find(search_key_lc) != std::string::npos) + return true; + return false; }); - - list_view_text.signal_row_activated().connect([this](const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *) { - select(); + } else { + filter_model->set_visible_func([search_key](const Gtk::TreeModel::const_iterator &iter) { + std::string row; + iter->get_value(0, row); + if (row.find(*search_key) == 0) + return true; + return false; }); - - auto text = text_view->get_buffer()->get_text(start_mark->get_iter(), - text_view->get_buffer()->get_insert()->get_iter()); - if (text.size() > 0) { - search_entry.set_text(text); - list_view_text.set_search_entry(search_entry); - } + } + list_view_text.set_model(filter_model); + search_entry.signal_changed().connect([this, search_key, filter_model]() { + *search_key = search_entry.get_text(); + filter_model->refilter(); + list_view_text.set_search_entry(search_entry); //TODO:Report the need of this to GTK's git (bug) + }); + + list_view_text.signal_row_activated().connect([this](const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *) { + select(); + }); + + auto text = text_view->get_buffer()->get_text(start_mark->get_iter(), + text_view->get_buffer()->get_insert()->get_iter()); + if (text.size() > 0) { + search_entry.set_text(text); + list_view_text.set_search_entry(search_entry); + } } void CompletionDialog::select(bool hide_window) { - row_in_entry = true; - - auto it = list_view_text.get_selection()->get_selected(); - if (on_select && it) { - auto index = it->get_value(list_view_text.column_record.index); - auto text = it->get_value(list_view_text.column_record.text); - on_select(index, text, hide_window); - } - if (hide_window) - hide(); + row_in_entry = true; + + auto it = list_view_text.get_selection()->get_selected(); + if (on_select && it) { + auto index = it->get_value(list_view_text.column_record.index); + auto text = it->get_value(list_view_text.column_record.text); + on_select(index, text, hide_window); + } + if (hide_window) + hide(); } bool CompletionDialog::on_key_release(GdkEventKey *key) { - if (key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down || key->keyval == GDK_KEY_Up || - key->keyval == GDK_KEY_KP_Up) - return false; + if (key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down || key->keyval == GDK_KEY_Up || + key->keyval == GDK_KEY_KP_Up) + return false; - if (show_offset > text_view->get_buffer()->get_insert()->get_iter().get_offset()) { - hide(); - } else { - auto text = text_view->get_buffer()->get_text(start_mark->get_iter(), - text_view->get_buffer()->get_insert()->get_iter()); - search_entry.set_text(text); - list_view_text.set_search_entry(search_entry); - if (text == "") { - if (list_view_text.get_model()->children().size() > 0) - list_view_text.set_cursor( - list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); - } - cursor_changed(); + if (show_offset > text_view->get_buffer()->get_insert()->get_iter().get_offset()) { + hide(); + } else { + auto text = text_view->get_buffer()->get_text(start_mark->get_iter(), + text_view->get_buffer()->get_insert()->get_iter()); + search_entry.set_text(text); + list_view_text.set_search_entry(search_entry); + if (text == "") { + if (list_view_text.get_model()->children().size() > 0) + list_view_text.set_cursor( + list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); } - return false; + cursor_changed(); + } + return false; } bool CompletionDialog::on_key_press(GdkEventKey *key) { - if ((key->keyval >= GDK_KEY_0 && key->keyval <= GDK_KEY_9) || - (key->keyval >= GDK_KEY_A && key->keyval <= GDK_KEY_Z) || - (key->keyval >= GDK_KEY_a && key->keyval <= GDK_KEY_z) || - key->keyval == GDK_KEY_underscore || key->keyval == GDK_KEY_BackSpace) { - if (row_in_entry) { - text_view->get_buffer()->erase(start_mark->get_iter(), text_view->get_buffer()->get_insert()->get_iter()); - row_in_entry = false; - if (key->keyval == GDK_KEY_BackSpace) - return true; - } - return false; - } - if (key->keyval == GDK_KEY_Shift_L || key->keyval == GDK_KEY_Shift_R || key->keyval == GDK_KEY_Alt_L || - key->keyval == GDK_KEY_Alt_R || key->keyval == GDK_KEY_Control_L || key->keyval == GDK_KEY_Control_R || - key->keyval == GDK_KEY_Meta_L || key->keyval == GDK_KEY_Meta_R) - return false; - if ((key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down) && - list_view_text.get_model()->children().size() > 0) { - auto it = list_view_text.get_selection()->get_selected(); - if (it) { - it++; - if (it) { - list_view_text.set_cursor(list_view_text.get_model()->get_path(it)); - cursor_changed(); - } - } else - list_view_text.set_cursor( - list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); - select(false); - return true; - } - if ((key->keyval == GDK_KEY_Up || key->keyval == GDK_KEY_KP_Up) && - list_view_text.get_model()->children().size() > 0) { - auto it = list_view_text.get_selection()->get_selected(); - if (it) { - it--; - if (it) { - list_view_text.set_cursor(list_view_text.get_model()->get_path(it)); - cursor_changed(); - } - } else { - auto last_it = list_view_text.get_model()->children().end(); - last_it--; - if (last_it) - list_view_text.set_cursor(list_view_text.get_model()->get_path(last_it)); - } - select(false); - return true; - } - if (key->keyval == GDK_KEY_Return || key->keyval == GDK_KEY_KP_Enter || key->keyval == GDK_KEY_ISO_Left_Tab || - key->keyval == GDK_KEY_Tab) { - select(); + if ((key->keyval >= GDK_KEY_0 && key->keyval <= GDK_KEY_9) || + (key->keyval >= GDK_KEY_A && key->keyval <= GDK_KEY_Z) || + (key->keyval >= GDK_KEY_a && key->keyval <= GDK_KEY_z) || + key->keyval == GDK_KEY_underscore || key->keyval == GDK_KEY_BackSpace) { + if (row_in_entry) { + text_view->get_buffer()->erase(start_mark->get_iter(), text_view->get_buffer()->get_insert()->get_iter()); + row_in_entry = false; + if (key->keyval == GDK_KEY_BackSpace) return true; } - hide(); - if (key->keyval == GDK_KEY_Escape) - return true; return false; + } + if (key->keyval == GDK_KEY_Shift_L || key->keyval == GDK_KEY_Shift_R || key->keyval == GDK_KEY_Alt_L || + key->keyval == GDK_KEY_Alt_R || key->keyval == GDK_KEY_Control_L || key->keyval == GDK_KEY_Control_R || + key->keyval == GDK_KEY_Meta_L || key->keyval == GDK_KEY_Meta_R) + return false; + if ((key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down) && + list_view_text.get_model()->children().size() > 0) { + auto it = list_view_text.get_selection()->get_selected(); + if (it) { + it++; + if (it) { + list_view_text.set_cursor(list_view_text.get_model()->get_path(it)); + cursor_changed(); + } + } else + list_view_text.set_cursor( + list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); + select(false); + return true; + } + if ((key->keyval == GDK_KEY_Up || key->keyval == GDK_KEY_KP_Up) && + list_view_text.get_model()->children().size() > 0) { + auto it = list_view_text.get_selection()->get_selected(); + if (it) { + it--; + if (it) { + list_view_text.set_cursor(list_view_text.get_model()->get_path(it)); + cursor_changed(); + } + } else { + auto last_it = list_view_text.get_model()->children().end(); + last_it--; + if (last_it) + list_view_text.set_cursor(list_view_text.get_model()->get_path(last_it)); + } + select(false); + return true; + } + if (key->keyval == GDK_KEY_Return || key->keyval == GDK_KEY_KP_Enter || key->keyval == GDK_KEY_ISO_Left_Tab || + key->keyval == GDK_KEY_Tab) { + select(); + return true; + } + hide(); + if (key->keyval == GDK_KEY_Escape) + return true; + return false; } diff --git a/src/selection_dialog.h b/src/selection_dialog.h index 05d33d48..224636d0 100644 --- a/src/selection_dialog.h +++ b/src/selection_dialog.h @@ -5,125 +5,125 @@ #include <functional> class SelectionDialogBase { - class ListViewText : public Gtk::TreeView { - class ColumnRecord : public Gtk::TreeModel::ColumnRecord { - public: - ColumnRecord() { - add(text); - add(index); - } - - Gtk::TreeModelColumn<std::string> text; - Gtk::TreeModelColumn<unsigned int> index; - }; - + class ListViewText : public Gtk::TreeView { + class ColumnRecord : public Gtk::TreeModel::ColumnRecord { public: - bool use_markup; - ColumnRecord column_record; + ColumnRecord() { + add(text); + add(index); + } - ListViewText(bool use_markup); + Gtk::TreeModelColumn<std::string> text; + Gtk::TreeModelColumn<unsigned int> index; + }; - void append(const std::string &value); + public: + bool use_markup; + ColumnRecord column_record; - void erase_rows(); + ListViewText(bool use_markup); - void clear(); + void append(const std::string &value); - private: - Glib::RefPtr<Gtk::ListStore> list_store; - Gtk::CellRendererText cell_renderer; - unsigned int size = 0; - }; + void erase_rows(); - class SearchEntry : public Gtk::Entry { - public: - SearchEntry() : Gtk::Entry() {} + void clear(); - bool on_key_press_event(GdkEventKey *event) override { return Gtk::Entry::on_key_press_event(event); }; - }; + private: + Glib::RefPtr<Gtk::ListStore> list_store; + Gtk::CellRendererText cell_renderer; + unsigned int size = 0; + }; + + class SearchEntry : public Gtk::Entry { + public: + SearchEntry() : Gtk::Entry() {} + + bool on_key_press_event(GdkEventKey *event) override { return Gtk::Entry::on_key_press_event(event); }; + }; public: - SelectionDialogBase(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, - bool show_search_entry, bool use_markup); + SelectionDialogBase(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, + bool show_search_entry, bool use_markup); - virtual ~SelectionDialogBase(); + virtual ~SelectionDialogBase(); - void add_row(const std::string &row); + void add_row(const std::string &row); - void erase_rows(); + void erase_rows(); - void set_cursor_at_last_row(); + void set_cursor_at_last_row(); - void show(); + void show(); - void hide(); + void hide(); - bool is_visible() { return window.is_visible(); } + bool is_visible() { return window.is_visible(); } - void get_position(int &root_x, int &root_y) { window.get_position(root_x, root_y); } + void get_position(int &root_x, int &root_y) { window.get_position(root_x, root_y); } - std::function<void()> on_show; - std::function<void()> on_hide; - std::function<void(unsigned int index, const std::string &text, bool hide_window)> on_select; - std::function<void(unsigned int index, const std::string &text)> on_changed; - std::function<void(const std::string &text)> on_search_entry_changed; - Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark; + std::function<void()> on_show; + std::function<void()> on_hide; + std::function<void(unsigned int index, const std::string &text, bool hide_window)> on_select; + std::function<void(unsigned int index, const std::string &text)> on_changed; + std::function<void(const std::string &text)> on_search_entry_changed; + Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark; protected: - void cursor_changed(); + void cursor_changed(); - Gtk::TextView *text_view; - Gtk::Window window; - Gtk::Box vbox; - Gtk::ScrolledWindow scrolled_window; - ListViewText list_view_text; - SearchEntry search_entry; - bool show_search_entry; + Gtk::TextView *text_view; + Gtk::Window window; + Gtk::Box vbox; + Gtk::ScrolledWindow scrolled_window; + ListViewText list_view_text; + SearchEntry search_entry; + bool show_search_entry; - unsigned int last_index = static_cast<unsigned int>(-1); + unsigned int last_index = static_cast<unsigned int>(-1); }; class SelectionDialog : public SelectionDialogBase { - SelectionDialog(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, bool show_search_entry, - bool use_markup); + SelectionDialog(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, bool show_search_entry, + bool use_markup); - static std::unique_ptr<SelectionDialog> instance; + static std::unique_ptr<SelectionDialog> instance; public: - bool on_key_press(GdkEventKey *key); + bool on_key_press(GdkEventKey *key); - static void - create(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, bool show_search_entry = true, - bool use_markup = false) { - instance = std::unique_ptr<SelectionDialog>( - new SelectionDialog(text_view, start_mark, show_search_entry, use_markup)); - } + static void + create(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, bool show_search_entry = true, + bool use_markup = false) { + instance = std::unique_ptr<SelectionDialog>( + new SelectionDialog(text_view, start_mark, show_search_entry, use_markup)); + } - static void create(bool show_search_entry = true, bool use_markup = false) { - instance = std::unique_ptr<SelectionDialog>( - new SelectionDialog(nullptr, Glib::RefPtr<Gtk::TextBuffer::Mark>(), show_search_entry, use_markup)); - } + static void create(bool show_search_entry = true, bool use_markup = false) { + instance = std::unique_ptr<SelectionDialog>( + new SelectionDialog(nullptr, Glib::RefPtr<Gtk::TextBuffer::Mark>(), show_search_entry, use_markup)); + } - static std::unique_ptr<SelectionDialog> &get() { return instance; } + static std::unique_ptr<SelectionDialog> &get() { return instance; } }; class CompletionDialog : public SelectionDialogBase { - CompletionDialog(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark); + CompletionDialog(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark); - static std::unique_ptr<CompletionDialog> instance; + static std::unique_ptr<CompletionDialog> instance; public: - bool on_key_release(GdkEventKey *key); + bool on_key_release(GdkEventKey *key); - bool on_key_press(GdkEventKey *key); + bool on_key_press(GdkEventKey *key); - static void create(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark) { - instance = std::unique_ptr<CompletionDialog>(new CompletionDialog(text_view, start_mark)); - } + static void create(Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark) { + instance = std::unique_ptr<CompletionDialog>(new CompletionDialog(text_view, start_mark)); + } - static std::unique_ptr<CompletionDialog> &get() { return instance; } + static std::unique_ptr<CompletionDialog> &get() { return instance; } private: - void select(bool hide_window = true); + void select(bool hide_window = true); - int show_offset; - bool row_in_entry = false; + int show_offset; + bool row_in_entry = false; }; diff --git a/src/source.cc b/src/source.cc index 01f7bd06..63e5bccb 100644 --- a/src/source.cc +++ b/src/source.cc @@ -19,91 +19,91 @@ #include <limits> Glib::RefPtr<Gsv::LanguageManager> Source::LanguageManager::get_default() { - static auto instance = Gsv::LanguageManager::create(); - return instance; + static auto instance = Gsv::LanguageManager::create(); + return instance; } Glib::RefPtr<Gsv::StyleSchemeManager> Source::StyleSchemeManager::get_default() { - static auto instance = Gsv::StyleSchemeManager::create(); - static bool first = true; - if (first) { - instance->prepend_search_path((Config::get().home_juci_path / "styles").string()); - first = false; - } - return instance; + static auto instance = Gsv::StyleSchemeManager::create(); + static bool first = true; + if (first) { + instance->prepend_search_path((Config::get().home_juci_path / "styles").string()); + first = false; + } + return instance; } Glib::RefPtr<Gsv::Language> Source::guess_language(const boost::filesystem::path &file_path) { - auto language_manager = LanguageManager::get_default(); - bool result_uncertain = false; - auto content_type = Gio::content_type_guess(file_path.string(), nullptr, 0, result_uncertain); - if (result_uncertain) { - content_type.clear(); - } - auto language = language_manager->guess_language(file_path.string(), content_type); - if (!language) { - auto filename = file_path.filename().string(); - if (filename == "CMakeLists.txt") - language = language_manager->get_language("cmake"); - else if (filename == "Makefile") - language = language_manager->get_language("makefile"); - else if (file_path.extension() == ".tcc") - language = language_manager->get_language("cpphdr"); - else if (file_path.extension() == ".ts" || file_path.extension() == ".jsx") - language = language_manager->get_language("js"); - else if (!file_path.has_extension()) { - for (auto &part: file_path) { - if (part == "include") { - language = language_manager->get_language("cpphdr"); - break; - } - } - } - } else if (language->get_id() == "cuda") { - if (file_path.extension() == ".cuh") - language = language_manager->get_language("cpphdr"); - else - language = language_manager->get_language("cpp"); - } else if (language->get_id() == "opencl") { - language = language_manager->get_language("cpp"); - } - return language; + auto language_manager = LanguageManager::get_default(); + bool result_uncertain = false; + auto content_type = Gio::content_type_guess(file_path.string(), nullptr, 0, result_uncertain); + if (result_uncertain) { + content_type.clear(); + } + auto language = language_manager->guess_language(file_path.string(), content_type); + if (!language) { + auto filename = file_path.filename().string(); + if (filename == "CMakeLists.txt") + language = language_manager->get_language("cmake"); + else if (filename == "Makefile") + language = language_manager->get_language("makefile"); + else if (file_path.extension() == ".tcc") + language = language_manager->get_language("cpphdr"); + else if (file_path.extension() == ".ts" || file_path.extension() == ".jsx") + language = language_manager->get_language("js"); + else if (!file_path.has_extension()) { + for (auto &part: file_path) { + if (part == "include") { + language = language_manager->get_language("cpphdr"); + break; + } + } + } + } else if (language->get_id() == "cuda") { + if (file_path.extension() == ".cuh") + language = language_manager->get_language("cpphdr"); + else + language = language_manager->get_language("cpp"); + } else if (language->get_id() == "opencl") { + language = language_manager->get_language("cpp"); + } + return language; } Source::FixIt::FixIt(const std::string &source, const std::pair<Offset, Offset> &offsets) : source(source), offsets(offsets) { - if (source.size() == 0) - type = Type::ERASE; - else { - if (this->offsets.first == this->offsets.second) - type = Type::INSERT; - else - type = Type::REPLACE; - } + if (source.size() == 0) + type = Type::ERASE; + else { + if (this->offsets.first == this->offsets.second) + type = Type::INSERT; + else + type = Type::REPLACE; + } } std::string Source::FixIt::string(Glib::RefPtr<Gtk::TextBuffer> buffer) { - auto iter = buffer->get_iter_at_line_index(offsets.first.line, offsets.first.index); - unsigned first_line_offset = iter.get_line_offset() + 1; - iter = buffer->get_iter_at_line_index(offsets.second.line, offsets.second.index); - unsigned second_line_offset = iter.get_line_offset() + 1; - - std::string text; - if (type == Type::INSERT) { - text += "Insert " + source + " at "; - text += std::to_string(offsets.first.line + 1) + ":" + std::to_string(first_line_offset); - } else if (type == Type::REPLACE) { - text += "Replace "; - text += std::to_string(offsets.first.line + 1) + ":" + std::to_string(first_line_offset) + " - "; - text += std::to_string(offsets.second.line + 1) + ":" + std::to_string(second_line_offset); - text += " with " + source; - } else { - text += "Erase "; - text += std::to_string(offsets.first.line + 1) + ":" + std::to_string(first_line_offset) + " - "; - text += std::to_string(offsets.second.line + 1) + ":" + std::to_string(second_line_offset); - } + auto iter = buffer->get_iter_at_line_index(offsets.first.line, offsets.first.index); + unsigned first_line_offset = iter.get_line_offset() + 1; + iter = buffer->get_iter_at_line_index(offsets.second.line, offsets.second.index); + unsigned second_line_offset = iter.get_line_offset() + 1; + + std::string text; + if (type == Type::INSERT) { + text += "Insert " + source + " at "; + text += std::to_string(offsets.first.line + 1) + ":" + std::to_string(first_line_offset); + } else if (type == Type::REPLACE) { + text += "Replace "; + text += std::to_string(offsets.first.line + 1) + ":" + std::to_string(first_line_offset) + " - "; + text += std::to_string(offsets.second.line + 1) + ":" + std::to_string(second_line_offset); + text += " with " + source; + } else { + text += "Erase "; + text += std::to_string(offsets.first.line + 1) + ":" + std::to_string(first_line_offset) + " - "; + text += std::to_string(offsets.second.line + 1) + ":" + std::to_string(second_line_offset); + } - return text; + return text; } ////////////// @@ -113,2789 +113,2789 @@ std::unordered_set<Source::View *> Source::View::non_deleted_views; std::unordered_set<Source::View *> Source::View::views; Source::View::View(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language, bool is_generic_view) - : BaseView(file_path, language), SpellCheckView(file_path, language), DiffView(file_path, language) { - non_deleted_views.emplace(this); - views.emplace(this); - - search_settings = gtk_source_search_settings_new(); - gtk_source_search_settings_set_wrap_around(search_settings, true); - search_context = gtk_source_search_context_new(get_source_buffer()->gobj(), search_settings); - gtk_source_search_context_set_highlight(search_context, true); - //TODO: why does this not work?: Might be best to use the styles from sourceview. These has to be read from file, search-matches got style "search-match" - //TODO: in header if trying again: GtkSourceStyle* search_match_style; - //TODO: We can drop this, only work on newer versions of gtksourceview. - //search_match_style=(GtkSourceStyle*)g_object_new(GTK_SOURCE_TYPE_STYLE, "background-set", 1, "background", "#00FF00", nullptr); - //gtk_source_search_context_set_match_style(search_context, search_match_style); - - //TODO: either use lambda if possible or create a gtkmm wrapper around search_context (including search_settings): - //TODO: (gtkmm's Gtk::Object has connect_property_changed, so subclassing this might be an idea) - g_signal_connect(search_context, "notify::occurrences-count", G_CALLBACK(search_occurrences_updated), this); - - get_buffer()->create_tag("def:warning"); - get_buffer()->create_tag("def:warning_underline"); - get_buffer()->create_tag("def:error"); - get_buffer()->create_tag("def:error_underline"); - - auto mark_attr_debug_breakpoint = Gsv::MarkAttributes::create(); - Gdk::RGBA rgba; - rgba.set_red(1.0); - rgba.set_green(0.5); - rgba.set_blue(0.5); - rgba.set_alpha(0.3); - mark_attr_debug_breakpoint->set_background(rgba); - set_mark_attributes("debug_breakpoint", mark_attr_debug_breakpoint, 100); - auto mark_attr_debug_stop = Gsv::MarkAttributes::create(); - rgba.set_red(0.5); - rgba.set_green(0.5); - rgba.set_blue(1.0); - mark_attr_debug_stop->set_background(rgba); - set_mark_attributes("debug_stop", mark_attr_debug_stop, 101); - auto mark_attr_debug_breakpoint_and_stop = Gsv::MarkAttributes::create(); - rgba.set_red(0.75); - rgba.set_green(0.5); - rgba.set_blue(0.75); - mark_attr_debug_breakpoint_and_stop->set_background(rgba); - set_mark_attributes("debug_breakpoint_and_stop", mark_attr_debug_breakpoint_and_stop, 102); - - get_buffer()->signal_changed().connect([this]() { - if (update_status_location) - update_status_location(this); - }); - - signal_realize().connect([this] { - auto gutter = get_gutter(Gtk::TextWindowType::TEXT_WINDOW_LEFT); - auto renderer = gutter->get_renderer_at_pos(15, 0); - if (renderer) { - renderer_activate_connection.disconnect(); - renderer_activate_connection = renderer->signal_activate().connect( - [this](const Gtk::TextIter &iter, const Gdk::Rectangle &, GdkEvent *) { - if (toggle_breakpoint) - toggle_breakpoint(iter.get_line()); - }); - } - }); - - if (language && (language->get_id() == "chdr" || language->get_id() == "cpphdr" || language->get_id() == "c" || - language->get_id() == "cpp" || language->get_id() == "objc" || language->get_id() == "java" || - language->get_id() == "js" || language->get_id() == "ts" || language->get_id() == "proto" || - language->get_id() == "c-sharp" || language->get_id() == "html" || language->get_id() == "cuda" || - language->get_id() == "php" || language->get_id() == "rust" || language->get_id() == "swift" || - language->get_id() == "go" || language->get_id() == "scala" || language->get_id() == "opencl" || - language->get_id() == "json" || language->get_id() == "css")) - is_bracket_language = true; - - setup_tooltip_and_dialog_events(); - setup_format_style(is_generic_view); + : BaseView(file_path, language), SpellCheckView(file_path, language), DiffView(file_path, language) { + non_deleted_views.emplace(this); + views.emplace(this); + + search_settings = gtk_source_search_settings_new(); + gtk_source_search_settings_set_wrap_around(search_settings, true); + search_context = gtk_source_search_context_new(get_source_buffer()->gobj(), search_settings); + gtk_source_search_context_set_highlight(search_context, true); + //TODO: why does this not work?: Might be best to use the styles from sourceview. These has to be read from file, search-matches got style "search-match" + //TODO: in header if trying again: GtkSourceStyle* search_match_style; + //TODO: We can drop this, only work on newer versions of gtksourceview. + //search_match_style=(GtkSourceStyle*)g_object_new(GTK_SOURCE_TYPE_STYLE, "background-set", 1, "background", "#00FF00", nullptr); + //gtk_source_search_context_set_match_style(search_context, search_match_style); + + //TODO: either use lambda if possible or create a gtkmm wrapper around search_context (including search_settings): + //TODO: (gtkmm's Gtk::Object has connect_property_changed, so subclassing this might be an idea) + g_signal_connect(search_context, "notify::occurrences-count", G_CALLBACK(search_occurrences_updated), this); + + get_buffer()->create_tag("def:warning"); + get_buffer()->create_tag("def:warning_underline"); + get_buffer()->create_tag("def:error"); + get_buffer()->create_tag("def:error_underline"); + + auto mark_attr_debug_breakpoint = Gsv::MarkAttributes::create(); + Gdk::RGBA rgba; + rgba.set_red(1.0); + rgba.set_green(0.5); + rgba.set_blue(0.5); + rgba.set_alpha(0.3); + mark_attr_debug_breakpoint->set_background(rgba); + set_mark_attributes("debug_breakpoint", mark_attr_debug_breakpoint, 100); + auto mark_attr_debug_stop = Gsv::MarkAttributes::create(); + rgba.set_red(0.5); + rgba.set_green(0.5); + rgba.set_blue(1.0); + mark_attr_debug_stop->set_background(rgba); + set_mark_attributes("debug_stop", mark_attr_debug_stop, 101); + auto mark_attr_debug_breakpoint_and_stop = Gsv::MarkAttributes::create(); + rgba.set_red(0.75); + rgba.set_green(0.5); + rgba.set_blue(0.75); + mark_attr_debug_breakpoint_and_stop->set_background(rgba); + set_mark_attributes("debug_breakpoint_and_stop", mark_attr_debug_breakpoint_and_stop, 102); + + get_buffer()->signal_changed().connect([this]() { + if (update_status_location) + update_status_location(this); + }); + + signal_realize().connect([this] { + auto gutter = get_gutter(Gtk::TextWindowType::TEXT_WINDOW_LEFT); + auto renderer = gutter->get_renderer_at_pos(15, 0); + if (renderer) { + renderer_activate_connection.disconnect(); + renderer_activate_connection = renderer->signal_activate().connect( + [this](const Gtk::TextIter &iter, const Gdk::Rectangle &, GdkEvent *) { + if (toggle_breakpoint) + toggle_breakpoint(iter.get_line()); + }); + } + }); + + if (language && (language->get_id() == "chdr" || language->get_id() == "cpphdr" || language->get_id() == "c" || + language->get_id() == "cpp" || language->get_id() == "objc" || language->get_id() == "java" || + language->get_id() == "js" || language->get_id() == "ts" || language->get_id() == "proto" || + language->get_id() == "c-sharp" || language->get_id() == "html" || language->get_id() == "cuda" || + language->get_id() == "php" || language->get_id() == "rust" || language->get_id() == "swift" || + language->get_id() == "go" || language->get_id() == "scala" || language->get_id() == "opencl" || + language->get_id() == "json" || language->get_id() == "css")) + is_bracket_language = true; + + setup_tooltip_and_dialog_events(); + setup_format_style(is_generic_view); #ifndef __APPLE__ - set_tab_width(4); //Visual size of a \t hardcoded to be equal to visual size of 4 spaces. Buggy on OS X + set_tab_width(4); //Visual size of a \t hardcoded to be equal to visual size of 4 spaces. Buggy on OS X #endif - tab_char = Config::get().source.default_tab_char; - tab_size = Config::get().source.default_tab_size; - if (Config::get().source.auto_tab_char_and_size) { - auto tab_char_and_size = find_tab_char_and_size(); - if (tab_char_and_size.second != 0) { - if (tab_char != tab_char_and_size.first || tab_size != tab_char_and_size.second) { - std::string tab_str; - if (tab_char_and_size.first == ' ') - tab_str = "<space>"; - else - tab_str = "<tab>"; - } + tab_char = Config::get().source.default_tab_char; + tab_size = Config::get().source.default_tab_size; + if (Config::get().source.auto_tab_char_and_size) { + auto tab_char_and_size = find_tab_char_and_size(); + if (tab_char_and_size.second != 0) { + if (tab_char != tab_char_and_size.first || tab_size != tab_char_and_size.second) { + std::string tab_str; + if (tab_char_and_size.first == ' ') + tab_str = "<space>"; + else + tab_str = "<tab>"; + } - tab_char = tab_char_and_size.first; - tab_size = tab_char_and_size.second; - } + tab_char = tab_char_and_size.first; + tab_size = tab_char_and_size.second; } - set_tab_char_and_size(tab_char, tab_size); - - std::string comment_characters; - if (is_bracket_language) - comment_characters = "//"; - else if (language) { - if (language->get_id() == "cmake" || language->get_id() == "makefile" || language->get_id() == "python" || - language->get_id() == "python3" || language->get_id() == "sh" || language->get_id() == "perl" || - language->get_id() == "ruby" || language->get_id() == "r" || language->get_id() == "asm" || - language->get_id() == "automake") - comment_characters = "#"; - else if (language->get_id() == "latex" || language->get_id() == "matlab" || language->get_id() == "octave" || - language->get_id() == "bibtex") - comment_characters = "%"; - else if (language->get_id() == "fortran") - comment_characters = "!"; - else if (language->get_id() == "pascal") - comment_characters = "//"; - else if (language->get_id() == "lua") - comment_characters = "--"; - } - if (!comment_characters.empty()) { - toggle_comments = [this, comment_characters = std::move(comment_characters)] { - std::vector<int> lines; - Gtk::TextIter selection_start, selection_end; - get_buffer()->get_selection_bounds(selection_start, selection_end); - auto line_start = selection_start.get_line(); - auto line_end = selection_end.get_line(); - if (line_start != line_end && selection_end.starts_line()) - --line_end; - bool lines_commented = true; - bool extra_spaces = true; - int min_indentation = -1; - for (auto line = line_start; line <= line_end; ++line) { - auto iter = get_buffer()->get_iter_at_line(line); - bool line_added = false; - bool line_commented = false; - bool extra_space = false; - int indentation = 0; - for (;;) { - if (iter.ends_line()) - break; - else if (*iter == ' ' || *iter == '\t') { - ++indentation; - iter.forward_char(); - continue; - } else { - lines.emplace_back(line); - line_added = true; - for (size_t c = 0; c < comment_characters.size(); ++c) { - if (iter.ends_line()) { - break; - } else if (*iter == static_cast<unsigned int>(comment_characters[c])) { - if (c < comment_characters.size() - 1) { - iter.forward_char(); - continue; - } else { - line_commented = true; - if (!iter.ends_line()) { - iter.forward_char(); - if (*iter == ' ') - extra_space = true; - } - break; - } - } else - break; - } - break; - } - } - if (line_added) { - lines_commented &= line_commented; - extra_spaces &= extra_space; - if (min_indentation == -1 || indentation < min_indentation) - min_indentation = indentation; - } - } - if (lines.size()) { - auto comment_characters_and_space = comment_characters + ' '; - get_buffer()->begin_user_action(); - for (auto &line: lines) { - auto iter = get_buffer()->get_iter_at_line(line); - iter.forward_chars(min_indentation); - if (lines_commented) { - auto end_iter = iter; - end_iter.forward_chars(comment_characters.size() + static_cast<int>(extra_spaces)); - while (*iter == ' ' || *iter == '\t') { - iter.forward_char(); - end_iter.forward_char(); - } - get_buffer()->erase(iter, end_iter); - } else - get_buffer()->insert(iter, comment_characters_and_space); + } + set_tab_char_and_size(tab_char, tab_size); + + std::string comment_characters; + if (is_bracket_language) + comment_characters = "//"; + else if (language) { + if (language->get_id() == "cmake" || language->get_id() == "makefile" || language->get_id() == "python" || + language->get_id() == "python3" || language->get_id() == "sh" || language->get_id() == "perl" || + language->get_id() == "ruby" || language->get_id() == "r" || language->get_id() == "asm" || + language->get_id() == "automake") + comment_characters = "#"; + else if (language->get_id() == "latex" || language->get_id() == "matlab" || language->get_id() == "octave" || + language->get_id() == "bibtex") + comment_characters = "%"; + else if (language->get_id() == "fortran") + comment_characters = "!"; + else if (language->get_id() == "pascal") + comment_characters = "//"; + else if (language->get_id() == "lua") + comment_characters = "--"; + } + if (!comment_characters.empty()) { + toggle_comments = [this, comment_characters = std::move(comment_characters)] { + std::vector<int> lines; + Gtk::TextIter selection_start, selection_end; + get_buffer()->get_selection_bounds(selection_start, selection_end); + auto line_start = selection_start.get_line(); + auto line_end = selection_end.get_line(); + if (line_start != line_end && selection_end.starts_line()) + --line_end; + bool lines_commented = true; + bool extra_spaces = true; + int min_indentation = -1; + for (auto line = line_start; line <= line_end; ++line) { + auto iter = get_buffer()->get_iter_at_line(line); + bool line_added = false; + bool line_commented = false; + bool extra_space = false; + int indentation = 0; + for (;;) { + if (iter.ends_line()) + break; + else if (*iter == ' ' || *iter == '\t') { + ++indentation; + iter.forward_char(); + continue; + } else { + lines.emplace_back(line); + line_added = true; + for (size_t c = 0; c < comment_characters.size(); ++c) { + if (iter.ends_line()) { + break; + } else if (*iter == static_cast<unsigned int>(comment_characters[c])) { + if (c < comment_characters.size() - 1) { + iter.forward_char(); + continue; + } else { + line_commented = true; + if (!iter.ends_line()) { + iter.forward_char(); + if (*iter == ' ') + extra_space = true; + } + break; } - get_buffer()->end_user_action(); + } else + break; } - }; - } + break; + } + } + if (line_added) { + lines_commented &= line_commented; + extra_spaces &= extra_space; + if (min_indentation == -1 || indentation < min_indentation) + min_indentation = indentation; + } + } + if (lines.size()) { + auto comment_characters_and_space = comment_characters + ' '; + get_buffer()->begin_user_action(); + for (auto &line: lines) { + auto iter = get_buffer()->get_iter_at_line(line); + iter.forward_chars(min_indentation); + if (lines_commented) { + auto end_iter = iter; + end_iter.forward_chars(comment_characters.size() + static_cast<int>(extra_spaces)); + while (*iter == ' ' || *iter == '\t') { + iter.forward_char(); + end_iter.forward_char(); + } + get_buffer()->erase(iter, end_iter); + } else + get_buffer()->insert(iter, comment_characters_and_space); + } + get_buffer()->end_user_action(); + } + }; + } } void Source::View::set_tab_char_and_size(char tab_char, unsigned tab_size) { - this->tab_char = tab_char; - this->tab_size = tab_size; + this->tab_char = tab_char; + this->tab_size = tab_size; - tab.clear(); - for (unsigned c = 0; c < tab_size; c++) - tab += tab_char; + tab.clear(); + for (unsigned c = 0; c < tab_size; c++) + tab += tab_char; } void Source::View::cleanup_whitespace_characters() { - auto buffer = get_buffer(); - buffer->begin_user_action(); - for (int line = 0; line < buffer->get_line_count(); line++) { - auto iter = buffer->get_iter_at_line(line); - auto end_iter = get_iter_at_line_end(line); - if (iter == end_iter) - continue; - iter = end_iter; - while (!iter.starts_line() && (*iter == ' ' || *iter == '\t' || iter.ends_line())) - iter.backward_char(); - if (*iter != ' ' && *iter != '\t') - iter.forward_char(); - if (iter == end_iter) - continue; - buffer->erase(iter, end_iter); - } - auto iter = buffer->end(); - if (!iter.starts_line()) - buffer->insert(buffer->end(), "\n"); - buffer->end_user_action(); + auto buffer = get_buffer(); + buffer->begin_user_action(); + for (int line = 0; line < buffer->get_line_count(); line++) { + auto iter = buffer->get_iter_at_line(line); + auto end_iter = get_iter_at_line_end(line); + if (iter == end_iter) + continue; + iter = end_iter; + while (!iter.starts_line() && (*iter == ' ' || *iter == '\t' || iter.ends_line())) + iter.backward_char(); + if (*iter != ' ' && *iter != '\t') + iter.forward_char(); + if (iter == end_iter) + continue; + buffer->erase(iter, end_iter); + } + auto iter = buffer->end(); + if (!iter.starts_line()) + buffer->insert(buffer->end(), "\n"); + buffer->end_user_action(); } Gsv::DrawSpacesFlags Source::View::parse_show_whitespace_characters(const std::string &text) { - namespace qi = boost::spirit::qi; - - qi::symbols<char, Gsv::DrawSpacesFlags> options; - options.add - ("space", Gsv::DRAW_SPACES_SPACE) - ("tab", Gsv::DRAW_SPACES_TAB) - ("newline", Gsv::DRAW_SPACES_NEWLINE) - ("nbsp", Gsv::DRAW_SPACES_NBSP) - ("leading", Gsv::DRAW_SPACES_LEADING) - ("text", Gsv::DRAW_SPACES_TEXT) - ("trailing", Gsv::DRAW_SPACES_TRAILING) - ("all", Gsv::DRAW_SPACES_ALL); - - std::set<Gsv::DrawSpacesFlags> out; - - // parse comma-separated list of options - qi::phrase_parse(text.begin(), text.end(), options % ',', qi::space, out); - - return out.count(Gsv::DRAW_SPACES_ALL) > 0 ? - Gsv::DRAW_SPACES_ALL : - static_cast<Gsv::DrawSpacesFlags>(std::accumulate(out.begin(), out.end(), 0)); + namespace qi = boost::spirit::qi; + + qi::symbols<char, Gsv::DrawSpacesFlags> options; + options.add + ("space", Gsv::DRAW_SPACES_SPACE) + ("tab", Gsv::DRAW_SPACES_TAB) + ("newline", Gsv::DRAW_SPACES_NEWLINE) + ("nbsp", Gsv::DRAW_SPACES_NBSP) + ("leading", Gsv::DRAW_SPACES_LEADING) + ("text", Gsv::DRAW_SPACES_TEXT) + ("trailing", Gsv::DRAW_SPACES_TRAILING) + ("all", Gsv::DRAW_SPACES_ALL); + + std::set<Gsv::DrawSpacesFlags> out; + + // parse comma-separated list of options + qi::phrase_parse(text.begin(), text.end(), options % ',', qi::space, out); + + return out.count(Gsv::DRAW_SPACES_ALL) > 0 ? + Gsv::DRAW_SPACES_ALL : + static_cast<Gsv::DrawSpacesFlags>(std::accumulate(out.begin(), out.end(), 0)); } bool Source::View::save() { - if (file_path.empty() || !get_buffer()->get_modified()) - return false; - if (Config::get().source.cleanup_whitespace_characters) - cleanup_whitespace_characters(); - - if (format_style) { - if (Config::get().source.format_style_on_save) - format_style(true); - else if (Config::get().source.format_style_on_save_if_style_file_found) - format_style(false); - } + if (file_path.empty() || !get_buffer()->get_modified()) + return false; + if (Config::get().source.cleanup_whitespace_characters) + cleanup_whitespace_characters(); + + if (format_style) { + if (Config::get().source.format_style_on_save) + format_style(true); + else if (Config::get().source.format_style_on_save_if_style_file_found) + format_style(false); + } - std::ofstream output(file_path.string(), std::ofstream::binary); - if (output) { - auto start_iter = get_buffer()->begin(); - auto end_iter = start_iter; - bool end_reached = false; - while (!end_reached) { - for (size_t c = 0; c < 131072; c++) { - if (!end_iter.forward_char()) { - end_reached = true; - break; - } - } - output << get_buffer()->get_text(start_iter, end_iter).c_str(); - start_iter = end_iter; - } - output.close(); - boost::system::error_code ec; - last_write_time = boost::filesystem::last_write_time(file_path, ec); - if (ec) - last_write_time = static_cast<std::time_t>(-1); - // Remonitor file in case it did not exist before - monitor_file(); - get_buffer()->set_modified(false); - Directories::get().on_save_file(file_path); - return true; - } else { - Terminal::get().print("Error: could not save file " + file_path.string() + "\n", true); - return false; - } + std::ofstream output(file_path.string(), std::ofstream::binary); + if (output) { + auto start_iter = get_buffer()->begin(); + auto end_iter = start_iter; + bool end_reached = false; + while (!end_reached) { + for (size_t c = 0; c < 131072; c++) { + if (!end_iter.forward_char()) { + end_reached = true; + break; + } + } + output << get_buffer()->get_text(start_iter, end_iter).c_str(); + start_iter = end_iter; + } + output.close(); + boost::system::error_code ec; + last_write_time = boost::filesystem::last_write_time(file_path, ec); + if (ec) + last_write_time = static_cast<std::time_t>(-1); + // Remonitor file in case it did not exist before + monitor_file(); + get_buffer()->set_modified(false); + Directories::get().on_save_file(file_path); + return true; + } else { + Terminal::get().print("Error: could not save file " + file_path.string() + "\n", true); + return false; + } } void Source::View::configure() { - SpellCheckView::configure(); - DiffView::configure(); - - if (Config::get().source.style.size() > 0) { - auto scheme = StyleSchemeManager::get_default()->get_scheme(Config::get().source.style); - if (scheme) - get_source_buffer()->set_style_scheme(scheme); - } + SpellCheckView::configure(); + DiffView::configure(); - set_draw_spaces(parse_show_whitespace_characters(Config::get().source.show_whitespace_characters)); + if (Config::get().source.style.size() > 0) { + auto scheme = StyleSchemeManager::get_default()->get_scheme(Config::get().source.style); + if (scheme) + get_source_buffer()->set_style_scheme(scheme); + } - if (Config::get().source.wrap_lines) - set_wrap_mode(Gtk::WrapMode::WRAP_CHAR); - else - set_wrap_mode(Gtk::WrapMode::WRAP_NONE); - property_highlight_current_line() = Config::get().source.highlight_current_line; - property_show_line_numbers() = Config::get().source.show_line_numbers; - if (Config::get().source.font.size() > 0) - override_font(Pango::FontDescription(Config::get().source.font)); - if (Config::get().source.show_background_pattern) - gtk_source_view_set_background_pattern(this->gobj(), GTK_SOURCE_BACKGROUND_PATTERN_TYPE_GRID); - else - gtk_source_view_set_background_pattern(this->gobj(), GTK_SOURCE_BACKGROUND_PATTERN_TYPE_NONE); - property_show_right_margin() = Config::get().source.show_right_margin; - property_right_margin_position() = Config::get().source.right_margin_position; - - //Create tags for diagnostic warnings and errors: - auto scheme = get_source_buffer()->get_style_scheme(); - auto tag_table = get_buffer()->get_tag_table(); - auto style = scheme->get_style("def:warning"); - auto diagnostic_tag = get_buffer()->get_tag_table()->lookup("def:warning"); - auto diagnostic_tag_underline = get_buffer()->get_tag_table()->lookup("def:warning_underline"); - if (style && (style->property_foreground_set() || style->property_background_set())) { - Glib::ustring warning_property; - if (style->property_foreground_set()) { - warning_property = style->property_foreground().get_value(); - diagnostic_tag->property_foreground() = warning_property; - } else if (style->property_background_set()) - warning_property = style->property_background().get_value(); - - diagnostic_tag_underline->property_underline() = Pango::Underline::UNDERLINE_ERROR; - auto tag_class = G_OBJECT_GET_CLASS(diagnostic_tag_underline->gobj()); //For older GTK+ 3 versions: - auto param_spec = g_object_class_find_property(tag_class, "underline-rgba"); - if (param_spec != nullptr) { - diagnostic_tag_underline->set_property("underline-rgba", Gdk::RGBA(warning_property)); - } - } - style = scheme->get_style("def:error"); - diagnostic_tag = get_buffer()->get_tag_table()->lookup("def:error"); - diagnostic_tag_underline = get_buffer()->get_tag_table()->lookup("def:error_underline"); - if (style && (style->property_foreground_set() || style->property_background_set())) { - Glib::ustring error_property; - if (style->property_foreground_set()) { - error_property = style->property_foreground().get_value(); - diagnostic_tag->property_foreground() = error_property; - } else if (style->property_background_set()) - error_property = style->property_background().get_value(); - - diagnostic_tag_underline->property_underline() = Pango::Underline::UNDERLINE_ERROR; - diagnostic_tag_underline->set_property("underline-rgba", Gdk::RGBA(error_property)); - } - //TODO: clear tag_class and param_spec? - - if (Config::get().menu.keys["source_show_completion"].empty()) { - get_completion()->unblock_interactive(); - interactive_completion = true; - } else { - get_completion()->block_interactive(); - interactive_completion = false; + set_draw_spaces(parse_show_whitespace_characters(Config::get().source.show_whitespace_characters)); + + if (Config::get().source.wrap_lines) + set_wrap_mode(Gtk::WrapMode::WRAP_CHAR); + else + set_wrap_mode(Gtk::WrapMode::WRAP_NONE); + property_highlight_current_line() = Config::get().source.highlight_current_line; + property_show_line_numbers() = Config::get().source.show_line_numbers; + if (Config::get().source.font.size() > 0) + override_font(Pango::FontDescription(Config::get().source.font)); + if (Config::get().source.show_background_pattern) + gtk_source_view_set_background_pattern(this->gobj(), GTK_SOURCE_BACKGROUND_PATTERN_TYPE_GRID); + else + gtk_source_view_set_background_pattern(this->gobj(), GTK_SOURCE_BACKGROUND_PATTERN_TYPE_NONE); + property_show_right_margin() = Config::get().source.show_right_margin; + property_right_margin_position() = Config::get().source.right_margin_position; + + //Create tags for diagnostic warnings and errors: + auto scheme = get_source_buffer()->get_style_scheme(); + auto tag_table = get_buffer()->get_tag_table(); + auto style = scheme->get_style("def:warning"); + auto diagnostic_tag = get_buffer()->get_tag_table()->lookup("def:warning"); + auto diagnostic_tag_underline = get_buffer()->get_tag_table()->lookup("def:warning_underline"); + if (style && (style->property_foreground_set() || style->property_background_set())) { + Glib::ustring warning_property; + if (style->property_foreground_set()) { + warning_property = style->property_foreground().get_value(); + diagnostic_tag->property_foreground() = warning_property; + } else if (style->property_background_set()) + warning_property = style->property_background().get_value(); + + diagnostic_tag_underline->property_underline() = Pango::Underline::UNDERLINE_ERROR; + auto tag_class = G_OBJECT_GET_CLASS(diagnostic_tag_underline->gobj()); //For older GTK+ 3 versions: + auto param_spec = g_object_class_find_property(tag_class, "underline-rgba"); + if (param_spec != nullptr) { + diagnostic_tag_underline->set_property("underline-rgba", Gdk::RGBA(warning_property)); } + } + style = scheme->get_style("def:error"); + diagnostic_tag = get_buffer()->get_tag_table()->lookup("def:error"); + diagnostic_tag_underline = get_buffer()->get_tag_table()->lookup("def:error_underline"); + if (style && (style->property_foreground_set() || style->property_background_set())) { + Glib::ustring error_property; + if (style->property_foreground_set()) { + error_property = style->property_foreground().get_value(); + diagnostic_tag->property_foreground() = error_property; + } else if (style->property_background_set()) + error_property = style->property_background().get_value(); + + diagnostic_tag_underline->property_underline() = Pango::Underline::UNDERLINE_ERROR; + diagnostic_tag_underline->set_property("underline-rgba", Gdk::RGBA(error_property)); + } + //TODO: clear tag_class and param_spec? + + if (Config::get().menu.keys["source_show_completion"].empty()) { + get_completion()->unblock_interactive(); + interactive_completion = true; + } else { + get_completion()->block_interactive(); + interactive_completion = false; + } } void Source::View::setup_tooltip_and_dialog_events() { - type_tooltips.on_motion = [this] { - delayed_tooltips_connection.disconnect(); - }; - diagnostic_tooltips.on_motion = [this] { - delayed_tooltips_connection.disconnect(); - }; - - get_buffer()->signal_changed().connect([this] { - hide_tooltips(); - }); - - signal_motion_notify_event().connect([this](GdkEventMotion *event) { - if (on_motion_last_x != event->x || on_motion_last_y != event->y) { - delayed_tooltips_connection.disconnect(); - if ((event->state & GDK_BUTTON1_MASK) == 0) { - gdouble x = event->x; - gdouble y = event->y; - delayed_tooltips_connection = Glib::signal_timeout().connect([this, x, y]() { - type_tooltips.hide(); - diagnostic_tooltips.hide(); - Tooltips::init(); - Gdk::Rectangle rectangle(x, y, 1, 1); - if (parsed) { - show_type_tooltips(rectangle); - show_diagnostic_tooltips(rectangle); - } - return false; - }, 100); + type_tooltips.on_motion = [this] { + delayed_tooltips_connection.disconnect(); + }; + diagnostic_tooltips.on_motion = [this] { + delayed_tooltips_connection.disconnect(); + }; + + get_buffer()->signal_changed().connect([this] { + hide_tooltips(); + }); + + signal_motion_notify_event().connect([this](GdkEventMotion *event) { + if (on_motion_last_x != event->x || on_motion_last_y != event->y) { + delayed_tooltips_connection.disconnect(); + if ((event->state & GDK_BUTTON1_MASK) == 0) { + gdouble x = event->x; + gdouble y = event->y; + delayed_tooltips_connection = Glib::signal_timeout().connect([this, x, y]() { + type_tooltips.hide(); + diagnostic_tooltips.hide(); + Tooltips::init(); + Gdk::Rectangle rectangle(x, y, 1, 1); + if (parsed) { + show_type_tooltips(rectangle); + show_diagnostic_tooltips(rectangle); + } + return false; + }, 100); + } + auto last_mouse_pos = std::make_pair(on_motion_last_x, on_motion_last_y); + auto mouse_pos = std::make_pair(event->x, event->y); + type_tooltips.hide(last_mouse_pos, mouse_pos); + diagnostic_tooltips.hide(last_mouse_pos, mouse_pos); + } + on_motion_last_x = event->x; + on_motion_last_y = event->y; + return false; + }); + + get_buffer()->signal_mark_set().connect( + [this](const Gtk::TextBuffer::iterator &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { + if (get_buffer()->get_has_selection() && mark->get_name() == "selection_bound") + delayed_tooltips_connection.disconnect(); + + if (mark->get_name() == "insert") { + hide_tooltips(); + delayed_tooltips_connection = Glib::signal_timeout().connect([this]() { + Tooltips::init(); + Gdk::Rectangle rectangle; + get_iter_location(get_buffer()->get_insert()->get_iter(), rectangle); + int location_window_x, location_window_y; + buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, rectangle.get_x(), + rectangle.get_y(), location_window_x, location_window_y); + rectangle.set_x(location_window_x - 2); + rectangle.set_y(location_window_y); + rectangle.set_width(5); + if (parsed) { + show_type_tooltips(rectangle); + show_diagnostic_tooltips(rectangle); } - auto last_mouse_pos = std::make_pair(on_motion_last_x, on_motion_last_y); - auto mouse_pos = std::make_pair(event->x, event->y); - type_tooltips.hide(last_mouse_pos, mouse_pos); - diagnostic_tooltips.hide(last_mouse_pos, mouse_pos); - } - on_motion_last_x = event->x; - on_motion_last_y = event->y; - return false; - }); + return false; + }, 500); - get_buffer()->signal_mark_set().connect( - [this](const Gtk::TextBuffer::iterator &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { - if (get_buffer()->get_has_selection() && mark->get_name() == "selection_bound") - delayed_tooltips_connection.disconnect(); - - if (mark->get_name() == "insert") { - hide_tooltips(); - delayed_tooltips_connection = Glib::signal_timeout().connect([this]() { - Tooltips::init(); - Gdk::Rectangle rectangle; - get_iter_location(get_buffer()->get_insert()->get_iter(), rectangle); - int location_window_x, location_window_y; - buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, rectangle.get_x(), - rectangle.get_y(), location_window_x, location_window_y); - rectangle.set_x(location_window_x - 2); - rectangle.set_y(location_window_y); - rectangle.set_width(5); - if (parsed) { - show_type_tooltips(rectangle); - show_diagnostic_tooltips(rectangle); - } - return false; - }, 500); - - if (SelectionDialog::get()) - SelectionDialog::get()->hide(); - if (CompletionDialog::get()) - CompletionDialog::get()->hide(); - - if (update_status_location) - update_status_location(this); - } - }); + if (SelectionDialog::get()) + SelectionDialog::get()->hide(); + if (CompletionDialog::get()) + CompletionDialog::get()->hide(); - signal_scroll_event().connect([this](GdkEventScroll *event) { - hide_tooltips(); - hide_dialogs(); - return false; - }); + if (update_status_location) + update_status_location(this); + } + }); - signal_focus_out_event().connect([this](GdkEventFocus *event) { - hide_tooltips(); - return false; - }); + signal_scroll_event().connect([this](GdkEventScroll *event) { + hide_tooltips(); + hide_dialogs(); + return false; + }); - signal_leave_notify_event().connect([this](GdkEventCrossing *) { - delayed_tooltips_connection.disconnect(); - return false; - }); + signal_focus_out_event().connect([this](GdkEventFocus *event) { + hide_tooltips(); + return false; + }); + + signal_leave_notify_event().connect([this](GdkEventCrossing *) { + delayed_tooltips_connection.disconnect(); + return false; + }); } void Source::View::setup_format_style(bool is_generic_view) { - static auto prettier = filesystem::find_executable("prettier"); - if (!prettier.empty() && language && - (language->get_id() == "js" || language->get_id() == "json" || language->get_id() == "css")) { - if (is_generic_view) { - goto_next_diagnostic = [this] { - place_cursor_at_next_diagnostic(); - }; - get_buffer()->signal_changed().connect([this] { - clear_diagnostic_tooltips(); - status_diagnostics = std::make_tuple<size_t, size_t, size_t>(0, 0, 0); - if (update_status_diagnostics) - update_status_diagnostics(this); - }); - } - format_style = [this, is_generic_view](bool continue_without_style_file) { - auto command = prettier.string(); - if (!continue_without_style_file) { - std::stringstream stdin_stream, stdout_stream; - auto exit_status = Terminal::get().process(stdin_stream, stdout_stream, - command + " --find-config-path " + this->file_path.string()); - if (exit_status == 0) { - if (stdout_stream.tellp() == 0) - return; - } else - return; - } + static auto prettier = filesystem::find_executable("prettier"); + if (!prettier.empty() && language && + (language->get_id() == "js" || language->get_id() == "json" || language->get_id() == "css")) { + if (is_generic_view) { + goto_next_diagnostic = [this] { + place_cursor_at_next_diagnostic(); + }; + get_buffer()->signal_changed().connect([this] { + clear_diagnostic_tooltips(); + status_diagnostics = std::make_tuple<size_t, size_t, size_t>(0, 0, 0); + if (update_status_diagnostics) + update_status_diagnostics(this); + }); + } + format_style = [this, is_generic_view](bool continue_without_style_file) { + auto command = prettier.string(); + if (!continue_without_style_file) { + std::stringstream stdin_stream, stdout_stream; + auto exit_status = Terminal::get().process(stdin_stream, stdout_stream, + command + " --find-config-path " + this->file_path.string()); + if (exit_status == 0) { + if (stdout_stream.tellp() == 0) + return; + } else + return; + } - command += " --stdin-filepath " + this->file_path.string() + - " --print-width 120 --config-precedence prefer-file"; - - if (get_buffer()->get_has_selection()) { // Cannot be used together with --cursor-offset - Gtk::TextIter start, end; - get_buffer()->get_selection_bounds(start, end); - command += " --range-start " + std::to_string(start.get_offset()); - command += " --range-end " + std::to_string(end.get_offset()); - } else - command += " --cursor-offset " + std::to_string(get_buffer()->get_insert()->get_iter().get_offset()); - - size_t num_warnings = 0, num_errors = 0, num_fix_its = 0; - if (is_generic_view) - clear_diagnostic_tooltips(); - - std::stringstream stdin_stream(get_buffer()->get_text()), stdout_stream, stderr_stream; - auto exit_status = Terminal::get().process(stdin_stream, stdout_stream, command, - this->file_path.parent_path(), &stderr_stream); - if (exit_status == 0) { - replace_text(stdout_stream.str()); - std::string line; - std::getline(stderr_stream, line); - if (!line.empty() && line != "NaN") { - try { - auto offset = atoi(line.c_str()); - if (offset < get_buffer()->size()) { - get_buffer()->place_cursor(get_buffer()->get_iter_at_offset(offset)); - hide_tooltips(); - } - } - catch (...) {} - } - } else if (is_generic_view) { - static std::regex regex("^\\[error\\] stdin: (.*) \\(([0-9]*):([0-9]*)\\)$"); - std::string line; - std::getline(stderr_stream, line); - std::smatch sm; - if (std::regex_match(line, sm, regex)) { - try { - auto start = get_iter_at_line_offset(atoi(sm[2].str().c_str()) - 1, - atoi(sm[3].str().c_str()) - 1); - ++num_errors; - if (start.ends_line()) - start.backward_char(); - auto end = start; - end.forward_char(); - if (start == end) - start.forward_char(); - - add_diagnostic_tooltip(start, end, sm[1].str(), true); - } - catch (...) {} - } - } - if (is_generic_view) { - status_diagnostics = std::make_tuple(num_warnings, num_errors, num_fix_its); - if (update_status_diagnostics) - update_status_diagnostics(this); - } - hide_tooltips(); - }; - } else if (is_bracket_language) { - format_style = [this](bool continue_without_style_file) { - static auto clang_format_command = filesystem::get_executable("clang-format").string(); - - auto command = clang_format_command + " -output-replacements-xml -assume-filename=" + - filesystem::escape_argument(this->file_path.string()); - - if (get_buffer()->get_has_selection()) { - Gtk::TextIter start, end; - get_buffer()->get_selection_bounds(start, end); - command += " -lines=" + std::to_string(start.get_line() + 1) + ':' + std::to_string(end.get_line() + 1); - } + command += " --stdin-filepath " + this->file_path.string() + + " --print-width 120 --config-precedence prefer-file"; - bool use_style_file = false; - auto style_file_search_path = this->file_path.parent_path(); - while (true) { - if (boost::filesystem::exists(style_file_search_path / ".clang-format") || - boost::filesystem::exists(style_file_search_path / "_clang-format")) { - use_style_file = true; - break; - } - if (style_file_search_path == style_file_search_path.root_directory()) - break; - style_file_search_path = style_file_search_path.parent_path(); - } + if (get_buffer()->get_has_selection()) { // Cannot be used together with --cursor-offset + Gtk::TextIter start, end; + get_buffer()->get_selection_bounds(start, end); + command += " --range-start " + std::to_string(start.get_offset()); + command += " --range-end " + std::to_string(end.get_offset()); + } else + command += " --cursor-offset " + std::to_string(get_buffer()->get_insert()->get_iter().get_offset()); + + size_t num_warnings = 0, num_errors = 0, num_fix_its = 0; + if (is_generic_view) + clear_diagnostic_tooltips(); + + std::stringstream stdin_stream(get_buffer()->get_text()), stdout_stream, stderr_stream; + auto exit_status = Terminal::get().process(stdin_stream, stdout_stream, command, + this->file_path.parent_path(), &stderr_stream); + if (exit_status == 0) { + replace_text(stdout_stream.str()); + std::string line; + std::getline(stderr_stream, line); + if (!line.empty() && line != "NaN") { + try { + auto offset = atoi(line.c_str()); + if (offset < get_buffer()->size()) { + get_buffer()->place_cursor(get_buffer()->get_iter_at_offset(offset)); + hide_tooltips(); + } + } + catch (...) {} + } + } else if (is_generic_view) { + static std::regex regex("^\\[error\\] stdin: (.*) \\(([0-9]*):([0-9]*)\\)$"); + std::string line; + std::getline(stderr_stream, line); + std::smatch sm; + if (std::regex_match(line, sm, regex)) { + try { + auto start = get_iter_at_line_offset(atoi(sm[2].str().c_str()) - 1, + atoi(sm[3].str().c_str()) - 1); + ++num_errors; + if (start.ends_line()) + start.backward_char(); + auto end = start; + end.forward_char(); + if (start == end) + start.forward_char(); + + add_diagnostic_tooltip(start, end, sm[1].str(), true); + } + catch (...) {} + } + } + if (is_generic_view) { + status_diagnostics = std::make_tuple(num_warnings, num_errors, num_fix_its); + if (update_status_diagnostics) + update_status_diagnostics(this); + } + hide_tooltips(); + }; + } else if (is_bracket_language) { + format_style = [this](bool continue_without_style_file) { + static auto clang_format_command = filesystem::get_executable("clang-format").string(); - if (use_style_file) - command += " -style=file"; - else { - if (!continue_without_style_file) - return; - unsigned indent_width; - std::string tab_style; - if (tab_char == '\t') { - indent_width = tab_size * 8; - tab_style = "UseTab: Always"; - } else { - indent_width = tab_size; - tab_style = "UseTab: Never"; - } - command += " -style=\"{IndentWidth: " + std::to_string(indent_width); - command += ", " + tab_style; - command += ", " + std::string("AccessModifierOffset: -") + std::to_string(indent_width); - if (Config::get().source.clang_format_style != "") - command += ", " + Config::get().source.clang_format_style; - command += "}\""; - } + auto command = clang_format_command + " -output-replacements-xml -assume-filename=" + + filesystem::escape_argument(this->file_path.string()); + + if (get_buffer()->get_has_selection()) { + Gtk::TextIter start, end; + get_buffer()->get_selection_bounds(start, end); + command += " -lines=" + std::to_string(start.get_line() + 1) + ':' + std::to_string(end.get_line() + 1); + } + + bool use_style_file = false; + auto style_file_search_path = this->file_path.parent_path(); + while (true) { + if (boost::filesystem::exists(style_file_search_path / ".clang-format") || + boost::filesystem::exists(style_file_search_path / "_clang-format")) { + use_style_file = true; + break; + } + if (style_file_search_path == style_file_search_path.root_directory()) + break; + style_file_search_path = style_file_search_path.parent_path(); + } + + if (use_style_file) + command += " -style=file"; + else { + if (!continue_without_style_file) + return; + unsigned indent_width; + std::string tab_style; + if (tab_char == '\t') { + indent_width = tab_size * 8; + tab_style = "UseTab: Always"; + } else { + indent_width = tab_size; + tab_style = "UseTab: Never"; + } + command += " -style=\"{IndentWidth: " + std::to_string(indent_width); + command += ", " + tab_style; + command += ", " + std::string("AccessModifierOffset: -") + std::to_string(indent_width); + if (Config::get().source.clang_format_style != "") + command += ", " + Config::get().source.clang_format_style; + command += "}\""; + } - std::stringstream stdin_stream(get_buffer()->get_text()), stdout_stream; + std::stringstream stdin_stream(get_buffer()->get_text()), stdout_stream; - auto exit_status = Terminal::get().process(stdin_stream, stdout_stream, command, - this->file_path.parent_path()); - if (exit_status == 0) { - // The following code is complex due to clang-format returning offsets in byte offsets instead of char offsets + auto exit_status = Terminal::get().process(stdin_stream, stdout_stream, command, + this->file_path.parent_path()); + if (exit_status == 0) { + // The following code is complex due to clang-format returning offsets in byte offsets instead of char offsets - // Create bytes_in_lines cache to significantly speed up the processing of finding iterators from byte offsets - std::vector<size_t> bytes_in_lines; - auto line_count = get_buffer()->get_line_count(); - for (int line_nr = 0; line_nr < line_count; ++line_nr) { - auto iter = get_buffer()->get_iter_at_line(line_nr); - bytes_in_lines.emplace_back(iter.get_bytes_in_line()); - } + // Create bytes_in_lines cache to significantly speed up the processing of finding iterators from byte offsets + std::vector<size_t> bytes_in_lines; + auto line_count = get_buffer()->get_line_count(); + for (int line_nr = 0; line_nr < line_count; ++line_nr) { + auto iter = get_buffer()->get_iter_at_line(line_nr); + bytes_in_lines.emplace_back(iter.get_bytes_in_line()); + } - get_buffer()->begin_user_action(); - try { - boost::property_tree::ptree pt; - boost::property_tree::xml_parser::read_xml(stdout_stream, pt); - auto replacements_pt = pt.get_child("replacements"); - for (auto it = replacements_pt.rbegin(); it != replacements_pt.rend(); ++it) { - if (it->first == "replacement") { - auto offset = it->second.get<size_t>("<xmlattr>.offset"); - auto length = it->second.get<size_t>("<xmlattr>.length"); - auto replacement_str = it->second.get<std::string>(""); - - size_t bytes = 0; - for (size_t c = 0; c < bytes_in_lines.size(); ++c) { - auto previous_bytes = bytes; - bytes += bytes_in_lines[c]; - if (offset < bytes || (c == bytes_in_lines.size() - 1 && offset == bytes)) { - std::pair<size_t, size_t> line_index(c, offset - previous_bytes); - auto start = get_buffer()->get_iter_at_line_index(line_index.first, - line_index.second); - - // Use left gravity insert to avoid moving cursor from end of line - bool left_gravity_insert = false; - if (get_buffer()->get_insert()->get_iter() == start) { - auto iter = start; - do { - if (*iter != ' ' && *iter != '\t') { - left_gravity_insert = iter.ends_line(); - break; - } - } while (iter.forward_char()); - } - - if (length > 0) { - auto offset_end = offset + length; - size_t bytes = 0; - for (size_t c = 0; c < bytes_in_lines.size(); ++c) { - auto previous_bytes = bytes; - bytes += bytes_in_lines[c]; - if (offset_end < bytes || - (c == bytes_in_lines.size() - 1 && offset_end == bytes)) { - auto end = get_buffer()->get_iter_at_line_index(c, offset_end - - previous_bytes); - get_buffer()->erase(start, end); - start = get_buffer()->get_iter_at_line_index(line_index.first, - line_index.second); - break; - } - } - } - if (left_gravity_insert) { - auto mark = get_buffer()->create_mark(start); - get_buffer()->insert(start, replacement_str); - get_buffer()->place_cursor(mark->get_iter()); - get_buffer()->delete_mark(mark); - } else - get_buffer()->insert(start, replacement_str); - break; - } - } - } + get_buffer()->begin_user_action(); + try { + boost::property_tree::ptree pt; + boost::property_tree::xml_parser::read_xml(stdout_stream, pt); + auto replacements_pt = pt.get_child("replacements"); + for (auto it = replacements_pt.rbegin(); it != replacements_pt.rend(); ++it) { + if (it->first == "replacement") { + auto offset = it->second.get<size_t>("<xmlattr>.offset"); + auto length = it->second.get<size_t>("<xmlattr>.length"); + auto replacement_str = it->second.get<std::string>(""); + + size_t bytes = 0; + for (size_t c = 0; c < bytes_in_lines.size(); ++c) { + auto previous_bytes = bytes; + bytes += bytes_in_lines[c]; + if (offset < bytes || (c == bytes_in_lines.size() - 1 && offset == bytes)) { + std::pair<size_t, size_t> line_index(c, offset - previous_bytes); + auto start = get_buffer()->get_iter_at_line_index(line_index.first, + line_index.second); + + // Use left gravity insert to avoid moving cursor from end of line + bool left_gravity_insert = false; + if (get_buffer()->get_insert()->get_iter() == start) { + auto iter = start; + do { + if (*iter != ' ' && *iter != '\t') { + left_gravity_insert = iter.ends_line(); + break; + } + } while (iter.forward_char()); + } + + if (length > 0) { + auto offset_end = offset + length; + size_t bytes = 0; + for (size_t c = 0; c < bytes_in_lines.size(); ++c) { + auto previous_bytes = bytes; + bytes += bytes_in_lines[c]; + if (offset_end < bytes || + (c == bytes_in_lines.size() - 1 && offset_end == bytes)) { + auto end = get_buffer()->get_iter_at_line_index(c, offset_end - + previous_bytes); + get_buffer()->erase(start, end); + start = get_buffer()->get_iter_at_line_index(line_index.first, + line_index.second); + break; + } } + } + if (left_gravity_insert) { + auto mark = get_buffer()->create_mark(start); + get_buffer()->insert(start, replacement_str); + get_buffer()->place_cursor(mark->get_iter()); + get_buffer()->delete_mark(mark); + } else + get_buffer()->insert(start, replacement_str); + break; } - catch (const std::exception &e) { - Terminal::get().print(std::string("Error: error parsing clang-format output: ") + e.what() + '\n', - true); - } - get_buffer()->end_user_action(); + } } - }; - } else if (language && language->get_id() == "markdown") { - // The style file currently has no options, but checking if it exists - format_style = [this](bool continue_without_style_file) { - bool has_style_file = false; - auto style_file_search_path = this->file_path.parent_path(); - while (true) { - if (boost::filesystem::exists(style_file_search_path / ".markdown-format")) { - has_style_file = true; - break; - } - if (style_file_search_path == style_file_search_path.root_directory()) - break; - style_file_search_path = style_file_search_path.parent_path(); + } + } + catch (const std::exception &e) { + Terminal::get().print(std::string("Error: error parsing clang-format output: ") + e.what() + '\n', + true); + } + get_buffer()->end_user_action(); + } + }; + } else if (language && language->get_id() == "markdown") { + // The style file currently has no options, but checking if it exists + format_style = [this](bool continue_without_style_file) { + bool has_style_file = false; + auto style_file_search_path = this->file_path.parent_path(); + while (true) { + if (boost::filesystem::exists(style_file_search_path / ".markdown-format")) { + has_style_file = true; + break; + } + if (style_file_search_path == style_file_search_path.root_directory()) + break; + style_file_search_path = style_file_search_path.parent_path(); + } + if (!has_style_file && !continue_without_style_file) + return; + + auto special_character = [](Gtk::TextIter iter) { + if (*iter == '*' || *iter == '#' || *iter == '<' || *iter == '>' || *iter == ' ' || *iter == '=' || + *iter == '`' || *iter == '-') + return true; + // Tests if a line starts with for instance: 2. + if (*iter >= '0' && *iter <= '9' && iter.forward_char() && + *iter == '.' && iter.forward_char() && + *iter == ' ') + return true; + return false; + }; + + get_buffer()->begin_user_action(); + disable_spellcheck = true; + cleanup_whitespace_characters(); + + auto iter = get_buffer()->begin(); + size_t last_space_offset = -1; + bool headline = false; + bool monospace = false; + bool script = false; + bool html_tag = false; + int square_brackets = 0; + do { + if (iter.starts_line()) { + last_space_offset = -1; + auto next_line_iter = iter; + if (*iter == '#' || (next_line_iter.forward_line() && *next_line_iter == '=')) + headline = true; + else + headline = false; + auto test_iter = iter; + if (*test_iter == '`' && test_iter.forward_char() && + *test_iter == '`' && test_iter.forward_char() && + *test_iter == '`') { + script = !script; + iter.forward_chars(3); + continue; + } + } + if (!script && *iter == '`') + monospace = !monospace; + if (!script && !monospace) { + if (*iter == '<') + html_tag = true; + else if (*iter == '>') + html_tag = false; + else if (*iter == '[') + ++square_brackets; + else if (*iter == ']') + --square_brackets; + } + if (!headline && !script && !monospace && !html_tag && square_brackets == 0) { + if (*iter == ' ' && iter.get_line_offset() <= 80) + last_space_offset = iter.get_offset(); + // Insert newline on long lines + else if ((*iter == ' ' || iter.ends_line()) && iter.get_line_offset() > 80 && + last_space_offset != static_cast<size_t>(-1)) { + auto stored_iter = iter; + iter = get_buffer()->get_iter_at_offset(last_space_offset); + auto next_iter = iter; + next_iter.forward_char(); + // Do not add newline if the next iter is a special character + if (special_character(next_iter)) { + iter = stored_iter; + if (*iter == ' ') + last_space_offset = iter.get_offset(); + continue; + } + iter = get_buffer()->erase(iter, next_iter); + iter = get_buffer()->insert(iter, "\n"); + iter.backward_char(); + } + // Remove newline on short lines + else if (iter.ends_line() && !iter.starts_line() && iter.get_line_offset() <= 80) { + auto next_line_iter = iter; + // Do not remove newline if the next line for instance is a header + if (next_line_iter.forward_char() && !next_line_iter.ends_line() && + !special_character(next_line_iter)) { + auto end_word_iter = next_line_iter; + // Do not remove newline if the word on the next line is too long + size_t diff = 0; + while (*end_word_iter != ' ' && !end_word_iter.ends_line() && end_word_iter.forward_char()) + ++diff; + if (iter.get_line_offset() + diff + 1 <= 80) { + iter = get_buffer()->erase(iter, next_line_iter); + iter = get_buffer()->insert(iter, " "); + iter.backward_char(); + if (iter.get_line_offset() <= 80) + last_space_offset = iter.get_offset(); + } } - if (!has_style_file && !continue_without_style_file) - return; - - auto special_character = [](Gtk::TextIter iter) { - if (*iter == '*' || *iter == '#' || *iter == '<' || *iter == '>' || *iter == ' ' || *iter == '=' || - *iter == '`' || *iter == '-') - return true; - // Tests if a line starts with for instance: 2. - if (*iter >= '0' && *iter <= '9' && iter.forward_char() && - *iter == '.' && iter.forward_char() && - *iter == ' ') - return true; - return false; - }; - - get_buffer()->begin_user_action(); - disable_spellcheck = true; - cleanup_whitespace_characters(); - - auto iter = get_buffer()->begin(); - size_t last_space_offset = -1; - bool headline = false; - bool monospace = false; - bool script = false; - bool html_tag = false; - int square_brackets = 0; - do { - if (iter.starts_line()) { - last_space_offset = -1; - auto next_line_iter = iter; - if (*iter == '#' || (next_line_iter.forward_line() && *next_line_iter == '=')) - headline = true; - else - headline = false; - auto test_iter = iter; - if (*test_iter == '`' && test_iter.forward_char() && - *test_iter == '`' && test_iter.forward_char() && - *test_iter == '`') { - script = !script; - iter.forward_chars(3); - continue; - } - } - if (!script && *iter == '`') - monospace = !monospace; - if (!script && !monospace) { - if (*iter == '<') - html_tag = true; - else if (*iter == '>') - html_tag = false; - else if (*iter == '[') - ++square_brackets; - else if (*iter == ']') - --square_brackets; - } - if (!headline && !script && !monospace && !html_tag && square_brackets == 0) { - if (*iter == ' ' && iter.get_line_offset() <= 80) - last_space_offset = iter.get_offset(); - // Insert newline on long lines - else if ((*iter == ' ' || iter.ends_line()) && iter.get_line_offset() > 80 && - last_space_offset != static_cast<size_t>(-1)) { - auto stored_iter = iter; - iter = get_buffer()->get_iter_at_offset(last_space_offset); - auto next_iter = iter; - next_iter.forward_char(); - // Do not add newline if the next iter is a special character - if (special_character(next_iter)) { - iter = stored_iter; - if (*iter == ' ') - last_space_offset = iter.get_offset(); - continue; - } - iter = get_buffer()->erase(iter, next_iter); - iter = get_buffer()->insert(iter, "\n"); - iter.backward_char(); - } - // Remove newline on short lines - else if (iter.ends_line() && !iter.starts_line() && iter.get_line_offset() <= 80) { - auto next_line_iter = iter; - // Do not remove newline if the next line for instance is a header - if (next_line_iter.forward_char() && !next_line_iter.ends_line() && - !special_character(next_line_iter)) { - auto end_word_iter = next_line_iter; - // Do not remove newline if the word on the next line is too long - size_t diff = 0; - while (*end_word_iter != ' ' && !end_word_iter.ends_line() && end_word_iter.forward_char()) - ++diff; - if (iter.get_line_offset() + diff + 1 <= 80) { - iter = get_buffer()->erase(iter, next_line_iter); - iter = get_buffer()->insert(iter, " "); - iter.backward_char(); - if (iter.get_line_offset() <= 80) - last_space_offset = iter.get_offset(); - } - } - } - } - } while (iter.forward_char()); - disable_spellcheck = false; - get_buffer()->end_user_action(); - }; - } + } + } + } while (iter.forward_char()); + disable_spellcheck = false; + get_buffer()->end_user_action(); + }; + } } void Source::View::search_occurrences_updated(GtkWidget *widget, GParamSpec *property, gpointer data) { - auto view = static_cast<Source::View *>(data); - if (view->update_search_occurrences) - view->update_search_occurrences(gtk_source_search_context_get_occurrences_count(view->search_context)); + auto view = static_cast<Source::View *>(data); + if (view->update_search_occurrences) + view->update_search_occurrences(gtk_source_search_context_get_occurrences_count(view->search_context)); } Source::View::~View() { - g_clear_object(&search_context); - g_clear_object(&search_settings); + g_clear_object(&search_context); + g_clear_object(&search_settings); - delayed_tooltips_connection.disconnect(); - renderer_activate_connection.disconnect(); + delayed_tooltips_connection.disconnect(); + renderer_activate_connection.disconnect(); - non_deleted_views.erase(this); - views.erase(this); + non_deleted_views.erase(this); + views.erase(this); } void Source::View::search_highlight(const std::string &text, bool case_sensitive, bool regex) { - gtk_source_search_settings_set_case_sensitive(search_settings, case_sensitive); - gtk_source_search_settings_set_regex_enabled(search_settings, regex); - gtk_source_search_settings_set_search_text(search_settings, text.c_str()); - search_occurrences_updated(nullptr, nullptr, this); + gtk_source_search_settings_set_case_sensitive(search_settings, case_sensitive); + gtk_source_search_settings_set_regex_enabled(search_settings, regex); + gtk_source_search_settings_set_search_text(search_settings, text.c_str()); + search_occurrences_updated(nullptr, nullptr, this); } void Source::View::search_forward() { - Gtk::TextIter insert, selection_bound; - get_buffer()->get_selection_bounds(insert, selection_bound); - auto &start = selection_bound; - Gtk::TextIter match_start, match_end; + Gtk::TextIter insert, selection_bound; + get_buffer()->get_selection_bounds(insert, selection_bound); + auto &start = selection_bound; + Gtk::TextIter match_start, match_end; #if defined(GTK_SOURCE_MAJOR_VERSION) && (GTK_SOURCE_MAJOR_VERSION > 3 || (GTK_SOURCE_MAJOR_VERSION == 3 && GTK_SOURCE_MINOR_VERSION >= 22)) - gboolean has_wrapped_around; - if (gtk_source_search_context_forward2(search_context, start.gobj(), match_start.gobj(), match_end.gobj(), - &has_wrapped_around)) { + gboolean has_wrapped_around; + if (gtk_source_search_context_forward2(search_context, start.gobj(), match_start.gobj(), match_end.gobj(), + &has_wrapped_around)) { #else - if(gtk_source_search_context_forward(search_context, start.gobj(), match_start.gobj(), match_end.gobj())) { + if(gtk_source_search_context_forward(search_context, start.gobj(), match_start.gobj(), match_end.gobj())) { #endif - get_buffer()->select_range(match_start, match_end); - scroll_to(get_buffer()->get_insert()); - } + get_buffer()->select_range(match_start, match_end); + scroll_to(get_buffer()->get_insert()); + } } void Source::View::search_backward() { - Gtk::TextIter insert, selection_bound; - get_buffer()->get_selection_bounds(insert, selection_bound); - auto &start = insert; - Gtk::TextIter match_start, match_end; + Gtk::TextIter insert, selection_bound; + get_buffer()->get_selection_bounds(insert, selection_bound); + auto &start = insert; + Gtk::TextIter match_start, match_end; #if defined(GTK_SOURCE_MAJOR_VERSION) && (GTK_SOURCE_MAJOR_VERSION > 3 || (GTK_SOURCE_MAJOR_VERSION == 3 && GTK_SOURCE_MINOR_VERSION >= 22)) - gboolean has_wrapped_around; - if (gtk_source_search_context_backward2(search_context, start.gobj(), match_start.gobj(), match_end.gobj(), - &has_wrapped_around)) { + gboolean has_wrapped_around; + if (gtk_source_search_context_backward2(search_context, start.gobj(), match_start.gobj(), match_end.gobj(), + &has_wrapped_around)) { #else - if(gtk_source_search_context_backward(search_context, start.gobj(), match_start.gobj(), match_end.gobj())) { + if(gtk_source_search_context_backward(search_context, start.gobj(), match_start.gobj(), match_end.gobj())) { #endif - get_buffer()->select_range(match_start, match_end); - scroll_to(get_buffer()->get_insert()); - } + get_buffer()->select_range(match_start, match_end); + scroll_to(get_buffer()->get_insert()); + } } void Source::View::replace_forward(const std::string &replacement) { - Gtk::TextIter insert, selection_bound; - get_buffer()->get_selection_bounds(insert, selection_bound); - auto &start = insert; - Gtk::TextIter match_start, match_end; + Gtk::TextIter insert, selection_bound; + get_buffer()->get_selection_bounds(insert, selection_bound); + auto &start = insert; + Gtk::TextIter match_start, match_end; #if defined(GTK_SOURCE_MAJOR_VERSION) && (GTK_SOURCE_MAJOR_VERSION > 3 || (GTK_SOURCE_MAJOR_VERSION == 3 && GTK_SOURCE_MINOR_VERSION >= 22)) - gboolean has_wrapped_around; - if (gtk_source_search_context_forward2(search_context, start.gobj(), match_start.gobj(), match_end.gobj(), - &has_wrapped_around)) { + gboolean has_wrapped_around; + if (gtk_source_search_context_forward2(search_context, start.gobj(), match_start.gobj(), match_end.gobj(), + &has_wrapped_around)) { #else - if(gtk_source_search_context_forward(search_context, start.gobj(), match_start.gobj(), match_end.gobj())) { + if(gtk_source_search_context_forward(search_context, start.gobj(), match_start.gobj(), match_end.gobj())) { #endif - auto offset = match_start.get_offset(); + auto offset = match_start.get_offset(); #if defined(GTK_SOURCE_MAJOR_VERSION) && (GTK_SOURCE_MAJOR_VERSION > 3 || (GTK_SOURCE_MAJOR_VERSION == 3 && GTK_SOURCE_MINOR_VERSION >= 22)) - gtk_source_search_context_replace2(search_context, match_start.gobj(), match_end.gobj(), replacement.c_str(), - replacement.size(), nullptr); + gtk_source_search_context_replace2(search_context, match_start.gobj(), match_end.gobj(), replacement.c_str(), + replacement.size(), nullptr); #else - gtk_source_search_context_replace(search_context, match_start.gobj(), match_end.gobj(), replacement.c_str(), replacement.size(), nullptr); + gtk_source_search_context_replace(search_context, match_start.gobj(), match_end.gobj(), replacement.c_str(), replacement.size(), nullptr); #endif - Glib::ustring replacement_ustring = replacement; - get_buffer()->select_range(get_buffer()->get_iter_at_offset(offset), - get_buffer()->get_iter_at_offset(offset + replacement_ustring.size())); - scroll_to(get_buffer()->get_insert()); - } + Glib::ustring replacement_ustring = replacement; + get_buffer()->select_range(get_buffer()->get_iter_at_offset(offset), + get_buffer()->get_iter_at_offset(offset + replacement_ustring.size())); + scroll_to(get_buffer()->get_insert()); + } } void Source::View::replace_backward(const std::string &replacement) { - Gtk::TextIter insert, selection_bound; - get_buffer()->get_selection_bounds(insert, selection_bound); - auto &start = selection_bound; - Gtk::TextIter match_start, match_end; + Gtk::TextIter insert, selection_bound; + get_buffer()->get_selection_bounds(insert, selection_bound); + auto &start = selection_bound; + Gtk::TextIter match_start, match_end; #if defined(GTK_SOURCE_MAJOR_VERSION) && (GTK_SOURCE_MAJOR_VERSION > 3 || (GTK_SOURCE_MAJOR_VERSION == 3 && GTK_SOURCE_MINOR_VERSION >= 22)) - gboolean has_wrapped_around; - if (gtk_source_search_context_backward2(search_context, start.gobj(), match_start.gobj(), match_end.gobj(), - &has_wrapped_around)) { + gboolean has_wrapped_around; + if (gtk_source_search_context_backward2(search_context, start.gobj(), match_start.gobj(), match_end.gobj(), + &has_wrapped_around)) { #else - if(gtk_source_search_context_backward(search_context, start.gobj(), match_start.gobj(), match_end.gobj())) { + if(gtk_source_search_context_backward(search_context, start.gobj(), match_start.gobj(), match_end.gobj())) { #endif - auto offset = match_start.get_offset(); + auto offset = match_start.get_offset(); #if defined(GTK_SOURCE_MAJOR_VERSION) && (GTK_SOURCE_MAJOR_VERSION > 3 || (GTK_SOURCE_MAJOR_VERSION == 3 && GTK_SOURCE_MINOR_VERSION >= 22)) - gtk_source_search_context_replace2(search_context, match_start.gobj(), match_end.gobj(), replacement.c_str(), - replacement.size(), nullptr); + gtk_source_search_context_replace2(search_context, match_start.gobj(), match_end.gobj(), replacement.c_str(), + replacement.size(), nullptr); #else - gtk_source_search_context_replace(search_context, match_start.gobj(), match_end.gobj(), replacement.c_str(), replacement.size(), nullptr); + gtk_source_search_context_replace(search_context, match_start.gobj(), match_end.gobj(), replacement.c_str(), replacement.size(), nullptr); #endif - get_buffer()->select_range(get_buffer()->get_iter_at_offset(offset), - get_buffer()->get_iter_at_offset(offset + replacement.size())); - scroll_to(get_buffer()->get_insert()); - } + get_buffer()->select_range(get_buffer()->get_iter_at_offset(offset), + get_buffer()->get_iter_at_offset(offset + replacement.size())); + scroll_to(get_buffer()->get_insert()); + } } void Source::View::replace_all(const std::string &replacement) { - gtk_source_search_context_replace_all(search_context, replacement.c_str(), replacement.size(), nullptr); + gtk_source_search_context_replace_all(search_context, replacement.c_str(), replacement.size(), nullptr); } void Source::View::paste() { - class Guard { - public: - bool &value; + class Guard { + public: + bool &value; - Guard(bool &value_) : value(value_) { value = true; } - - ~Guard() { value = false; } - }; - Guard guard{multiple_cursors_use}; + Guard(bool &value_) : value(value_) { value = true; } - std::string text = Gtk::Clipboard::get()->wait_for_text(); + ~Guard() { value = false; } + }; + Guard guard{multiple_cursors_use}; - //Replace carriage returns (which leads to crash) with newlines - for (size_t c = 0; c < text.size(); c++) { - if (text[c] == '\r') { - if ((c + 1) < text.size() && text[c + 1] == '\n') - text.replace(c, 2, "\n"); - else - text.replace(c, 1, "\n"); - } - } + std::string text = Gtk::Clipboard::get()->wait_for_text(); - //Exception for when pasted text is only whitespaces - bool only_whitespaces = true; - for (auto &chr: text) { - if (chr != '\n' && chr != '\r' && chr != ' ' && chr != '\t') { - only_whitespaces = false; - break; - } - } - if (only_whitespaces) { - Gtk::Clipboard::get()->set_text(text); - get_buffer()->paste_clipboard(Gtk::Clipboard::get()); - scroll_to_cursor_delayed(this, false, false); - return; + //Replace carriage returns (which leads to crash) with newlines + for (size_t c = 0; c < text.size(); c++) { + if (text[c] == '\r') { + if ((c + 1) < text.size() && text[c + 1] == '\n') + text.replace(c, 2, "\n"); + else + text.replace(c, 1, "\n"); } + } - get_buffer()->begin_user_action(); - if (get_buffer()->get_has_selection()) { - Gtk::TextIter start, end; - get_buffer()->get_selection_bounds(start, end); - get_buffer()->erase(start, end); + //Exception for when pasted text is only whitespaces + bool only_whitespaces = true; + for (auto &chr: text) { + if (chr != '\n' && chr != '\r' && chr != ' ' && chr != '\t') { + only_whitespaces = false; + break; } - auto iter = get_buffer()->get_insert()->get_iter(); - auto tabs_end_iter = get_tabs_end_iter(); - auto prefix_tabs = get_line_before(iter < tabs_end_iter ? iter : tabs_end_iter); - - size_t start_line = 0; - size_t end_line = 0; - bool paste_line = false; - bool first_paste_line = true; - size_t paste_line_tabs = -1; - bool first_paste_line_has_tabs = false; - for (size_t c = 0; c < text.size(); c++) { - if (text[c] == '\n') { - end_line = c; - paste_line = true; - } else if (c == text.size() - 1) { - end_line = c + 1; - paste_line = true; - } - if (paste_line) { - bool empty_line = true; - std::string line = text.substr(start_line, end_line - start_line); - size_t tabs = 0; - for (auto chr: line) { - if (chr == tab_char) - tabs++; - else { - empty_line = false; - break; - } - } - if (first_paste_line) { - if (tabs != 0) { - first_paste_line_has_tabs = true; - paste_line_tabs = tabs; - } - first_paste_line = false; - } else if (!empty_line) - paste_line_tabs = std::min(paste_line_tabs, tabs); + } + if (only_whitespaces) { + Gtk::Clipboard::get()->set_text(text); + get_buffer()->paste_clipboard(Gtk::Clipboard::get()); + scroll_to_cursor_delayed(this, false, false); + return; + } - start_line = end_line + 1; - paste_line = false; + get_buffer()->begin_user_action(); + if (get_buffer()->get_has_selection()) { + Gtk::TextIter start, end; + get_buffer()->get_selection_bounds(start, end); + get_buffer()->erase(start, end); + } + auto iter = get_buffer()->get_insert()->get_iter(); + auto tabs_end_iter = get_tabs_end_iter(); + auto prefix_tabs = get_line_before(iter < tabs_end_iter ? iter : tabs_end_iter); + + size_t start_line = 0; + size_t end_line = 0; + bool paste_line = false; + bool first_paste_line = true; + size_t paste_line_tabs = -1; + bool first_paste_line_has_tabs = false; + for (size_t c = 0; c < text.size(); c++) { + if (text[c] == '\n') { + end_line = c; + paste_line = true; + } else if (c == text.size() - 1) { + end_line = c + 1; + paste_line = true; + } + if (paste_line) { + bool empty_line = true; + std::string line = text.substr(start_line, end_line - start_line); + size_t tabs = 0; + for (auto chr: line) { + if (chr == tab_char) + tabs++; + else { + empty_line = false; + break; } - } - if (paste_line_tabs == static_cast<size_t>(-1)) - paste_line_tabs = 0; - start_line = 0; - end_line = 0; - paste_line = false; - first_paste_line = true; - for (size_t c = 0; c < text.size(); c++) { - if (text[c] == '\n') { - end_line = c; - paste_line = true; - } else if (c == text.size() - 1) { - end_line = c + 1; - paste_line = true; + } + if (first_paste_line) { + if (tabs != 0) { + first_paste_line_has_tabs = true; + paste_line_tabs = tabs; } - if (paste_line) { - std::string line = text.substr(start_line, end_line - start_line); - size_t line_tabs = 0; - for (auto chr: line) { - if (chr == tab_char) - line_tabs++; - else - break; - } - auto tabs = paste_line_tabs; - if (!(first_paste_line && !first_paste_line_has_tabs) && line_tabs < paste_line_tabs) { - tabs = line_tabs; - } + first_paste_line = false; + } else if (!empty_line) + paste_line_tabs = std::min(paste_line_tabs, tabs); - if (first_paste_line) { - if (first_paste_line_has_tabs) - get_buffer()->insert_at_cursor(text.substr(start_line + tabs, end_line - start_line - tabs)); - else - get_buffer()->insert_at_cursor(text.substr(start_line, end_line - start_line)); - first_paste_line = false; - } else - get_buffer()->insert_at_cursor( - '\n' + prefix_tabs + text.substr(start_line + tabs, end_line - start_line - tabs)); - start_line = end_line + 1; - paste_line = false; - } + start_line = end_line + 1; + paste_line = false; } - // add final newline if present in text - if (text.size() > 0 && text.back() == '\n') - get_buffer()->insert_at_cursor('\n' + prefix_tabs); - get_buffer()->place_cursor(get_buffer()->get_insert()->get_iter()); - get_buffer()->end_user_action(); - scroll_to_cursor_delayed(this, false, false); + } + if (paste_line_tabs == static_cast<size_t>(-1)) + paste_line_tabs = 0; + start_line = 0; + end_line = 0; + paste_line = false; + first_paste_line = true; + for (size_t c = 0; c < text.size(); c++) { + if (text[c] == '\n') { + end_line = c; + paste_line = true; + } else if (c == text.size() - 1) { + end_line = c + 1; + paste_line = true; + } + if (paste_line) { + std::string line = text.substr(start_line, end_line - start_line); + size_t line_tabs = 0; + for (auto chr: line) { + if (chr == tab_char) + line_tabs++; + else + break; + } + auto tabs = paste_line_tabs; + if (!(first_paste_line && !first_paste_line_has_tabs) && line_tabs < paste_line_tabs) { + tabs = line_tabs; + } + + if (first_paste_line) { + if (first_paste_line_has_tabs) + get_buffer()->insert_at_cursor(text.substr(start_line + tabs, end_line - start_line - tabs)); + else + get_buffer()->insert_at_cursor(text.substr(start_line, end_line - start_line)); + first_paste_line = false; + } else + get_buffer()->insert_at_cursor( + '\n' + prefix_tabs + text.substr(start_line + tabs, end_line - start_line - tabs)); + start_line = end_line + 1; + paste_line = false; + } + } + // add final newline if present in text + if (text.size() > 0 && text.back() == '\n') + get_buffer()->insert_at_cursor('\n' + prefix_tabs); + get_buffer()->place_cursor(get_buffer()->get_insert()->get_iter()); + get_buffer()->end_user_action(); + scroll_to_cursor_delayed(this, false, false); } void Source::View::hide_tooltips() { - delayed_tooltips_connection.disconnect(); - type_tooltips.hide(); - diagnostic_tooltips.hide(); + delayed_tooltips_connection.disconnect(); + type_tooltips.hide(); + diagnostic_tooltips.hide(); } void Source::View::add_diagnostic_tooltip(const Gtk::TextIter &start, const Gtk::TextIter &end, std::string spelling, bool error) { - diagnostic_offsets.emplace(start.get_offset()); + diagnostic_offsets.emplace(start.get_offset()); - std::string severity_tag_name = error ? "def:error" : "def:warning"; + std::string severity_tag_name = error ? "def:error" : "def:warning"; - auto create_tooltip_buffer = [this, spelling = std::move(spelling), error, severity_tag_name]() { - auto tooltip_buffer = Gtk::TextBuffer::create(get_buffer()->get_tag_table()); - tooltip_buffer->insert_with_tag(tooltip_buffer->get_insert()->get_iter(), error ? "Error" : "Warning", - severity_tag_name); - tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), ":\n" + spelling); - return tooltip_buffer; - }; - diagnostic_tooltips.emplace_back(create_tooltip_buffer, this, get_buffer()->create_mark(start), - get_buffer()->create_mark(end)); + auto create_tooltip_buffer = [this, spelling = std::move(spelling), error, severity_tag_name]() { + auto tooltip_buffer = Gtk::TextBuffer::create(get_buffer()->get_tag_table()); + tooltip_buffer->insert_with_tag(tooltip_buffer->get_insert()->get_iter(), error ? "Error" : "Warning", + severity_tag_name); + tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), ":\n" + spelling); + return tooltip_buffer; + }; + diagnostic_tooltips.emplace_back(create_tooltip_buffer, this, get_buffer()->create_mark(start), + get_buffer()->create_mark(end)); - get_buffer()->apply_tag_by_name(severity_tag_name + "_underline", start, end); + get_buffer()->apply_tag_by_name(severity_tag_name + "_underline", start, end); - auto iter = get_buffer()->get_insert()->get_iter(); - if (iter.ends_line()) { - auto next_iter = iter; - if (next_iter.forward_char()) - get_buffer()->remove_tag_by_name(severity_tag_name + "_underline", iter, next_iter); - } + auto iter = get_buffer()->get_insert()->get_iter(); + if (iter.ends_line()) { + auto next_iter = iter; + if (next_iter.forward_char()) + get_buffer()->remove_tag_by_name(severity_tag_name + "_underline", iter, next_iter); + } } void Source::View::clear_diagnostic_tooltips() { - diagnostic_offsets.clear(); - diagnostic_tooltips.clear(); - get_buffer()->remove_tag_by_name("def:warning_underline", get_buffer()->begin(), get_buffer()->end()); - get_buffer()->remove_tag_by_name("def:error_underline", get_buffer()->begin(), get_buffer()->end()); + diagnostic_offsets.clear(); + diagnostic_tooltips.clear(); + get_buffer()->remove_tag_by_name("def:warning_underline", get_buffer()->begin(), get_buffer()->end()); + get_buffer()->remove_tag_by_name("def:error_underline", get_buffer()->begin(), get_buffer()->end()); } void Source::View::hide_dialogs() { - SpellCheckView::hide_dialogs(); - if (SelectionDialog::get()) - SelectionDialog::get()->hide(); - if (CompletionDialog::get()) - CompletionDialog::get()->hide(); + SpellCheckView::hide_dialogs(); + if (SelectionDialog::get()) + SelectionDialog::get()->hide(); + if (CompletionDialog::get()) + CompletionDialog::get()->hide(); } bool Source::View::find_open_non_curly_bracket_backward(Gtk::TextIter iter, Gtk::TextIter &found_iter) { - long para_count = 0; - long square_count = 0; - long curly_count = 0; - - do { - if (*iter == '(' && is_code_iter(iter)) - para_count++; - else if (*iter == ')' && is_code_iter(iter)) - para_count--; - else if (*iter == '[' && is_code_iter(iter)) - square_count++; - else if (*iter == ']' && is_code_iter(iter)) - square_count--; - else if (*iter == '{' && is_code_iter(iter)) - curly_count++; - else if (*iter == '}' && is_code_iter(iter)) - curly_count--; - - if (curly_count > 0) - break; - - if (para_count > 0 || square_count > 0) { - found_iter = iter; - return true; - } - } while (iter.backward_char()); - return false; + long para_count = 0; + long square_count = 0; + long curly_count = 0; + + do { + if (*iter == '(' && is_code_iter(iter)) + para_count++; + else if (*iter == ')' && is_code_iter(iter)) + para_count--; + else if (*iter == '[' && is_code_iter(iter)) + square_count++; + else if (*iter == ']' && is_code_iter(iter)) + square_count--; + else if (*iter == '{' && is_code_iter(iter)) + curly_count++; + else if (*iter == '}' && is_code_iter(iter)) + curly_count--; + + if (curly_count > 0) + break; + + if (para_count > 0 || square_count > 0) { + found_iter = iter; + return true; + } + } while (iter.backward_char()); + return false; } Gtk::TextIter Source::View::find_start_of_sentence(Gtk::TextIter iter) { - if (iter.starts_line()) - return iter; - - bool stream_operator_test = false; - bool colon_test = false; - - if (*iter == ';') - stream_operator_test = true; - if (*iter == '{') { - iter.backward_char(); - colon_test = true; - } - - int para_count = 0; - int square_count = 0; - long curly_count = 0; + if (iter.starts_line()) + return iter; - do { - if (*iter == '(' && is_code_iter(iter)) - para_count++; - else if (*iter == ')' && is_code_iter(iter)) - para_count--; - else if (*iter == '[' && is_code_iter(iter)) - square_count++; - else if (*iter == ']' && is_code_iter(iter)) - square_count--; - else if (*iter == '{' && is_code_iter(iter)) - curly_count++; - else if (*iter == '}' && is_code_iter(iter)) - curly_count--; - - if (curly_count > 0) - break; + bool stream_operator_test = false; + bool colon_test = false; - if (iter.starts_line() && para_count == 0 && square_count == 0) { - bool stream_operator_found = false; - bool colon_found = false; - // Handle << at the beginning of the sentence if iter initially started with ; - if (stream_operator_test) { - auto test_iter = get_tabs_end_iter(iter); - if (!test_iter.starts_line() && *test_iter == '<' && is_code_iter(test_iter) && - test_iter.forward_char() && *test_iter == '<') - stream_operator_found = true; - } - // Handle : at the beginning of the sentence if iter initially started with { - else if (colon_test) { - auto test_iter = get_tabs_end_iter(iter); - if (!test_iter.starts_line() && *test_iter == ':' && is_code_iter(test_iter)) - colon_found = true; - } - // Handle : and , on previous line - if (!stream_operator_found && !colon_found) { - auto previous_iter = iter; - previous_iter.backward_char(); - while (!previous_iter.starts_line() && (*previous_iter == ' ' || previous_iter.ends_line()) && - previous_iter.backward_char()) {} - if (*previous_iter != ',' && *previous_iter != ':') - return iter; - else if (*previous_iter == ':') { - previous_iter.backward_char(); - while (!previous_iter.starts_line() && *previous_iter == ' ' && previous_iter.backward_char()) {} - if (*previous_iter == ')') { - auto token = get_token( - get_tabs_end_iter(get_buffer()->get_iter_at_line(previous_iter.get_line()))); - if (token == "case") - return iter; - } else - return iter; - } - } - } - } while (iter.backward_char()); + if (*iter == ';') + stream_operator_test = true; + if (*iter == '{') { + iter.backward_char(); + colon_test = true; + } - return iter; + int para_count = 0; + int square_count = 0; + long curly_count = 0; + + do { + if (*iter == '(' && is_code_iter(iter)) + para_count++; + else if (*iter == ')' && is_code_iter(iter)) + para_count--; + else if (*iter == '[' && is_code_iter(iter)) + square_count++; + else if (*iter == ']' && is_code_iter(iter)) + square_count--; + else if (*iter == '{' && is_code_iter(iter)) + curly_count++; + else if (*iter == '}' && is_code_iter(iter)) + curly_count--; + + if (curly_count > 0) + break; + + if (iter.starts_line() && para_count == 0 && square_count == 0) { + bool stream_operator_found = false; + bool colon_found = false; + // Handle << at the beginning of the sentence if iter initially started with ; + if (stream_operator_test) { + auto test_iter = get_tabs_end_iter(iter); + if (!test_iter.starts_line() && *test_iter == '<' && is_code_iter(test_iter) && + test_iter.forward_char() && *test_iter == '<') + stream_operator_found = true; + } + // Handle : at the beginning of the sentence if iter initially started with { + else if (colon_test) { + auto test_iter = get_tabs_end_iter(iter); + if (!test_iter.starts_line() && *test_iter == ':' && is_code_iter(test_iter)) + colon_found = true; + } + // Handle : and , on previous line + if (!stream_operator_found && !colon_found) { + auto previous_iter = iter; + previous_iter.backward_char(); + while (!previous_iter.starts_line() && (*previous_iter == ' ' || previous_iter.ends_line()) && + previous_iter.backward_char()) {} + if (*previous_iter != ',' && *previous_iter != ':') + return iter; + else if (*previous_iter == ':') { + previous_iter.backward_char(); + while (!previous_iter.starts_line() && *previous_iter == ' ' && previous_iter.backward_char()) {} + if (*previous_iter == ')') { + auto token = get_token( + get_tabs_end_iter(get_buffer()->get_iter_at_line(previous_iter.get_line()))); + if (token == "case") + return iter; + } else + return iter; + } + } + } + } while (iter.backward_char()); + + return iter; } bool Source::View::find_open_curly_bracket_backward(Gtk::TextIter iter, Gtk::TextIter &found_iter) { - long count = 0; + long count = 0; - do { - if (*iter == '{') { - if (count == 0 && is_code_iter(iter)) { - found_iter = iter; - return true; - } - count++; - } else if (*iter == '}' && is_code_iter(iter)) - count--; - } while (iter.backward_char()); - return false; + do { + if (*iter == '{') { + if (count == 0 && is_code_iter(iter)) { + found_iter = iter; + return true; + } + count++; + } else if (*iter == '}' && is_code_iter(iter)) + count--; + } while (iter.backward_char()); + return false; } bool Source::View::find_close_curly_bracket_forward(Gtk::TextIter iter, Gtk::TextIter &found_iter) { - long count = 0; + long count = 0; - do { - if (*iter == '}' && is_code_iter(iter)) { - if (count == 0) { - found_iter = iter; - return true; - } - count--; - } else if (*iter == '{' && is_code_iter(iter)) - count++; - } while (iter.forward_char()); - return false; + do { + if (*iter == '}' && is_code_iter(iter)) { + if (count == 0) { + found_iter = iter; + return true; + } + count--; + } else if (*iter == '{' && is_code_iter(iter)) + count++; + } while (iter.forward_char()); + return false; } long Source::View::symbol_count(Gtk::TextIter iter, unsigned int positive_char, unsigned int negative_char) { - auto iter_stored = iter; - long symbol_count = 0; - long curly_count = 0; - bool break_on_curly = true; - if (positive_char == '{' || negative_char == '}') - break_on_curly = false; - bool check_if_next_iter_is_code_iter = false; - if (positive_char == '\'' || negative_char == '\'' || positive_char == '"' || negative_char == '"') - check_if_next_iter_is_code_iter = true; - - Gtk::TextIter previous_iter; - do { - if (*iter == positive_char && is_code_iter(iter)) - symbol_count++; - else if (*iter == negative_char && is_code_iter(iter)) - symbol_count--; - else if (*iter == '{' && is_code_iter(iter)) - curly_count++; - else if (*iter == '}' && is_code_iter(iter)) - curly_count--; - else if (check_if_next_iter_is_code_iter) { - auto next_iter = iter; - next_iter.forward_char(); - if (*iter == positive_char && is_code_iter(next_iter)) - symbol_count++; - else if (*iter == negative_char && is_code_iter(next_iter)) - symbol_count--; - } + auto iter_stored = iter; + long symbol_count = 0; + long curly_count = 0; + bool break_on_curly = true; + if (positive_char == '{' || negative_char == '}') + break_on_curly = false; + bool check_if_next_iter_is_code_iter = false; + if (positive_char == '\'' || negative_char == '\'' || positive_char == '"' || negative_char == '"') + check_if_next_iter_is_code_iter = true; + + Gtk::TextIter previous_iter; + do { + if (*iter == positive_char && is_code_iter(iter)) + symbol_count++; + else if (*iter == negative_char && is_code_iter(iter)) + symbol_count--; + else if (*iter == '{' && is_code_iter(iter)) + curly_count++; + else if (*iter == '}' && is_code_iter(iter)) + curly_count--; + else if (check_if_next_iter_is_code_iter) { + auto next_iter = iter; + next_iter.forward_char(); + if (*iter == positive_char && is_code_iter(next_iter)) + symbol_count++; + else if (*iter == negative_char && is_code_iter(next_iter)) + symbol_count--; + } + + if (break_on_curly && curly_count > 0) + break; + } while (iter.backward_char()); + + iter = iter_stored; + if (!iter.forward_char()) { + return symbol_count; + } - if (break_on_curly && curly_count > 0) - break; - } while (iter.backward_char()); - - iter = iter_stored; - if (!iter.forward_char()) { - return symbol_count; - } - - curly_count = 0; - do { - if (*iter == positive_char && is_code_iter(iter)) - symbol_count++; - else if (*iter == negative_char && is_code_iter(iter)) - symbol_count--; - else if (*iter == '{' && is_code_iter(iter)) - curly_count++; - else if (*iter == '}' && is_code_iter(iter)) - curly_count--; - else if (check_if_next_iter_is_code_iter) { - auto next_iter = iter; - next_iter.forward_char(); - if (*iter == positive_char && is_code_iter(next_iter)) - symbol_count++; - else if (*iter == negative_char && is_code_iter(next_iter)) - symbol_count--; - } - - if (break_on_curly && curly_count < 0) - break; - } while (iter.forward_char()); - - return symbol_count; + curly_count = 0; + do { + if (*iter == positive_char && is_code_iter(iter)) + symbol_count++; + else if (*iter == negative_char && is_code_iter(iter)) + symbol_count--; + else if (*iter == '{' && is_code_iter(iter)) + curly_count++; + else if (*iter == '}' && is_code_iter(iter)) + curly_count--; + else if (check_if_next_iter_is_code_iter) { + auto next_iter = iter; + next_iter.forward_char(); + if (*iter == positive_char && is_code_iter(next_iter)) + symbol_count++; + else if (*iter == negative_char && is_code_iter(next_iter)) + symbol_count--; + } + + if (break_on_curly && curly_count < 0) + break; + } while (iter.forward_char()); + + return symbol_count; } bool Source::View::is_templated_function(Gtk::TextIter iter, Gtk::TextIter &parenthesis_end_iter) { - auto iter_stored = iter; - long bracket_count = 0; - long curly_count = 0; + auto iter_stored = iter; + long bracket_count = 0; + long curly_count = 0; - if (!(iter.backward_char() && *iter == '>' && *iter_stored == '(')) - return false; - - do { - if (*iter == '<' && is_code_iter(iter)) - bracket_count++; - else if (*iter == '>' && is_code_iter(iter)) - bracket_count--; - else if (*iter == '{' && is_code_iter(iter)) - curly_count++; - else if (*iter == '}' && is_code_iter(iter)) - curly_count--; - - if (bracket_count == 0) - break; + if (!(iter.backward_char() && *iter == '>' && *iter_stored == '(')) + return false; - if (curly_count > 0) - break; - } while (iter.backward_char()); + do { + if (*iter == '<' && is_code_iter(iter)) + bracket_count++; + else if (*iter == '>' && is_code_iter(iter)) + bracket_count--; + else if (*iter == '{' && is_code_iter(iter)) + curly_count++; + else if (*iter == '}' && is_code_iter(iter)) + curly_count--; - if (bracket_count != 0) - return false; + if (bracket_count == 0) + break; - iter = iter_stored; - bracket_count = 0; - curly_count = 0; - do { - if (*iter == '(' && is_code_iter(iter)) - bracket_count++; - else if (*iter == ')' && is_code_iter(iter)) - bracket_count--; - else if (*iter == '{' && is_code_iter(iter)) - curly_count++; - else if (*iter == '}' && is_code_iter(iter)) - curly_count--; - - if (bracket_count == 0) { - parenthesis_end_iter = iter; - return true; - } - - if (curly_count < 0) - return false; - } while (iter.forward_char()); + if (curly_count > 0) + break; + } while (iter.backward_char()); + if (bracket_count != 0) return false; + + iter = iter_stored; + bracket_count = 0; + curly_count = 0; + do { + if (*iter == '(' && is_code_iter(iter)) + bracket_count++; + else if (*iter == ')' && is_code_iter(iter)) + bracket_count--; + else if (*iter == '{' && is_code_iter(iter)) + curly_count++; + else if (*iter == '}' && is_code_iter(iter)) + curly_count--; + + if (bracket_count == 0) { + parenthesis_end_iter = iter; + return true; + } + + if (curly_count < 0) + return false; + } while (iter.forward_char()); + + return false; } std::string Source::View::get_token(Gtk::TextIter iter) { - auto start = iter; - auto end = iter; - - while ((*iter >= 'A' && *iter <= 'Z') || (*iter >= 'a' && *iter <= 'z') || (*iter >= '0' && *iter <= '9') || - *iter == '_') { - start = iter; - if (!iter.backward_char()) - break; - } - while ((*end >= 'A' && *end <= 'Z') || (*end >= 'a' && *end <= 'z') || (*end >= '0' && *end <= '9') || - *end == '_') { - if (!end.forward_char()) - break; - } + auto start = iter; + auto end = iter; + + while ((*iter >= 'A' && *iter <= 'Z') || (*iter >= 'a' && *iter <= 'z') || (*iter >= '0' && *iter <= '9') || + *iter == '_') { + start = iter; + if (!iter.backward_char()) + break; + } + while ((*end >= 'A' && *end <= 'Z') || (*end >= 'a' && *end <= 'z') || (*end >= '0' && *end <= '9') || + *end == '_') { + if (!end.forward_char()) + break; + } - return get_buffer()->get_text(start, end); + return get_buffer()->get_text(start, end); } void Source::View::cleanup_whitespace_characters_on_return(const Gtk::TextIter &iter) { - auto start_blank_iter = iter; - auto end_blank_iter = iter; - while ((*end_blank_iter == ' ' || *end_blank_iter == '\t') && - !end_blank_iter.ends_line() && end_blank_iter.forward_char()) {} - if (!start_blank_iter.starts_line()) { - start_blank_iter.backward_char(); - while ((*start_blank_iter == ' ' || *start_blank_iter == '\t') && - !start_blank_iter.starts_line() && start_blank_iter.backward_char()) {} - if (*start_blank_iter != ' ' && *start_blank_iter != '\t') - start_blank_iter.forward_char(); - } - - if (start_blank_iter.starts_line()) - get_buffer()->erase(iter, end_blank_iter); - else - get_buffer()->erase(start_blank_iter, end_blank_iter); + auto start_blank_iter = iter; + auto end_blank_iter = iter; + while ((*end_blank_iter == ' ' || *end_blank_iter == '\t') && + !end_blank_iter.ends_line() && end_blank_iter.forward_char()) {} + if (!start_blank_iter.starts_line()) { + start_blank_iter.backward_char(); + while ((*start_blank_iter == ' ' || *start_blank_iter == '\t') && + !start_blank_iter.starts_line() && start_blank_iter.backward_char()) {} + if (*start_blank_iter != ' ' && *start_blank_iter != '\t') + start_blank_iter.forward_char(); + } + + if (start_blank_iter.starts_line()) + get_buffer()->erase(iter, end_blank_iter); + else + get_buffer()->erase(start_blank_iter, end_blank_iter); } bool Source::View::on_key_press_event(GdkEventKey *key) { - class Guard { - public: - bool &value; + class Guard { + public: + bool &value; - Guard(bool &value_) : value(value_) { value = true; } + Guard(bool &value_) : value(value_) { value = true; } - ~Guard() { value = false; } - }; - Guard guard{multiple_cursors_use}; + ~Guard() { value = false; } + }; + Guard guard{multiple_cursors_use}; - if (SelectionDialog::get() && SelectionDialog::get()->is_visible()) { - if (SelectionDialog::get()->on_key_press(key)) - return true; - } - if (CompletionDialog::get() && CompletionDialog::get()->is_visible()) { - if (CompletionDialog::get()->on_key_press(key)) - return true; - } + if (SelectionDialog::get() && SelectionDialog::get()->is_visible()) { + if (SelectionDialog::get()->on_key_press(key)) + return true; + } + if (CompletionDialog::get() && CompletionDialog::get()->is_visible()) { + if (CompletionDialog::get()->on_key_press(key)) + return true; + } - if (last_keyval < GDK_KEY_Shift_L || last_keyval > GDK_KEY_Hyper_R) - previous_non_modifier_keyval = last_keyval; - last_keyval = key->keyval; + if (last_keyval < GDK_KEY_Shift_L || last_keyval > GDK_KEY_Hyper_R) + previous_non_modifier_keyval = last_keyval; + last_keyval = key->keyval; - if (Config::get().source.enable_multiple_cursors && on_key_press_event_multiple_cursors(key)) - return true; + if (Config::get().source.enable_multiple_cursors && on_key_press_event_multiple_cursors(key)) + return true; - //Move cursor one paragraph down - if ((key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down) && (key->state & GDK_CONTROL_MASK) > 0) { - auto selection_start_iter = get_buffer()->get_selection_bound()->get_iter(); - auto iter = get_buffer()->get_iter_at_line(get_buffer()->get_insert()->get_iter().get_line()); - bool empty_line = false; - bool text_found = false; - for (;;) { - if (!iter) - break; - if (iter.starts_line()) - empty_line = true; - if (empty_line && !iter.ends_line() && *iter != '\n' && *iter != ' ' && *iter != '\t') - empty_line = false; - if (!text_found && !iter.ends_line() && *iter != '\n' && *iter != ' ' && *iter != '\t') - text_found = true; - if (empty_line && text_found && iter.ends_line()) - break; - iter.forward_char(); - } - iter = get_buffer()->get_iter_at_line(iter.get_line()); - if ((key->state & GDK_SHIFT_MASK) > 0) - get_buffer()->select_range(iter, selection_start_iter); - else - get_buffer()->place_cursor(iter); - scroll_to(get_buffer()->get_insert()); - return true; - } - //Move cursor one paragraph up - else if ((key->keyval == GDK_KEY_Up || key->keyval == GDK_KEY_KP_Up) && (key->state & GDK_CONTROL_MASK) > 0) { - auto selection_start_iter = get_buffer()->get_selection_bound()->get_iter(); - auto iter = get_buffer()->get_iter_at_line(get_buffer()->get_insert()->get_iter().get_line()); - iter.backward_char(); - bool empty_line = false; - bool text_found = false; - bool move_to_start = false; - for (;;) { - if (!iter) - break; - if (iter.ends_line()) - empty_line = true; - if (empty_line && !iter.ends_line() && *iter != '\n' && *iter != ' ' && *iter != '\t') - empty_line = false; - if (!text_found && !iter.ends_line() && *iter != '\n' && *iter != ' ' && *iter != '\t') - text_found = true; - if (empty_line && text_found && iter.starts_line()) - break; - if (iter.is_start()) { - move_to_start = true; - break; - } - iter.backward_char(); - } - if (empty_line && !move_to_start) { - iter = get_iter_at_line_end(iter.get_line()); - iter.forward_char(); - if (!iter.starts_line()) // For CR+LF - iter.forward_char(); - } - if ((key->state & GDK_SHIFT_MASK) > 0) - get_buffer()->select_range(iter, selection_start_iter); - else - get_buffer()->place_cursor(iter); - scroll_to(get_buffer()->get_insert()); - return true; + //Move cursor one paragraph down + if ((key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down) && (key->state & GDK_CONTROL_MASK) > 0) { + auto selection_start_iter = get_buffer()->get_selection_bound()->get_iter(); + auto iter = get_buffer()->get_iter_at_line(get_buffer()->get_insert()->get_iter().get_line()); + bool empty_line = false; + bool text_found = false; + for (;;) { + if (!iter) + break; + if (iter.starts_line()) + empty_line = true; + if (empty_line && !iter.ends_line() && *iter != '\n' && *iter != ' ' && *iter != '\t') + empty_line = false; + if (!text_found && !iter.ends_line() && *iter != '\n' && *iter != ' ' && *iter != '\t') + text_found = true; + if (empty_line && text_found && iter.ends_line()) + break; + iter.forward_char(); + } + iter = get_buffer()->get_iter_at_line(iter.get_line()); + if ((key->state & GDK_SHIFT_MASK) > 0) + get_buffer()->select_range(iter, selection_start_iter); + else + get_buffer()->place_cursor(iter); + scroll_to(get_buffer()->get_insert()); + return true; + } + //Move cursor one paragraph up + else if ((key->keyval == GDK_KEY_Up || key->keyval == GDK_KEY_KP_Up) && (key->state & GDK_CONTROL_MASK) > 0) { + auto selection_start_iter = get_buffer()->get_selection_bound()->get_iter(); + auto iter = get_buffer()->get_iter_at_line(get_buffer()->get_insert()->get_iter().get_line()); + iter.backward_char(); + bool empty_line = false; + bool text_found = false; + bool move_to_start = false; + for (;;) { + if (!iter) + break; + if (iter.ends_line()) + empty_line = true; + if (empty_line && !iter.ends_line() && *iter != '\n' && *iter != ' ' && *iter != '\t') + empty_line = false; + if (!text_found && !iter.ends_line() && *iter != '\n' && *iter != ' ' && *iter != '\t') + text_found = true; + if (empty_line && text_found && iter.starts_line()) + break; + if (iter.is_start()) { + move_to_start = true; + break; + } + iter.backward_char(); + } + if (empty_line && !move_to_start) { + iter = get_iter_at_line_end(iter.get_line()); + iter.forward_char(); + if (!iter.starts_line()) // For CR+LF + iter.forward_char(); } + if ((key->state & GDK_SHIFT_MASK) > 0) + get_buffer()->select_range(iter, selection_start_iter); + else + get_buffer()->place_cursor(iter); + scroll_to(get_buffer()->get_insert()); + return true; + } - get_buffer()->begin_user_action(); + get_buffer()->begin_user_action(); - // Shift+enter: go to end of line and enter - if ((key->keyval == GDK_KEY_Return || key->keyval == GDK_KEY_KP_Enter) && (key->state & GDK_SHIFT_MASK) > 0) { - auto iter = get_buffer()->get_insert()->get_iter(); - if (!iter.ends_line()) { - iter.forward_to_line_end(); - get_buffer()->place_cursor(iter); - } + // Shift+enter: go to end of line and enter + if ((key->keyval == GDK_KEY_Return || key->keyval == GDK_KEY_KP_Enter) && (key->state & GDK_SHIFT_MASK) > 0) { + auto iter = get_buffer()->get_insert()->get_iter(); + if (!iter.ends_line()) { + iter.forward_to_line_end(); + get_buffer()->place_cursor(iter); } + } - if (Config::get().source.smart_brackets && on_key_press_event_smart_brackets(key)) { - get_buffer()->end_user_action(); - return true; - } - if (Config::get().source.smart_inserts && on_key_press_event_smart_inserts(key)) { - get_buffer()->end_user_action(); - return true; - } + if (Config::get().source.smart_brackets && on_key_press_event_smart_brackets(key)) { + get_buffer()->end_user_action(); + return true; + } + if (Config::get().source.smart_inserts && on_key_press_event_smart_inserts(key)) { + get_buffer()->end_user_action(); + return true; + } - if (is_bracket_language && on_key_press_event_bracket_language(key)) { - get_buffer()->end_user_action(); - return true; - } else if (on_key_press_event_basic(key)) { - get_buffer()->end_user_action(); - return true; - } else { - get_buffer()->end_user_action(); - return Gsv::View::on_key_press_event(key); - } + if (is_bracket_language && on_key_press_event_bracket_language(key)) { + get_buffer()->end_user_action(); + return true; + } else if (on_key_press_event_basic(key)) { + get_buffer()->end_user_action(); + return true; + } else { + get_buffer()->end_user_action(); + return Gsv::View::on_key_press_event(key); + } } //Basic indentation bool Source::View::on_key_press_event_basic(GdkEventKey *key) { - auto iter = get_buffer()->get_insert()->get_iter(); + auto iter = get_buffer()->get_insert()->get_iter(); - //Indent as in next or previous line - if ((key->keyval == GDK_KEY_Return || key->keyval == GDK_KEY_KP_Enter) && !get_buffer()->get_has_selection() && - !iter.starts_line()) { - cleanup_whitespace_characters_on_return(iter); - - iter = get_buffer()->get_insert()->get_iter(); - auto tabs = get_line_before(get_tabs_end_iter(iter)); - - int line_nr = iter.get_line(); - if (iter.ends_line() && (line_nr + 1) < get_buffer()->get_line_count()) { - auto next_line_tabs = get_line_before(get_tabs_end_iter(line_nr + 1)); - if (next_line_tabs.size() > tabs.size()) { - get_buffer()->insert_at_cursor("\n" + next_line_tabs); - scroll_to(get_buffer()->get_insert()); - return true; - } - } - get_buffer()->insert_at_cursor("\n" + tabs); - scroll_to(get_buffer()->get_insert()); - return true; - } else if (key->keyval == GDK_KEY_Tab && (key->state & GDK_SHIFT_MASK) == 0) { - if (!Config::get().source.tab_indents_line && !get_buffer()->get_has_selection()) { - get_buffer()->insert_at_cursor(tab); - return true; - } - //Indent right when clicking tab, no matter where in the line the cursor is. Also works on selected text. - //Special case if insert is at beginning of empty line: - if (iter.starts_line() && iter.ends_line() && !get_buffer()->get_has_selection()) { - auto prev_line_iter = iter; - while (prev_line_iter.starts_line() && prev_line_iter.backward_char()) {} - auto prev_line_tabs_end_iter = get_tabs_end_iter(prev_line_iter); - auto previous_line_tabs = get_line_before(prev_line_tabs_end_iter); + //Indent as in next or previous line + if ((key->keyval == GDK_KEY_Return || key->keyval == GDK_KEY_KP_Enter) && !get_buffer()->get_has_selection() && + !iter.starts_line()) { + cleanup_whitespace_characters_on_return(iter); - auto next_line_iter = iter; - while (next_line_iter.starts_line() && next_line_iter.forward_char()) {} - auto next_line_tabs_end_iter = get_tabs_end_iter(next_line_iter); - auto next_line_tabs = get_line_before(next_line_tabs_end_iter); - - std::string tabs; - if (previous_line_tabs.size() < next_line_tabs.size()) - tabs = previous_line_tabs; - else - tabs = next_line_tabs; - if (tabs.size() >= tab_size) { - get_buffer()->insert_at_cursor(tabs); - return true; - } - } + iter = get_buffer()->get_insert()->get_iter(); + auto tabs = get_line_before(get_tabs_end_iter(iter)); - Gtk::TextIter selection_start, selection_end; - get_buffer()->get_selection_bounds(selection_start, selection_end); - auto selection_end_mark = get_buffer()->create_mark(selection_end); - int line_start = selection_start.get_line(); - int line_end = selection_end.get_line(); - for (int line = line_start; line <= line_end; line++) { - Gtk::TextIter line_it = get_buffer()->get_iter_at_line(line); - if (!get_buffer()->get_has_selection() || line_it != selection_end_mark->get_iter()) - get_buffer()->insert(line_it, tab); - } - get_buffer()->delete_mark(selection_end_mark); + int line_nr = iter.get_line(); + if (iter.ends_line() && (line_nr + 1) < get_buffer()->get_line_count()) { + auto next_line_tabs = get_line_before(get_tabs_end_iter(line_nr + 1)); + if (next_line_tabs.size() > tabs.size()) { + get_buffer()->insert_at_cursor("\n" + next_line_tabs); + scroll_to(get_buffer()->get_insert()); return true; + } } - //Indent left when clicking shift-tab, no matter where in the line the cursor is. Also works on selected text. - else if ((key->keyval == GDK_KEY_ISO_Left_Tab || key->keyval == GDK_KEY_Tab) && (key->state & GDK_SHIFT_MASK) > 0) { - Gtk::TextIter selection_start, selection_end; - get_buffer()->get_selection_bounds(selection_start, selection_end); - int line_start = selection_start.get_line(); - int line_end = selection_end.get_line(); - - unsigned indent_left_steps = tab_size; - std::vector<bool> ignore_line; - for (int line_nr = line_start; line_nr <= line_end; line_nr++) { - auto line_it = get_buffer()->get_iter_at_line(line_nr); - if (!get_buffer()->get_has_selection() || line_it != selection_end) { - auto tabs_end_iter = get_tabs_end_iter(line_nr); - if (tabs_end_iter.starts_line() && tabs_end_iter.ends_line()) - ignore_line.push_back(true); - else { - auto line_tabs = get_line_before(tabs_end_iter); - - if (line_tabs.size() > 0) { - indent_left_steps = std::min(indent_left_steps, static_cast<unsigned>(line_tabs.size())); - ignore_line.push_back(false); - } else - return true; - } - } - } - - for (int line_nr = line_start; line_nr <= line_end; line_nr++) { - Gtk::TextIter line_it = get_buffer()->get_iter_at_line(line_nr); - Gtk::TextIter line_plus_it = line_it; - if (!get_buffer()->get_has_selection() || line_it != selection_end) { - line_plus_it.forward_chars(indent_left_steps); - if (!ignore_line.at(line_nr - line_start)) - get_buffer()->erase(line_it, line_plus_it); - } - } + get_buffer()->insert_at_cursor("\n" + tabs); + scroll_to(get_buffer()->get_insert()); + return true; + } else if (key->keyval == GDK_KEY_Tab && (key->state & GDK_SHIFT_MASK) == 0) { + if (!Config::get().source.tab_indents_line && !get_buffer()->get_has_selection()) { + get_buffer()->insert_at_cursor(tab); + return true; + } + //Indent right when clicking tab, no matter where in the line the cursor is. Also works on selected text. + //Special case if insert is at beginning of empty line: + if (iter.starts_line() && iter.ends_line() && !get_buffer()->get_has_selection()) { + auto prev_line_iter = iter; + while (prev_line_iter.starts_line() && prev_line_iter.backward_char()) {} + auto prev_line_tabs_end_iter = get_tabs_end_iter(prev_line_iter); + auto previous_line_tabs = get_line_before(prev_line_tabs_end_iter); + + auto next_line_iter = iter; + while (next_line_iter.starts_line() && next_line_iter.forward_char()) {} + auto next_line_tabs_end_iter = get_tabs_end_iter(next_line_iter); + auto next_line_tabs = get_line_before(next_line_tabs_end_iter); + + std::string tabs; + if (previous_line_tabs.size() < next_line_tabs.size()) + tabs = previous_line_tabs; + else + tabs = next_line_tabs; + if (tabs.size() >= tab_size) { + get_buffer()->insert_at_cursor(tabs); return true; + } } - //"Smart" backspace key - else if (key->keyval == GDK_KEY_BackSpace && !get_buffer()->get_has_selection()) { - auto line = get_line_before(); - bool do_smart_backspace = true; - for (auto &chr: line) { - if (chr != ' ' && chr != '\t') { - do_smart_backspace = false; - break; - } - } - if (iter.get_line() == 0) // Special case since there are no previous line - do_smart_backspace = false; - if (do_smart_backspace) { - auto previous_line_end_iter = iter; - if (previous_line_end_iter.backward_chars(line.size() + 1)) { - if (!previous_line_end_iter.ends_line()) // For CR+LF - previous_line_end_iter.backward_char(); - if (previous_line_end_iter.starts_line()) // When previous line is empty, keep tabs in current line - get_buffer()->erase(previous_line_end_iter, get_buffer()->get_iter_at_line(iter.get_line())); - else - get_buffer()->erase(previous_line_end_iter, iter); - return true; - } - } + + Gtk::TextIter selection_start, selection_end; + get_buffer()->get_selection_bounds(selection_start, selection_end); + auto selection_end_mark = get_buffer()->create_mark(selection_end); + int line_start = selection_start.get_line(); + int line_end = selection_end.get_line(); + for (int line = line_start; line <= line_end; line++) { + Gtk::TextIter line_it = get_buffer()->get_iter_at_line(line); + if (!get_buffer()->get_has_selection() || line_it != selection_end_mark->get_iter()) + get_buffer()->insert(line_it, tab); } - //"Smart" delete key - else if (key->keyval == GDK_KEY_Delete && !get_buffer()->get_has_selection()) { - auto insert_iter = iter; - bool do_smart_delete = true; - do { - if (*iter != ' ' && *iter != '\t' && !iter.ends_line()) { - do_smart_delete = false; - break; - } - if (iter.ends_line()) { - if (*iter == '\r') // For CR+LF - iter.forward_char(); - if (!iter.forward_char()) - do_smart_delete = false; - break; - } - } while (iter.forward_char()); - if (do_smart_delete) { - if (!insert_iter.starts_line()) { - while ((*iter == ' ' || *iter == '\t') && iter.forward_char()) {} - } - get_buffer()->erase(insert_iter, iter); + get_buffer()->delete_mark(selection_end_mark); + return true; + } + //Indent left when clicking shift-tab, no matter where in the line the cursor is. Also works on selected text. + else if ((key->keyval == GDK_KEY_ISO_Left_Tab || key->keyval == GDK_KEY_Tab) && (key->state & GDK_SHIFT_MASK) > 0) { + Gtk::TextIter selection_start, selection_end; + get_buffer()->get_selection_bounds(selection_start, selection_end); + int line_start = selection_start.get_line(); + int line_end = selection_end.get_line(); + + unsigned indent_left_steps = tab_size; + std::vector<bool> ignore_line; + for (int line_nr = line_start; line_nr <= line_end; line_nr++) { + auto line_it = get_buffer()->get_iter_at_line(line_nr); + if (!get_buffer()->get_has_selection() || line_it != selection_end) { + auto tabs_end_iter = get_tabs_end_iter(line_nr); + if (tabs_end_iter.starts_line() && tabs_end_iter.ends_line()) + ignore_line.push_back(true); + else { + auto line_tabs = get_line_before(tabs_end_iter); + + if (line_tabs.size() > 0) { + indent_left_steps = std::min(indent_left_steps, static_cast<unsigned>(line_tabs.size())); + ignore_line.push_back(false); + } else return true; } + } } - // Smart Home/End-keys - else if ((key->keyval == GDK_KEY_Home || key->keyval == GDK_KEY_KP_Home) && (key->state & GDK_CONTROL_MASK) == 0) { - if ((key->state & GDK_SHIFT_MASK) > 0) - get_buffer()->move_mark_by_name("insert", get_smart_home_iter(iter)); - else - get_buffer()->place_cursor(get_smart_home_iter(iter)); - scroll_to(get_buffer()->get_insert()); - return true; - } else if ((key->keyval == GDK_KEY_End || key->keyval == GDK_KEY_KP_End) && (key->state & GDK_CONTROL_MASK) == 0) { - if ((key->state & GDK_SHIFT_MASK) > 0) - get_buffer()->move_mark_by_name("insert", get_smart_end_iter(iter)); + + for (int line_nr = line_start; line_nr <= line_end; line_nr++) { + Gtk::TextIter line_it = get_buffer()->get_iter_at_line(line_nr); + Gtk::TextIter line_plus_it = line_it; + if (!get_buffer()->get_has_selection() || line_it != selection_end) { + line_plus_it.forward_chars(indent_left_steps); + if (!ignore_line.at(line_nr - line_start)) + get_buffer()->erase(line_it, line_plus_it); + } + } + return true; + } + //"Smart" backspace key + else if (key->keyval == GDK_KEY_BackSpace && !get_buffer()->get_has_selection()) { + auto line = get_line_before(); + bool do_smart_backspace = true; + for (auto &chr: line) { + if (chr != ' ' && chr != '\t') { + do_smart_backspace = false; + break; + } + } + if (iter.get_line() == 0) // Special case since there are no previous line + do_smart_backspace = false; + if (do_smart_backspace) { + auto previous_line_end_iter = iter; + if (previous_line_end_iter.backward_chars(line.size() + 1)) { + if (!previous_line_end_iter.ends_line()) // For CR+LF + previous_line_end_iter.backward_char(); + if (previous_line_end_iter.starts_line()) // When previous line is empty, keep tabs in current line + get_buffer()->erase(previous_line_end_iter, get_buffer()->get_iter_at_line(iter.get_line())); else - get_buffer()->place_cursor(get_smart_end_iter(iter)); - scroll_to(get_buffer()->get_insert()); + get_buffer()->erase(previous_line_end_iter, iter); return true; + } } + } + //"Smart" delete key + else if (key->keyval == GDK_KEY_Delete && !get_buffer()->get_has_selection()) { + auto insert_iter = iter; + bool do_smart_delete = true; + do { + if (*iter != ' ' && *iter != '\t' && !iter.ends_line()) { + do_smart_delete = false; + break; + } + if (iter.ends_line()) { + if (*iter == '\r') // For CR+LF + iter.forward_char(); + if (!iter.forward_char()) + do_smart_delete = false; + break; + } + } while (iter.forward_char()); + if (do_smart_delete) { + if (!insert_iter.starts_line()) { + while ((*iter == ' ' || *iter == '\t') && iter.forward_char()) {} + } + get_buffer()->erase(insert_iter, iter); + return true; + } + } + // Smart Home/End-keys + else if ((key->keyval == GDK_KEY_Home || key->keyval == GDK_KEY_KP_Home) && (key->state & GDK_CONTROL_MASK) == 0) { + if ((key->state & GDK_SHIFT_MASK) > 0) + get_buffer()->move_mark_by_name("insert", get_smart_home_iter(iter)); + else + get_buffer()->place_cursor(get_smart_home_iter(iter)); + scroll_to(get_buffer()->get_insert()); + return true; + } else if ((key->keyval == GDK_KEY_End || key->keyval == GDK_KEY_KP_End) && (key->state & GDK_CONTROL_MASK) == 0) { + if ((key->state & GDK_SHIFT_MASK) > 0) + get_buffer()->move_mark_by_name("insert", get_smart_end_iter(iter)); + else + get_buffer()->place_cursor(get_smart_end_iter(iter)); + scroll_to(get_buffer()->get_insert()); + return true; + } - //Workaround for TextView::on_key_press_event bug sometimes causing segmentation faults - //TODO: figure out the bug and create pull request to gtk - //Have only experienced this on OS X - //Note: valgrind reports issues on TextView::on_key_press_event as well - auto unicode = gdk_keyval_to_unicode(key->keyval); - if ((key->state & (GDK_CONTROL_MASK | GDK_META_MASK)) == 0 && unicode >= 32 && unicode != 127 && - (previous_non_modifier_keyval < GDK_KEY_dead_grave || previous_non_modifier_keyval > GDK_KEY_dead_greek)) { - if (get_buffer()->get_has_selection()) { - Gtk::TextIter selection_start, selection_end; - get_buffer()->get_selection_bounds(selection_start, selection_end); - get_buffer()->erase(selection_start, selection_end); - } - get_buffer()->insert_at_cursor(Glib::ustring(1, unicode)); - scroll_to(get_buffer()->get_insert()); + //Workaround for TextView::on_key_press_event bug sometimes causing segmentation faults + //TODO: figure out the bug and create pull request to gtk + //Have only experienced this on OS X + //Note: valgrind reports issues on TextView::on_key_press_event as well + auto unicode = gdk_keyval_to_unicode(key->keyval); + if ((key->state & (GDK_CONTROL_MASK | GDK_META_MASK)) == 0 && unicode >= 32 && unicode != 127 && + (previous_non_modifier_keyval < GDK_KEY_dead_grave || previous_non_modifier_keyval > GDK_KEY_dead_greek)) { + if (get_buffer()->get_has_selection()) { + Gtk::TextIter selection_start, selection_end; + get_buffer()->get_selection_bounds(selection_start, selection_end); + get_buffer()->erase(selection_start, selection_end); + } + get_buffer()->insert_at_cursor(Glib::ustring(1, unicode)); + scroll_to(get_buffer()->get_insert()); - //Trick to make the cursor visible right after insertion: - set_cursor_visible(false); - set_cursor_visible(); + //Trick to make the cursor visible right after insertion: + set_cursor_visible(false); + set_cursor_visible(); - return true; - } + return true; + } - return false; + return false; } //Bracket language indentation bool Source::View::on_key_press_event_bracket_language(GdkEventKey *key) { - const static std::regex no_bracket_statement_regex("^ *(if|for|while) *\\(.*[^;}{] *$|" - "^[}]? *else if *\\(.*[^;}{] *$|" - "^[}]? *else *$", std::regex::extended); - - auto iter = get_buffer()->get_insert()->get_iter(); + const static std::regex no_bracket_statement_regex("^ *(if|for|while) *\\(.*[^;}{] *$|" + "^[}]? *else if *\\(.*[^;}{] *$|" + "^[}]? *else *$", std::regex::extended); - if (get_buffer()->get_has_selection()) - return false; + auto iter = get_buffer()->get_insert()->get_iter(); - if (!is_code_iter(iter)) { - // Add * at start of line in comment blocks - if (key->keyval == GDK_KEY_Return || key->keyval == GDK_KEY_KP_Enter) { - if (!iter.starts_line() && (!string_tag || (!iter.has_tag(string_tag) && !iter.ends_tag(string_tag)))) { - cleanup_whitespace_characters_on_return(iter); - iter = get_buffer()->get_insert()->get_iter(); - - auto start_iter = get_tabs_end_iter(iter.get_line()); - auto end_iter = start_iter; - end_iter.forward_chars(2); - auto start_of_sentence = get_buffer()->get_text(start_iter, end_iter); - if (!start_of_sentence.empty()) { - if (start_of_sentence == "/*" || start_of_sentence[0] == '*') { - auto tabs = get_line_before(start_iter); - auto insert_str = "\n" + tabs; - if (start_of_sentence[0] == '/') - insert_str += ' '; - insert_str += "* "; - - get_buffer()->insert_at_cursor(insert_str); - return true; - } - } - } else if (!comment_tag || !iter.ends_tag(comment_tag)) - return false; - } else - return false; - } - - // get iter for if expressions below, which is moved backwards past any comment - auto get_condition_iter = [this](const Gtk::TextIter &iter) { - auto condition_iter = iter; - condition_iter.backward_char(); - if (!comment_tag) - return condition_iter; - while (!condition_iter.starts_line() && (condition_iter.has_tag(comment_tag) || - #if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION >= 20) - condition_iter.starts_tag(comment_tag) || - #else - *condition_iter=='/' || - #endif - *condition_iter == ' ' || *condition_iter == '\t') && - condition_iter.backward_char()) {} - return condition_iter; - }; + if (get_buffer()->get_has_selection()) + return false; - //Indent depending on if/else/etc and brackets - if ((key->keyval == GDK_KEY_Return || key->keyval == GDK_KEY_KP_Enter) && !iter.starts_line()) { + if (!is_code_iter(iter)) { + // Add * at start of line in comment blocks + if (key->keyval == GDK_KEY_Return || key->keyval == GDK_KEY_KP_Enter) { + if (!iter.starts_line() && (!string_tag || (!iter.has_tag(string_tag) && !iter.ends_tag(string_tag)))) { cleanup_whitespace_characters_on_return(iter); iter = get_buffer()->get_insert()->get_iter(); - auto condition_iter = get_condition_iter(iter); - auto start_iter = condition_iter; - if (*start_iter == '{') - start_iter.backward_char(); - Gtk::TextIter open_non_curly_bracket_iter; - bool open_non_curly_bracket_iter_found = false; - if (find_open_non_curly_bracket_backward(start_iter, open_non_curly_bracket_iter)) { - open_non_curly_bracket_iter_found = true; - start_iter = get_tabs_end_iter(get_buffer()->get_iter_at_line(open_non_curly_bracket_iter.get_line())); - } else - start_iter = get_tabs_end_iter( - get_buffer()->get_iter_at_line(find_start_of_sentence(condition_iter).get_line())); - auto tabs = get_line_before(start_iter); - - /* - * Change tabs after ending comment block with an extra space (as in this case) - */ - if (tabs.size() % tab_size == 1 && !start_iter.ends_line() && !is_code_iter(start_iter)) { - auto end_of_line_iter = start_iter; - end_of_line_iter.forward_to_line_end(); - auto line = get_buffer()->get_text(start_iter, end_of_line_iter); - if (!line.empty() && line.compare(0, 2, "*/") == 0) { - tabs.pop_back(); - get_buffer()->insert_at_cursor("\n" + tabs); - scroll_to(get_buffer()->get_insert()); - return true; - } - } - - if (*condition_iter == '{' && is_code_iter(condition_iter)) { - Gtk::TextIter found_iter; - // Check if an '}' is needed - bool has_right_curly_bracket = false; - bool found_right_bracket = find_close_curly_bracket_forward(iter, found_iter); - if (found_right_bracket) { - auto tabs_end_iter = get_tabs_end_iter(found_iter); - auto line_tabs = get_line_before(tabs_end_iter); - if (tabs.size() == line_tabs.size()) - has_right_curly_bracket = true; - } - // special case for functions and classes with no indentation after: namespace { - if (tabs.empty() && has_right_curly_bracket) - has_right_curly_bracket = symbol_count(iter, '{', '}') != 1; - - if (*get_buffer()->get_insert()->get_iter() == '}') { - get_buffer()->insert_at_cursor("\n" + tabs + tab + "\n" + tabs); - auto insert_it = get_buffer()->get_insert()->get_iter(); - if (insert_it.backward_chars(tabs.size() + 1)) { - scroll_to(get_buffer()->get_insert()); - get_buffer()->place_cursor(insert_it); - } - return true; - } else if (!has_right_curly_bracket) { - //Insert new lines with bracket end - bool add_semicolon = false; - if (language && (language->get_id() == "chdr" || language->get_id() == "cpphdr" || - language->get_id() == "c" || language->get_id() == "cpp")) { - auto token = get_token(start_iter); - if (token.empty()) { - auto iter = start_iter; - while (!iter.starts_line() && iter.backward_char()) {} - if (iter.backward_char()) - token = get_token(get_tabs_end_iter(get_buffer()->get_iter_at_line(iter.get_line()))); - } - //Add semicolon after class or struct - if (token == "class" || token == "struct") - add_semicolon = true; - //Add semicolon after lambda unless it's a parameter - else if (!open_non_curly_bracket_iter_found) { - auto it = condition_iter; - long para_count = 0; - long square_count = 0; - bool square_outside_para_found = false; - while (it.backward_char()) { - if (*it == ']' && is_code_iter(it)) { - --square_count; - if (para_count == 0) - square_outside_para_found = true; - } else if (*it == '[' && is_code_iter(it)) - ++square_count; - else if (*it == ')' && is_code_iter(it)) - --para_count; - else if (*it == '(' && is_code_iter(it)) - ++para_count; - - if (square_outside_para_found && square_count == 0 && para_count == 0) { - add_semicolon = true; - break; - } - if (it == start_iter) - break; - if (!square_outside_para_found && square_count == 0 && para_count == 0) { - if ((*it >= 'A' && *it <= 'Z') || (*it >= 'a' && *it <= 'z') || - (*it >= '0' && *it <= '9') || *it == '_' || - *it == '-' || *it == ' ' || *it == '\t' || *it == '<' || *it == '>' || *it == '(' || - *it == ':' || - *it == '*' || *it == '&' || *it == '/' || it.ends_line() || !is_code_iter(it)) { - continue; - } else - break; - } - } - } - } - get_buffer()->insert_at_cursor("\n" + tabs + tab + "\n" + tabs + (add_semicolon ? "};" : "}")); - auto insert_it = get_buffer()->get_insert()->get_iter(); - if (insert_it.backward_chars(tabs.size() + (add_semicolon ? 3 : 2))) { - scroll_to(get_buffer()->get_insert()); - get_buffer()->place_cursor(insert_it); - } - return true; - } else { - get_buffer()->insert_at_cursor("\n" + tabs + tab); - scroll_to(get_buffer()->get_insert()); - return true; - } - } - - //Indent multiline expressions - if (open_non_curly_bracket_iter_found) { - auto tabs_end_iter = get_tabs_end_iter(open_non_curly_bracket_iter); - auto tabs = get_line_before(get_tabs_end_iter(open_non_curly_bracket_iter)); - auto iter = tabs_end_iter; - while (iter <= open_non_curly_bracket_iter) { - tabs += ' '; - iter.forward_char(); - } - get_buffer()->insert_at_cursor("\n" + tabs); - scroll_to(get_buffer()->get_insert()); + auto start_iter = get_tabs_end_iter(iter.get_line()); + auto end_iter = start_iter; + end_iter.forward_chars(2); + auto start_of_sentence = get_buffer()->get_text(start_iter, end_iter); + if (!start_of_sentence.empty()) { + if (start_of_sentence == "/*" || start_of_sentence[0] == '*') { + auto tabs = get_line_before(start_iter); + auto insert_str = "\n" + tabs; + if (start_of_sentence[0] == '/') + insert_str += ' '; + insert_str += "* "; + + get_buffer()->insert_at_cursor(insert_str); return true; + } } - auto after_condition_iter = condition_iter; - after_condition_iter.forward_char(); - std::string sentence = get_buffer()->get_text(start_iter, after_condition_iter); - std::smatch sm; - if (std::regex_match(sentence, sm, no_bracket_statement_regex)) { - get_buffer()->insert_at_cursor("\n" + tabs + tab); - scroll_to(get_buffer()->get_insert()); - return true; + } else if (!comment_tag || !iter.ends_tag(comment_tag)) + return false; + } else + return false; + } + + // get iter for if expressions below, which is moved backwards past any comment + auto get_condition_iter = [this](const Gtk::TextIter &iter) { + auto condition_iter = iter; + condition_iter.backward_char(); + if (!comment_tag) + return condition_iter; + while (!condition_iter.starts_line() && (condition_iter.has_tag(comment_tag) || + #if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION >= 20) + condition_iter.starts_tag(comment_tag) || + #else + *condition_iter=='/' || + #endif + *condition_iter == ' ' || *condition_iter == '\t') && + condition_iter.backward_char()) {} + return condition_iter; + }; + + //Indent depending on if/else/etc and brackets + if ((key->keyval == GDK_KEY_Return || key->keyval == GDK_KEY_KP_Enter) && !iter.starts_line()) { + cleanup_whitespace_characters_on_return(iter); + iter = get_buffer()->get_insert()->get_iter(); + + auto condition_iter = get_condition_iter(iter); + auto start_iter = condition_iter; + if (*start_iter == '{') + start_iter.backward_char(); + Gtk::TextIter open_non_curly_bracket_iter; + bool open_non_curly_bracket_iter_found = false; + if (find_open_non_curly_bracket_backward(start_iter, open_non_curly_bracket_iter)) { + open_non_curly_bracket_iter_found = true; + start_iter = get_tabs_end_iter(get_buffer()->get_iter_at_line(open_non_curly_bracket_iter.get_line())); + } else + start_iter = get_tabs_end_iter( + get_buffer()->get_iter_at_line(find_start_of_sentence(condition_iter).get_line())); + auto tabs = get_line_before(start_iter); + + /* + * Change tabs after ending comment block with an extra space (as in this case) + */ + if (tabs.size() % tab_size == 1 && !start_iter.ends_line() && !is_code_iter(start_iter)) { + auto end_of_line_iter = start_iter; + end_of_line_iter.forward_to_line_end(); + auto line = get_buffer()->get_text(start_iter, end_of_line_iter); + if (!line.empty() && line.compare(0, 2, "*/") == 0) { + tabs.pop_back(); + get_buffer()->insert_at_cursor("\n" + tabs); + scroll_to(get_buffer()->get_insert()); + return true; + } + } + + if (*condition_iter == '{' && is_code_iter(condition_iter)) { + Gtk::TextIter found_iter; + // Check if an '}' is needed + bool has_right_curly_bracket = false; + bool found_right_bracket = find_close_curly_bracket_forward(iter, found_iter); + if (found_right_bracket) { + auto tabs_end_iter = get_tabs_end_iter(found_iter); + auto line_tabs = get_line_before(tabs_end_iter); + if (tabs.size() == line_tabs.size()) + has_right_curly_bracket = true; + } + // special case for functions and classes with no indentation after: namespace { + if (tabs.empty() && has_right_curly_bracket) + has_right_curly_bracket = symbol_count(iter, '{', '}') != 1; + + if (*get_buffer()->get_insert()->get_iter() == '}') { + get_buffer()->insert_at_cursor("\n" + tabs + tab + "\n" + tabs); + auto insert_it = get_buffer()->get_insert()->get_iter(); + if (insert_it.backward_chars(tabs.size() + 1)) { + scroll_to(get_buffer()->get_insert()); + get_buffer()->place_cursor(insert_it); } - //Indenting after for instance if(...)\n...;\n - else if (*condition_iter == ';' && condition_iter.get_line() > 0 && is_code_iter(condition_iter)) { - auto previous_end_iter = start_iter; - while (previous_end_iter.backward_char() && !previous_end_iter.ends_line()) {} - auto condition_iter = get_condition_iter(previous_end_iter); - auto previous_start_iter = get_tabs_end_iter( - get_buffer()->get_iter_at_line(find_start_of_sentence(condition_iter).get_line())); - auto previous_tabs = get_line_before(previous_start_iter); - auto after_condition_iter = condition_iter; - after_condition_iter.forward_char(); - std::string previous_sentence = get_buffer()->get_text(previous_start_iter, after_condition_iter); - std::smatch sm2; - if (std::regex_match(previous_sentence, sm2, no_bracket_statement_regex)) { - get_buffer()->insert_at_cursor("\n" + previous_tabs); - scroll_to(get_buffer()->get_insert()); - return true; + return true; + } else if (!has_right_curly_bracket) { + //Insert new lines with bracket end + bool add_semicolon = false; + if (language && (language->get_id() == "chdr" || language->get_id() == "cpphdr" || + language->get_id() == "c" || language->get_id() == "cpp")) { + auto token = get_token(start_iter); + if (token.empty()) { + auto iter = start_iter; + while (!iter.starts_line() && iter.backward_char()) {} + if (iter.backward_char()) + token = get_token(get_tabs_end_iter(get_buffer()->get_iter_at_line(iter.get_line()))); + } + //Add semicolon after class or struct + if (token == "class" || token == "struct") + add_semicolon = true; + //Add semicolon after lambda unless it's a parameter + else if (!open_non_curly_bracket_iter_found) { + auto it = condition_iter; + long para_count = 0; + long square_count = 0; + bool square_outside_para_found = false; + while (it.backward_char()) { + if (*it == ']' && is_code_iter(it)) { + --square_count; + if (para_count == 0) + square_outside_para_found = true; + } else if (*it == '[' && is_code_iter(it)) + ++square_count; + else if (*it == ')' && is_code_iter(it)) + --para_count; + else if (*it == '(' && is_code_iter(it)) + ++para_count; + + if (square_outside_para_found && square_count == 0 && para_count == 0) { + add_semicolon = true; + break; + } + if (it == start_iter) + break; + if (!square_outside_para_found && square_count == 0 && para_count == 0) { + if ((*it >= 'A' && *it <= 'Z') || (*it >= 'a' && *it <= 'z') || + (*it >= '0' && *it <= '9') || *it == '_' || + *it == '-' || *it == ' ' || *it == '\t' || *it == '<' || *it == '>' || *it == '(' || + *it == ':' || + *it == '*' || *it == '&' || *it == '/' || it.ends_line() || !is_code_iter(it)) { + continue; + } else + break; + } } + } } - //Indenting after ':' - else if (*condition_iter == ':' && is_code_iter(condition_iter)) { - bool perform_indent = true; - auto iter = condition_iter; - while (!iter.starts_line() && *iter == ' ' && iter.backward_char()) {} - if (*iter == ')') { - auto token = get_token(get_tabs_end_iter(get_buffer()->get_iter_at_line(iter.get_line()))); - if (token != "case") - perform_indent = false; - } - if (perform_indent) { - Gtk::TextIter found_curly_iter; - if (find_open_curly_bracket_backward(iter, found_curly_iter)) { - auto tabs_end_iter = get_tabs_end_iter(get_buffer()->get_iter_at_line(found_curly_iter.get_line())); - auto tabs_start_of_sentence = get_line_before(tabs_end_iter); - if (tabs.size() == (tabs_start_of_sentence.size() + tab_size)) { - auto start_line_iter = get_buffer()->get_iter_at_line(iter.get_line()); - auto start_line_plus_tab_size = start_line_iter; - for (size_t c = 0; c < tab_size; c++) - start_line_plus_tab_size.forward_char(); - get_buffer()->erase(start_line_iter, start_line_plus_tab_size); - } else { - get_buffer()->insert_at_cursor("\n" + tabs + tab); - scroll_to(get_buffer()->get_insert()); - return true; - } - } - } + get_buffer()->insert_at_cursor("\n" + tabs + tab + "\n" + tabs + (add_semicolon ? "};" : "}")); + auto insert_it = get_buffer()->get_insert()->get_iter(); + if (insert_it.backward_chars(tabs.size() + (add_semicolon ? 3 : 2))) { + scroll_to(get_buffer()->get_insert()); + get_buffer()->place_cursor(insert_it); } - get_buffer()->insert_at_cursor("\n" + tabs); + return true; + } else { + get_buffer()->insert_at_cursor("\n" + tabs + tab); scroll_to(get_buffer()->get_insert()); return true; + } } - //Indent left when writing } on a new line - else if (key->keyval == GDK_KEY_braceright) { - std::string line = get_line_before(); - if (line.size() >= tab_size && iter.ends_line()) { - bool indent_left = true; - for (auto c: line) { - if (c != tab_char) { - indent_left = false; - break; - } - } - if (indent_left) { - Gtk::TextIter insert_it = get_buffer()->get_insert()->get_iter(); - Gtk::TextIter line_it = get_buffer()->get_iter_at_line(insert_it.get_line()); - Gtk::TextIter line_plus_it = line_it; - line_plus_it.forward_chars(tab_size); - get_buffer()->erase(line_it, line_plus_it); - get_buffer()->insert_at_cursor("}"); - return true; - } + + //Indent multiline expressions + if (open_non_curly_bracket_iter_found) { + auto tabs_end_iter = get_tabs_end_iter(open_non_curly_bracket_iter); + auto tabs = get_line_before(get_tabs_end_iter(open_non_curly_bracket_iter)); + auto iter = tabs_end_iter; + while (iter <= open_non_curly_bracket_iter) { + tabs += ' '; + iter.forward_char(); + } + get_buffer()->insert_at_cursor("\n" + tabs); + scroll_to(get_buffer()->get_insert()); + return true; + } + auto after_condition_iter = condition_iter; + after_condition_iter.forward_char(); + std::string sentence = get_buffer()->get_text(start_iter, after_condition_iter); + std::smatch sm; + if (std::regex_match(sentence, sm, no_bracket_statement_regex)) { + get_buffer()->insert_at_cursor("\n" + tabs + tab); + scroll_to(get_buffer()->get_insert()); + return true; + } + //Indenting after for instance if(...)\n...;\n + else if (*condition_iter == ';' && condition_iter.get_line() > 0 && is_code_iter(condition_iter)) { + auto previous_end_iter = start_iter; + while (previous_end_iter.backward_char() && !previous_end_iter.ends_line()) {} + auto condition_iter = get_condition_iter(previous_end_iter); + auto previous_start_iter = get_tabs_end_iter( + get_buffer()->get_iter_at_line(find_start_of_sentence(condition_iter).get_line())); + auto previous_tabs = get_line_before(previous_start_iter); + auto after_condition_iter = condition_iter; + after_condition_iter.forward_char(); + std::string previous_sentence = get_buffer()->get_text(previous_start_iter, after_condition_iter); + std::smatch sm2; + if (std::regex_match(previous_sentence, sm2, no_bracket_statement_regex)) { + get_buffer()->insert_at_cursor("\n" + previous_tabs); + scroll_to(get_buffer()->get_insert()); + return true; + } + } + //Indenting after ':' + else if (*condition_iter == ':' && is_code_iter(condition_iter)) { + bool perform_indent = true; + auto iter = condition_iter; + while (!iter.starts_line() && *iter == ' ' && iter.backward_char()) {} + if (*iter == ')') { + auto token = get_token(get_tabs_end_iter(get_buffer()->get_iter_at_line(iter.get_line()))); + if (token != "case") + perform_indent = false; + } + if (perform_indent) { + Gtk::TextIter found_curly_iter; + if (find_open_curly_bracket_backward(iter, found_curly_iter)) { + auto tabs_end_iter = get_tabs_end_iter(get_buffer()->get_iter_at_line(found_curly_iter.get_line())); + auto tabs_start_of_sentence = get_line_before(tabs_end_iter); + if (tabs.size() == (tabs_start_of_sentence.size() + tab_size)) { + auto start_line_iter = get_buffer()->get_iter_at_line(iter.get_line()); + auto start_line_plus_tab_size = start_line_iter; + for (size_t c = 0; c < tab_size; c++) + start_line_plus_tab_size.forward_char(); + get_buffer()->erase(start_line_iter, start_line_plus_tab_size); + } else { + get_buffer()->insert_at_cursor("\n" + tabs + tab); + scroll_to(get_buffer()->get_insert()); + return true; + } } + } } - //Indent left when writing { on a new line after for instance if(...)\n... - else if (key->keyval == GDK_KEY_braceleft) { - auto tabs_end_iter = get_tabs_end_iter(); - auto tabs = get_line_before(tabs_end_iter); - size_t line_nr = iter.get_line(); - if (line_nr > 0 && tabs.size() >= tab_size && iter == tabs_end_iter) { - auto previous_end_iter = iter; - while (previous_end_iter.backward_char() && !previous_end_iter.ends_line()) {} - auto condition_iter = get_condition_iter(previous_end_iter); - auto previous_start_iter = get_tabs_end_iter( - get_buffer()->get_iter_at_line(find_start_of_sentence(condition_iter).get_line())); - auto previous_tabs = get_line_before(previous_start_iter); - auto after_condition_iter = condition_iter; - after_condition_iter.forward_char(); - if ((tabs.size() - tab_size) == previous_tabs.size()) { - std::string previous_sentence = get_buffer()->get_text(previous_start_iter, after_condition_iter); - std::smatch sm; - if (std::regex_match(previous_sentence, sm, no_bracket_statement_regex)) { - auto start_iter = iter; - start_iter.backward_chars(tab_size); - get_buffer()->erase(start_iter, iter); - get_buffer()->insert_at_cursor("{"); - scroll_to(get_buffer()->get_insert()); - return true; - } - } - } + get_buffer()->insert_at_cursor("\n" + tabs); + scroll_to(get_buffer()->get_insert()); + return true; + } + //Indent left when writing } on a new line + else if (key->keyval == GDK_KEY_braceright) { + std::string line = get_line_before(); + if (line.size() >= tab_size && iter.ends_line()) { + bool indent_left = true; + for (auto c: line) { + if (c != tab_char) { + indent_left = false; + break; + } + } + if (indent_left) { + Gtk::TextIter insert_it = get_buffer()->get_insert()->get_iter(); + Gtk::TextIter line_it = get_buffer()->get_iter_at_line(insert_it.get_line()); + Gtk::TextIter line_plus_it = line_it; + line_plus_it.forward_chars(tab_size); + get_buffer()->erase(line_it, line_plus_it); + get_buffer()->insert_at_cursor("}"); + return true; + } } - // Mark parameters of templated functions after pressing tab and after writing template argument - else if (key->keyval == GDK_KEY_Tab && (key->state & GDK_SHIFT_MASK) == 0) { - if (*iter == '>') { - iter.forward_char(); - Gtk::TextIter parenthesis_end_iter; - if (*iter == '(' && is_templated_function(iter, parenthesis_end_iter)) { - iter.forward_char(); - get_buffer()->select_range(iter, parenthesis_end_iter); - scroll_to(get_buffer()->get_insert()); - return true; - } + } + //Indent left when writing { on a new line after for instance if(...)\n... + else if (key->keyval == GDK_KEY_braceleft) { + auto tabs_end_iter = get_tabs_end_iter(); + auto tabs = get_line_before(tabs_end_iter); + size_t line_nr = iter.get_line(); + if (line_nr > 0 && tabs.size() >= tab_size && iter == tabs_end_iter) { + auto previous_end_iter = iter; + while (previous_end_iter.backward_char() && !previous_end_iter.ends_line()) {} + auto condition_iter = get_condition_iter(previous_end_iter); + auto previous_start_iter = get_tabs_end_iter( + get_buffer()->get_iter_at_line(find_start_of_sentence(condition_iter).get_line())); + auto previous_tabs = get_line_before(previous_start_iter); + auto after_condition_iter = condition_iter; + after_condition_iter.forward_char(); + if ((tabs.size() - tab_size) == previous_tabs.size()) { + std::string previous_sentence = get_buffer()->get_text(previous_start_iter, after_condition_iter); + std::smatch sm; + if (std::regex_match(previous_sentence, sm, no_bracket_statement_regex)) { + auto start_iter = iter; + start_iter.backward_chars(tab_size); + get_buffer()->erase(start_iter, iter); + get_buffer()->insert_at_cursor("{"); + scroll_to(get_buffer()->get_insert()); + return true; } + } } + } + // Mark parameters of templated functions after pressing tab and after writing template argument + else if (key->keyval == GDK_KEY_Tab && (key->state & GDK_SHIFT_MASK) == 0) { + if (*iter == '>') { + iter.forward_char(); + Gtk::TextIter parenthesis_end_iter; + if (*iter == '(' && is_templated_function(iter, parenthesis_end_iter)) { + iter.forward_char(); + get_buffer()->select_range(iter, parenthesis_end_iter); + scroll_to(get_buffer()->get_insert()); + return true; + } + } + } - return false; + return false; } bool Source::View::on_key_press_event_smart_brackets(GdkEventKey *key) { - if (get_buffer()->get_has_selection()) - return false; + if (get_buffer()->get_has_selection()) + return false; - auto iter = get_buffer()->get_insert()->get_iter(); - auto previous_iter = iter; - previous_iter.backward_char(); - if (is_code_iter(iter)) { - //Move after ')' if closed expression - if (key->keyval == GDK_KEY_parenright) { - if (*iter == ')' && symbol_count(iter, '(', ')') == 0) { - iter.forward_char(); - get_buffer()->place_cursor(iter); - scroll_to(get_buffer()->get_insert()); - return true; - } - } - //Move after '>' if >( and closed expression - else if (key->keyval == GDK_KEY_greater) { - if (*iter == '>') { - iter.forward_char(); - Gtk::TextIter parenthesis_end_iter; - if (*iter == '(' && is_templated_function(iter, parenthesis_end_iter)) { - get_buffer()->place_cursor(iter); - scroll_to(get_buffer()->get_insert()); - return true; - } - } - } - //Move after '(' if >( and select text inside parentheses - else if (key->keyval == GDK_KEY_parenleft) { - auto previous_iter = iter; - previous_iter.backward_char(); - if (*previous_iter == '>') { - Gtk::TextIter parenthesis_end_iter; - if (*iter == '(' && is_templated_function(iter, parenthesis_end_iter)) { - iter.forward_char(); - get_buffer()->select_range(iter, parenthesis_end_iter); - scroll_to(iter); - return true; - } - } - } + auto iter = get_buffer()->get_insert()->get_iter(); + auto previous_iter = iter; + previous_iter.backward_char(); + if (is_code_iter(iter)) { + //Move after ')' if closed expression + if (key->keyval == GDK_KEY_parenright) { + if (*iter == ')' && symbol_count(iter, '(', ')') == 0) { + iter.forward_char(); + get_buffer()->place_cursor(iter); + scroll_to(get_buffer()->get_insert()); + return true; + } } + //Move after '>' if >( and closed expression + else if (key->keyval == GDK_KEY_greater) { + if (*iter == '>') { + iter.forward_char(); + Gtk::TextIter parenthesis_end_iter; + if (*iter == '(' && is_templated_function(iter, parenthesis_end_iter)) { + get_buffer()->place_cursor(iter); + scroll_to(get_buffer()->get_insert()); + return true; + } + } + } + //Move after '(' if >( and select text inside parentheses + else if (key->keyval == GDK_KEY_parenleft) { + auto previous_iter = iter; + previous_iter.backward_char(); + if (*previous_iter == '>') { + Gtk::TextIter parenthesis_end_iter; + if (*iter == '(' && is_templated_function(iter, parenthesis_end_iter)) { + iter.forward_char(); + get_buffer()->select_range(iter, parenthesis_end_iter); + scroll_to(iter); + return true; + } + } + } + } - return false; + return false; } bool Source::View::on_key_press_event_smart_inserts(GdkEventKey *key) { - if (get_buffer()->get_has_selection()) { - bool perform_insertion = false; - char left_char, right_char; - // Insert () around selection - if (key->keyval == GDK_KEY_parenleft) { - perform_insertion = true; - left_char = '('; - right_char = ')'; - } - // Insert [] around selection - else if (key->keyval == GDK_KEY_bracketleft) { - perform_insertion = true; - left_char = '['; - right_char = ']'; - } - // Insert {} around selection - else if (key->keyval == GDK_KEY_braceleft) { - perform_insertion = true; - left_char = '{'; - right_char = '}'; - } - // Insert <> around selection - else if (key->keyval == GDK_KEY_less) { - perform_insertion = true; - left_char = '<'; - right_char = '>'; - } - // Insert '' around selection - else if (key->keyval == GDK_KEY_apostrophe) { - perform_insertion = true; - left_char = '\''; - right_char = '\''; - } - // Insert "" around selection - else if (key->keyval == GDK_KEY_quotedbl) { - perform_insertion = true; - left_char = '"'; - right_char = '"'; - } else if (language && language->get_id() == "markdown") { - if (key->keyval == GDK_KEY_dead_grave) { - perform_insertion = true; - left_char = '`'; - right_char = '`'; - } - if (key->keyval == GDK_KEY_asterisk) { - perform_insertion = true; - left_char = '*'; - right_char = '*'; - } - if (key->keyval == GDK_KEY_underscore) { - perform_insertion = true; - left_char = '_'; - right_char = '_'; - } - if (key->keyval == GDK_KEY_dead_tilde) { - perform_insertion = true; - left_char = '~'; - right_char = '~'; - } - } - if (perform_insertion) { - Gtk::TextIter start, end; - get_buffer()->get_selection_bounds(start, end); - auto start_mark = get_buffer()->create_mark(start); - auto end_mark = get_buffer()->create_mark(end); - get_buffer()->insert(start, Glib::ustring() + left_char); - get_buffer()->insert(end_mark->get_iter(), Glib::ustring() + right_char); - auto start_mark_next_iter = start_mark->get_iter(); - start_mark_next_iter.forward_char(); - get_buffer()->select_range(start_mark_next_iter, end_mark->get_iter()); - get_buffer()->delete_mark(start_mark); - get_buffer()->delete_mark(end_mark); - return true; - } - return false; - } - - auto iter = get_buffer()->get_insert()->get_iter(); - auto previous_iter = iter; - previous_iter.backward_char(); - auto next_iter = iter; - next_iter.forward_char(); - - auto allow_insertion = [](const Gtk::TextIter &iter) { - if (iter.ends_line() || *iter == ' ' || *iter == '\t' || *iter == ';' || *iter == ')' || *iter == ']' || - *iter == '[' || *iter == '{' || *iter == '}') - return true; - return false; - }; - - // Move right when clicking ' before a ' or when clicking " before a " - if (((key->keyval == GDK_KEY_apostrophe && *iter == '\'') || - (key->keyval == GDK_KEY_quotedbl && *iter == '\"')) && is_code_iter(next_iter)) { - bool perform_move = false; - if (*previous_iter != '\\') - perform_move = true; - else { - auto it = previous_iter; - long backslash_count = 1; - while (it.backward_char() && *it == '\\') { - ++backslash_count; - } - if (backslash_count % 2 == 0) - perform_move = true; - } - if (perform_move) { - get_buffer()->place_cursor(next_iter); - scroll_to(get_buffer()->get_insert()); - return true; - } - } - // When to delete '' or "" - else if (key->keyval == GDK_KEY_BackSpace) { - if (((*previous_iter == '\'' && *iter == '\'') || - (*previous_iter == '"' && *iter == '"')) && is_code_iter(previous_iter)) { - get_buffer()->erase(previous_iter, next_iter); - scroll_to(get_buffer()->get_insert()); - return true; - } - } - - if (is_code_iter(iter)) { - // Insert () - if (key->keyval == GDK_KEY_parenleft && allow_insertion(iter)) { - if (symbol_count(iter, '(', ')') == 0) { - get_buffer()->insert_at_cursor(")"); - iter = get_buffer()->get_insert()->get_iter(); - iter.backward_char(); - get_buffer()->place_cursor(iter); - get_buffer()->insert_at_cursor("("); - scroll_to(get_buffer()->get_insert()); - return true; - } - } - // Insert [] - else if (key->keyval == GDK_KEY_bracketleft && allow_insertion(iter)) { - if (symbol_count(iter, '[', ']') == 0) { - get_buffer()->insert_at_cursor("[]"); - auto iter = get_buffer()->get_insert()->get_iter(); - iter.backward_char(); - get_buffer()->place_cursor(iter); - scroll_to(get_buffer()->get_insert()); - return true; - } - } - // Move left on ] in [] - else if (key->keyval == GDK_KEY_bracketright) { - if (*iter == ']' && symbol_count(iter, '[', ']') == 0) { - iter.forward_char(); - get_buffer()->place_cursor(iter); - scroll_to(get_buffer()->get_insert()); - return true; - } - } - // Insert '' - else if (key->keyval == GDK_KEY_apostrophe && allow_insertion(iter) && symbol_count(iter, '\'', -1) % 2 == 0) { - get_buffer()->insert_at_cursor("''"); - auto iter = get_buffer()->get_insert()->get_iter(); - iter.backward_char(); - get_buffer()->place_cursor(iter); - scroll_to(get_buffer()->get_insert()); - return true; - } - // Insert "" - else if (key->keyval == GDK_KEY_quotedbl && allow_insertion(iter) && symbol_count(iter, '"', -1) % 2 == 0) { - get_buffer()->insert_at_cursor("\"\""); - auto iter = get_buffer()->get_insert()->get_iter(); - iter.backward_char(); - get_buffer()->place_cursor(iter); - scroll_to(get_buffer()->get_insert()); - return true; - } - // Insert ; at the end of line, if iter is at the last ) - else if (key->keyval == GDK_KEY_semicolon) { - if (*iter == ')' && symbol_count(iter, '(', ')') == 0) { - if (next_iter.ends_line()) { - Gtk::TextIter open_non_curly_bracket_iter; - if (find_open_non_curly_bracket_backward(previous_iter, open_non_curly_bracket_iter)) { - open_non_curly_bracket_iter.backward_char(); - if (*open_non_curly_bracket_iter == ' ') - open_non_curly_bracket_iter.backward_char(); - if (get_token(open_non_curly_bracket_iter) != "for") { - iter.forward_char(); - get_buffer()->place_cursor(iter); - get_buffer()->insert_at_cursor(";"); - scroll_to(get_buffer()->get_insert()); - return true; - } - } - } - } - } - // Delete () - else if (key->keyval == GDK_KEY_BackSpace) { - if (*previous_iter == '(' && *iter == ')' && symbol_count(iter, '(', ')') == 0) { - auto next_iter = iter; - next_iter.forward_char(); - get_buffer()->erase(previous_iter, next_iter); - scroll_to(get_buffer()->get_insert()); - return true; - } - // Delete [] - else if (*previous_iter == '[' && *iter == ']' && symbol_count(iter, '[', ']') == 0) { - auto next_iter = iter; - next_iter.forward_char(); - get_buffer()->erase(previous_iter, next_iter); - scroll_to(get_buffer()->get_insert()); - return true; - } - } + if (get_buffer()->get_has_selection()) { + bool perform_insertion = false; + char left_char, right_char; + // Insert () around selection + if (key->keyval == GDK_KEY_parenleft) { + perform_insertion = true; + left_char = '('; + right_char = ')'; + } + // Insert [] around selection + else if (key->keyval == GDK_KEY_bracketleft) { + perform_insertion = true; + left_char = '['; + right_char = ']'; + } + // Insert {} around selection + else if (key->keyval == GDK_KEY_braceleft) { + perform_insertion = true; + left_char = '{'; + right_char = '}'; + } + // Insert <> around selection + else if (key->keyval == GDK_KEY_less) { + perform_insertion = true; + left_char = '<'; + right_char = '>'; + } + // Insert '' around selection + else if (key->keyval == GDK_KEY_apostrophe) { + perform_insertion = true; + left_char = '\''; + right_char = '\''; + } + // Insert "" around selection + else if (key->keyval == GDK_KEY_quotedbl) { + perform_insertion = true; + left_char = '"'; + right_char = '"'; + } else if (language && language->get_id() == "markdown") { + if (key->keyval == GDK_KEY_dead_grave) { + perform_insertion = true; + left_char = '`'; + right_char = '`'; + } + if (key->keyval == GDK_KEY_asterisk) { + perform_insertion = true; + left_char = '*'; + right_char = '*'; + } + if (key->keyval == GDK_KEY_underscore) { + perform_insertion = true; + left_char = '_'; + right_char = '_'; + } + if (key->keyval == GDK_KEY_dead_tilde) { + perform_insertion = true; + left_char = '~'; + right_char = '~'; + } + } + if (perform_insertion) { + Gtk::TextIter start, end; + get_buffer()->get_selection_bounds(start, end); + auto start_mark = get_buffer()->create_mark(start); + auto end_mark = get_buffer()->create_mark(end); + get_buffer()->insert(start, Glib::ustring() + left_char); + get_buffer()->insert(end_mark->get_iter(), Glib::ustring() + right_char); + auto start_mark_next_iter = start_mark->get_iter(); + start_mark_next_iter.forward_char(); + get_buffer()->select_range(start_mark_next_iter, end_mark->get_iter()); + get_buffer()->delete_mark(start_mark); + get_buffer()->delete_mark(end_mark); + return true; } - return false; -} + } -bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) { - if (!multiple_cursors_signals_set) { - multiple_cursors_signals_set = true; - multiple_cursors_last_insert = get_buffer()->create_mark(get_buffer()->get_insert()->get_iter(), false); - get_buffer()->signal_mark_set().connect( - [this](const Gtk::TextBuffer::iterator &iter, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { - for (auto &extra_cursor: multiple_cursors_extra_cursors) { - if (extra_cursor.first == mark && - (!iter.ends_line() || iter.get_line_offset() > extra_cursor.second)) { - extra_cursor.second = iter.get_line_offset(); - break; - } - } + auto iter = get_buffer()->get_insert()->get_iter(); + auto previous_iter = iter; + previous_iter.backward_char(); + auto next_iter = iter; + next_iter.forward_char(); - if (mark->get_name() == "insert") { - if (multiple_cursors_use) { - multiple_cursors_use = false; - auto offset_diff = mark->get_iter().get_offset() - - multiple_cursors_last_insert->get_iter().get_offset(); - if (offset_diff != 0) { - for (auto &extra_cursor: multiple_cursors_extra_cursors) { - auto iter = extra_cursor.first->get_iter(); - iter.forward_chars(offset_diff); - get_buffer()->move_mark(extra_cursor.first, iter); - } - } - multiple_cursors_use = true; - } - get_buffer()->delete_mark(multiple_cursors_last_insert); - multiple_cursors_last_insert = get_buffer()->create_mark(mark->get_iter(), false); - } - }); - - // TODO: this handler should run after signal_insert - get_buffer()->signal_insert().connect( - [this](const Gtk::TextBuffer::iterator &iter, const Glib::ustring &text, int bytes) { - if (multiple_cursors_use) { - multiple_cursors_use = false; - auto offset = iter.get_offset() - get_buffer()->get_insert()->get_iter().get_offset(); - for (auto &extra_cursor: multiple_cursors_extra_cursors) { - auto iter = extra_cursor.first->get_iter(); - iter.forward_chars(offset); - get_buffer()->insert(iter, text); - } - multiple_cursors_use = true; - } - }); - - get_buffer()->signal_erase().connect( - [this](const Gtk::TextBuffer::iterator &iter_start, const Gtk::TextBuffer::iterator &iter_end) { - if (multiple_cursors_use) { - auto insert_offset = get_buffer()->get_insert()->get_iter().get_offset(); - multiple_cursors_erase_backward_length = insert_offset - iter_start.get_offset(); - multiple_cursors_erase_forward_length = iter_end.get_offset() - insert_offset; - } - }, false); - - get_buffer()->signal_erase().connect( - [this](const Gtk::TextBuffer::iterator &iter_start, const Gtk::TextBuffer::iterator &iter_end) { - if (multiple_cursors_use) { - multiple_cursors_use = false; - for (auto &extra_cursor: multiple_cursors_extra_cursors) { - auto start_iter = extra_cursor.first->get_iter(); - auto end_iter = start_iter; - start_iter.backward_chars(multiple_cursors_erase_backward_length); - end_iter.forward_chars(multiple_cursors_erase_forward_length); - get_buffer()->erase(start_iter, end_iter); - } - multiple_cursors_use = true; - } - }); + auto allow_insertion = [](const Gtk::TextIter &iter) { + if (iter.ends_line() || *iter == ' ' || *iter == '\t' || *iter == ';' || *iter == ')' || *iter == ']' || + *iter == '[' || *iter == '{' || *iter == '}') + return true; + return false; + }; + + // Move right when clicking ' before a ' or when clicking " before a " + if (((key->keyval == GDK_KEY_apostrophe && *iter == '\'') || + (key->keyval == GDK_KEY_quotedbl && *iter == '\"')) && is_code_iter(next_iter)) { + bool perform_move = false; + if (*previous_iter != '\\') + perform_move = true; + else { + auto it = previous_iter; + long backslash_count = 1; + while (it.backward_char() && *it == '\\') { + ++backslash_count; + } + if (backslash_count % 2 == 0) + perform_move = true; + } + if (perform_move) { + get_buffer()->place_cursor(next_iter); + scroll_to(get_buffer()->get_insert()); + return true; } + } + // When to delete '' or "" + else if (key->keyval == GDK_KEY_BackSpace) { + if (((*previous_iter == '\'' && *iter == '\'') || + (*previous_iter == '"' && *iter == '"')) && is_code_iter(previous_iter)) { + get_buffer()->erase(previous_iter, next_iter); + scroll_to(get_buffer()->get_insert()); + return true; + } + } - - if (key->keyval == GDK_KEY_Escape && !multiple_cursors_extra_cursors.empty()) { - for (auto &extra_cursor: multiple_cursors_extra_cursors) { - extra_cursor.first->set_visible(false); - get_buffer()->delete_mark(extra_cursor.first); - } - multiple_cursors_extra_cursors.clear(); + if (is_code_iter(iter)) { + // Insert () + if (key->keyval == GDK_KEY_parenleft && allow_insertion(iter)) { + if (symbol_count(iter, '(', ')') == 0) { + get_buffer()->insert_at_cursor(")"); + iter = get_buffer()->get_insert()->get_iter(); + iter.backward_char(); + get_buffer()->place_cursor(iter); + get_buffer()->insert_at_cursor("("); + scroll_to(get_buffer()->get_insert()); return true; + } } - - unsigned create_cursor_mask = GDK_MOD1_MASK; - unsigned move_last_created_cursor_mask = GDK_SHIFT_MASK | GDK_MOD1_MASK; - - // Move last created cursor - if ((key->keyval == GDK_KEY_Left || key->keyval == GDK_KEY_KP_Left) && - (key->state & move_last_created_cursor_mask) == move_last_created_cursor_mask) { - if (multiple_cursors_extra_cursors.empty()) - return false; - auto &cursor = multiple_cursors_extra_cursors.back().first; - auto iter = cursor->get_iter(); + // Insert [] + else if (key->keyval == GDK_KEY_bracketleft && allow_insertion(iter)) { + if (symbol_count(iter, '[', ']') == 0) { + get_buffer()->insert_at_cursor("[]"); + auto iter = get_buffer()->get_insert()->get_iter(); iter.backward_char(); - get_buffer()->move_mark(cursor, iter); + get_buffer()->place_cursor(iter); + scroll_to(get_buffer()->get_insert()); return true; + } } - if ((key->keyval == GDK_KEY_Right || key->keyval == GDK_KEY_KP_Right) && - (key->state & move_last_created_cursor_mask) == move_last_created_cursor_mask) { - if (multiple_cursors_extra_cursors.empty()) - return false; - auto &cursor = multiple_cursors_extra_cursors.back().first; - auto iter = cursor->get_iter(); + // Move left on ] in [] + else if (key->keyval == GDK_KEY_bracketright) { + if (*iter == ']' && symbol_count(iter, '[', ']') == 0) { iter.forward_char(); - get_buffer()->move_mark(cursor, iter); + get_buffer()->place_cursor(iter); + scroll_to(get_buffer()->get_insert()); return true; - } - if ((key->keyval == GDK_KEY_Up || key->keyval == GDK_KEY_KP_Up) && - (key->state & move_last_created_cursor_mask) == move_last_created_cursor_mask) { - if (multiple_cursors_extra_cursors.empty()) - return false; - auto &extra_cursor = multiple_cursors_extra_cursors.back(); - auto iter = extra_cursor.first->get_iter(); - auto line_offset = extra_cursor.second; - if (iter.backward_line()) { - auto end_line_iter = iter; - if (!end_line_iter.ends_line()) - end_line_iter.forward_to_line_end(); - iter.forward_chars(std::min(line_offset, end_line_iter.get_line_offset())); - get_buffer()->move_mark(extra_cursor.first, iter); - } + } + } + // Insert '' + else if (key->keyval == GDK_KEY_apostrophe && allow_insertion(iter) && symbol_count(iter, '\'', -1) % 2 == 0) { + get_buffer()->insert_at_cursor("''"); + auto iter = get_buffer()->get_insert()->get_iter(); + iter.backward_char(); + get_buffer()->place_cursor(iter); + scroll_to(get_buffer()->get_insert()); + return true; + } + // Insert "" + else if (key->keyval == GDK_KEY_quotedbl && allow_insertion(iter) && symbol_count(iter, '"', -1) % 2 == 0) { + get_buffer()->insert_at_cursor("\"\""); + auto iter = get_buffer()->get_insert()->get_iter(); + iter.backward_char(); + get_buffer()->place_cursor(iter); + scroll_to(get_buffer()->get_insert()); + return true; + } + // Insert ; at the end of line, if iter is at the last ) + else if (key->keyval == GDK_KEY_semicolon) { + if (*iter == ')' && symbol_count(iter, '(', ')') == 0) { + if (next_iter.ends_line()) { + Gtk::TextIter open_non_curly_bracket_iter; + if (find_open_non_curly_bracket_backward(previous_iter, open_non_curly_bracket_iter)) { + open_non_curly_bracket_iter.backward_char(); + if (*open_non_curly_bracket_iter == ' ') + open_non_curly_bracket_iter.backward_char(); + if (get_token(open_non_curly_bracket_iter) != "for") { + iter.forward_char(); + get_buffer()->place_cursor(iter); + get_buffer()->insert_at_cursor(";"); + scroll_to(get_buffer()->get_insert()); + return true; + } + } + } + } + } + // Delete () + else if (key->keyval == GDK_KEY_BackSpace) { + if (*previous_iter == '(' && *iter == ')' && symbol_count(iter, '(', ')') == 0) { + auto next_iter = iter; + next_iter.forward_char(); + get_buffer()->erase(previous_iter, next_iter); + scroll_to(get_buffer()->get_insert()); return true; - } - if ((key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down) && - (key->state & move_last_created_cursor_mask) == move_last_created_cursor_mask) { - if (multiple_cursors_extra_cursors.empty()) - return false; - auto &extra_cursor = multiple_cursors_extra_cursors.back(); - auto iter = extra_cursor.first->get_iter(); - auto line_offset = extra_cursor.second; - if (iter.forward_line()) { - auto end_line_iter = iter; - if (!end_line_iter.ends_line()) - end_line_iter.forward_to_line_end(); - iter.forward_chars(std::min(line_offset, end_line_iter.get_line_offset())); - get_buffer()->move_mark(extra_cursor.first, iter); - } + } + // Delete [] + else if (*previous_iter == '[' && *iter == ']' && symbol_count(iter, '[', ']') == 0) { + auto next_iter = iter; + next_iter.forward_char(); + get_buffer()->erase(previous_iter, next_iter); + scroll_to(get_buffer()->get_insert()); return true; + } } + } - // Create extra cursor - if ((key->keyval == GDK_KEY_Up || key->keyval == GDK_KEY_KP_Up) && - (key->state & create_cursor_mask) == create_cursor_mask) { - auto insert_iter = get_buffer()->get_insert()->get_iter(); - auto insert_line_offset = insert_iter.get_line_offset(); - auto offset = insert_iter.get_offset(); - for (auto &extra_cursor: multiple_cursors_extra_cursors) - offset = std::min(offset, extra_cursor.first->get_iter().get_offset()); - auto iter = get_buffer()->get_iter_at_offset(offset); - if (iter.backward_line()) { - auto end_line_iter = iter; - if (!end_line_iter.ends_line()) - end_line_iter.forward_to_line_end(); - iter.forward_chars(std::min(insert_line_offset, end_line_iter.get_line_offset())); - multiple_cursors_extra_cursors.emplace_back(get_buffer()->create_mark(iter, false), insert_line_offset); - multiple_cursors_extra_cursors.back().first->set_visible(true); - } - return true; - } - if ((key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down) && - (key->state & create_cursor_mask) == create_cursor_mask) { - auto insert_iter = get_buffer()->get_insert()->get_iter(); - auto insert_line_offset = insert_iter.get_line_offset(); - auto offset = insert_iter.get_offset(); - for (auto &extra_cursor: multiple_cursors_extra_cursors) - offset = std::max(offset, extra_cursor.first->get_iter().get_offset()); - auto iter = get_buffer()->get_iter_at_offset(offset); - if (iter.forward_line()) { - auto end_line_iter = iter; - if (!end_line_iter.ends_line()) - end_line_iter.forward_to_line_end(); - iter.forward_chars(std::min(insert_line_offset, end_line_iter.get_line_offset())); - multiple_cursors_extra_cursors.emplace_back(get_buffer()->create_mark(iter, false), insert_line_offset); - multiple_cursors_extra_cursors.back().first->set_visible(true); - } - return true; + return false; +} + +bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) { + if (!multiple_cursors_signals_set) { + multiple_cursors_signals_set = true; + multiple_cursors_last_insert = get_buffer()->create_mark(get_buffer()->get_insert()->get_iter(), false); + get_buffer()->signal_mark_set().connect( + [this](const Gtk::TextBuffer::iterator &iter, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { + for (auto &extra_cursor: multiple_cursors_extra_cursors) { + if (extra_cursor.first == mark && + (!iter.ends_line() || iter.get_line_offset() > extra_cursor.second)) { + extra_cursor.second = iter.get_line_offset(); + break; + } + } + + if (mark->get_name() == "insert") { + if (multiple_cursors_use) { + multiple_cursors_use = false; + auto offset_diff = mark->get_iter().get_offset() - + multiple_cursors_last_insert->get_iter().get_offset(); + if (offset_diff != 0) { + for (auto &extra_cursor: multiple_cursors_extra_cursors) { + auto iter = extra_cursor.first->get_iter(); + iter.forward_chars(offset_diff); + get_buffer()->move_mark(extra_cursor.first, iter); + } + } + multiple_cursors_use = true; + } + get_buffer()->delete_mark(multiple_cursors_last_insert); + multiple_cursors_last_insert = get_buffer()->create_mark(mark->get_iter(), false); + } + }); + + // TODO: this handler should run after signal_insert + get_buffer()->signal_insert().connect( + [this](const Gtk::TextBuffer::iterator &iter, const Glib::ustring &text, int bytes) { + if (multiple_cursors_use) { + multiple_cursors_use = false; + auto offset = iter.get_offset() - get_buffer()->get_insert()->get_iter().get_offset(); + for (auto &extra_cursor: multiple_cursors_extra_cursors) { + auto iter = extra_cursor.first->get_iter(); + iter.forward_chars(offset); + get_buffer()->insert(iter, text); + } + multiple_cursors_use = true; + } + }); + + get_buffer()->signal_erase().connect( + [this](const Gtk::TextBuffer::iterator &iter_start, const Gtk::TextBuffer::iterator &iter_end) { + if (multiple_cursors_use) { + auto insert_offset = get_buffer()->get_insert()->get_iter().get_offset(); + multiple_cursors_erase_backward_length = insert_offset - iter_start.get_offset(); + multiple_cursors_erase_forward_length = iter_end.get_offset() - insert_offset; + } + }, false); + + get_buffer()->signal_erase().connect( + [this](const Gtk::TextBuffer::iterator &iter_start, const Gtk::TextBuffer::iterator &iter_end) { + if (multiple_cursors_use) { + multiple_cursors_use = false; + for (auto &extra_cursor: multiple_cursors_extra_cursors) { + auto start_iter = extra_cursor.first->get_iter(); + auto end_iter = start_iter; + start_iter.backward_chars(multiple_cursors_erase_backward_length); + end_iter.forward_chars(multiple_cursors_erase_forward_length); + get_buffer()->erase(start_iter, end_iter); + } + multiple_cursors_use = true; + } + }); + } + + + if (key->keyval == GDK_KEY_Escape && !multiple_cursors_extra_cursors.empty()) { + for (auto &extra_cursor: multiple_cursors_extra_cursors) { + extra_cursor.first->set_visible(false); + get_buffer()->delete_mark(extra_cursor.first); } + multiple_cursors_extra_cursors.clear(); + return true; + } - // Move cursors left/right - if ((key->keyval == GDK_KEY_Left || key->keyval == GDK_KEY_KP_Left) && (key->state & GDK_CONTROL_MASK) > 0) { - multiple_cursors_use = false; - for (auto &extra_cursor: multiple_cursors_extra_cursors) { - auto iter = extra_cursor.first->get_iter(); - iter.backward_word_start(); - extra_cursor.second = iter.get_line_offset(); - get_buffer()->move_mark(extra_cursor.first, iter); - } - auto insert = get_buffer()->get_insert(); - auto iter = insert->get_iter(); - iter.backward_word_start(); - get_buffer()->move_mark(insert, iter); - if ((key->state & GDK_SHIFT_MASK) == 0) - get_buffer()->move_mark_by_name("selection_bound", iter); - return true; + unsigned create_cursor_mask = GDK_MOD1_MASK; + unsigned move_last_created_cursor_mask = GDK_SHIFT_MASK | GDK_MOD1_MASK; + + // Move last created cursor + if ((key->keyval == GDK_KEY_Left || key->keyval == GDK_KEY_KP_Left) && + (key->state & move_last_created_cursor_mask) == move_last_created_cursor_mask) { + if (multiple_cursors_extra_cursors.empty()) + return false; + auto &cursor = multiple_cursors_extra_cursors.back().first; + auto iter = cursor->get_iter(); + iter.backward_char(); + get_buffer()->move_mark(cursor, iter); + return true; + } + if ((key->keyval == GDK_KEY_Right || key->keyval == GDK_KEY_KP_Right) && + (key->state & move_last_created_cursor_mask) == move_last_created_cursor_mask) { + if (multiple_cursors_extra_cursors.empty()) + return false; + auto &cursor = multiple_cursors_extra_cursors.back().first; + auto iter = cursor->get_iter(); + iter.forward_char(); + get_buffer()->move_mark(cursor, iter); + return true; + } + if ((key->keyval == GDK_KEY_Up || key->keyval == GDK_KEY_KP_Up) && + (key->state & move_last_created_cursor_mask) == move_last_created_cursor_mask) { + if (multiple_cursors_extra_cursors.empty()) + return false; + auto &extra_cursor = multiple_cursors_extra_cursors.back(); + auto iter = extra_cursor.first->get_iter(); + auto line_offset = extra_cursor.second; + if (iter.backward_line()) { + auto end_line_iter = iter; + if (!end_line_iter.ends_line()) + end_line_iter.forward_to_line_end(); + iter.forward_chars(std::min(line_offset, end_line_iter.get_line_offset())); + get_buffer()->move_mark(extra_cursor.first, iter); } - if ((key->keyval == GDK_KEY_Right || key->keyval == GDK_KEY_KP_Right) && (key->state & GDK_CONTROL_MASK) > 0) { - multiple_cursors_use = false; - for (auto &extra_cursor: multiple_cursors_extra_cursors) { - auto iter = extra_cursor.first->get_iter(); - iter.forward_visible_word_end(); - extra_cursor.second = iter.get_line_offset(); - get_buffer()->move_mark(extra_cursor.first, iter); - } - auto insert = get_buffer()->get_insert(); - auto iter = insert->get_iter(); - iter.forward_visible_word_end(); - get_buffer()->move_mark(insert, iter); - if ((key->state & GDK_SHIFT_MASK) == 0) - get_buffer()->move_mark_by_name("selection_bound", iter); - return true; + return true; + } + if ((key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down) && + (key->state & move_last_created_cursor_mask) == move_last_created_cursor_mask) { + if (multiple_cursors_extra_cursors.empty()) + return false; + auto &extra_cursor = multiple_cursors_extra_cursors.back(); + auto iter = extra_cursor.first->get_iter(); + auto line_offset = extra_cursor.second; + if (iter.forward_line()) { + auto end_line_iter = iter; + if (!end_line_iter.ends_line()) + end_line_iter.forward_to_line_end(); + iter.forward_chars(std::min(line_offset, end_line_iter.get_line_offset())); + get_buffer()->move_mark(extra_cursor.first, iter); } + return true; + } - // Move cursors up/down - if ((key->keyval == GDK_KEY_Up || key->keyval == GDK_KEY_KP_Up)) { - multiple_cursors_use = false; - for (auto &extra_cursor: multiple_cursors_extra_cursors) { - auto iter = extra_cursor.first->get_iter(); - auto line_offset = extra_cursor.second; - if (iter.backward_line()) { - auto end_line_iter = iter; - if (!end_line_iter.ends_line()) - end_line_iter.forward_to_line_end(); - iter.forward_chars(std::min(line_offset, end_line_iter.get_line_offset())); - get_buffer()->move_mark(extra_cursor.first, iter); - } - } - return false; + // Create extra cursor + if ((key->keyval == GDK_KEY_Up || key->keyval == GDK_KEY_KP_Up) && + (key->state & create_cursor_mask) == create_cursor_mask) { + auto insert_iter = get_buffer()->get_insert()->get_iter(); + auto insert_line_offset = insert_iter.get_line_offset(); + auto offset = insert_iter.get_offset(); + for (auto &extra_cursor: multiple_cursors_extra_cursors) + offset = std::min(offset, extra_cursor.first->get_iter().get_offset()); + auto iter = get_buffer()->get_iter_at_offset(offset); + if (iter.backward_line()) { + auto end_line_iter = iter; + if (!end_line_iter.ends_line()) + end_line_iter.forward_to_line_end(); + iter.forward_chars(std::min(insert_line_offset, end_line_iter.get_line_offset())); + multiple_cursors_extra_cursors.emplace_back(get_buffer()->create_mark(iter, false), insert_line_offset); + multiple_cursors_extra_cursors.back().first->set_visible(true); } - if ((key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down)) { - multiple_cursors_use = false; - for (auto &extra_cursor: multiple_cursors_extra_cursors) { - auto iter = extra_cursor.first->get_iter(); - auto line_offset = extra_cursor.second; - if (iter.forward_line()) { - auto end_line_iter = iter; - if (!end_line_iter.ends_line()) - end_line_iter.forward_to_line_end(); - iter.forward_chars(std::min(line_offset, end_line_iter.get_line_offset())); - get_buffer()->move_mark(extra_cursor.first, iter); - } - } - return false; + return true; + } + if ((key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down) && + (key->state & create_cursor_mask) == create_cursor_mask) { + auto insert_iter = get_buffer()->get_insert()->get_iter(); + auto insert_line_offset = insert_iter.get_line_offset(); + auto offset = insert_iter.get_offset(); + for (auto &extra_cursor: multiple_cursors_extra_cursors) + offset = std::max(offset, extra_cursor.first->get_iter().get_offset()); + auto iter = get_buffer()->get_iter_at_offset(offset); + if (iter.forward_line()) { + auto end_line_iter = iter; + if (!end_line_iter.ends_line()) + end_line_iter.forward_to_line_end(); + iter.forward_chars(std::min(insert_line_offset, end_line_iter.get_line_offset())); + multiple_cursors_extra_cursors.emplace_back(get_buffer()->create_mark(iter, false), insert_line_offset); + multiple_cursors_extra_cursors.back().first->set_visible(true); } + return true; + } - // Smart Home-key, start of line - if ((key->keyval == GDK_KEY_Home || key->keyval == GDK_KEY_KP_Home) && (key->state & GDK_CONTROL_MASK) == 0) { - for (auto &extra_cursor: multiple_cursors_extra_cursors) - get_buffer()->move_mark(extra_cursor.first, get_smart_home_iter(extra_cursor.first->get_iter())); - multiple_cursors_use = false; - return false; + // Move cursors left/right + if ((key->keyval == GDK_KEY_Left || key->keyval == GDK_KEY_KP_Left) && (key->state & GDK_CONTROL_MASK) > 0) { + multiple_cursors_use = false; + for (auto &extra_cursor: multiple_cursors_extra_cursors) { + auto iter = extra_cursor.first->get_iter(); + iter.backward_word_start(); + extra_cursor.second = iter.get_line_offset(); + get_buffer()->move_mark(extra_cursor.first, iter); + } + auto insert = get_buffer()->get_insert(); + auto iter = insert->get_iter(); + iter.backward_word_start(); + get_buffer()->move_mark(insert, iter); + if ((key->state & GDK_SHIFT_MASK) == 0) + get_buffer()->move_mark_by_name("selection_bound", iter); + return true; + } + if ((key->keyval == GDK_KEY_Right || key->keyval == GDK_KEY_KP_Right) && (key->state & GDK_CONTROL_MASK) > 0) { + multiple_cursors_use = false; + for (auto &extra_cursor: multiple_cursors_extra_cursors) { + auto iter = extra_cursor.first->get_iter(); + iter.forward_visible_word_end(); + extra_cursor.second = iter.get_line_offset(); + get_buffer()->move_mark(extra_cursor.first, iter); + } + auto insert = get_buffer()->get_insert(); + auto iter = insert->get_iter(); + iter.forward_visible_word_end(); + get_buffer()->move_mark(insert, iter); + if ((key->state & GDK_SHIFT_MASK) == 0) + get_buffer()->move_mark_by_name("selection_bound", iter); + return true; + } + + // Move cursors up/down + if ((key->keyval == GDK_KEY_Up || key->keyval == GDK_KEY_KP_Up)) { + multiple_cursors_use = false; + for (auto &extra_cursor: multiple_cursors_extra_cursors) { + auto iter = extra_cursor.first->get_iter(); + auto line_offset = extra_cursor.second; + if (iter.backward_line()) { + auto end_line_iter = iter; + if (!end_line_iter.ends_line()) + end_line_iter.forward_to_line_end(); + iter.forward_chars(std::min(line_offset, end_line_iter.get_line_offset())); + get_buffer()->move_mark(extra_cursor.first, iter); + } } - // Smart End-key, end of line - if ((key->keyval == GDK_KEY_End || key->keyval == GDK_KEY_KP_End) && (key->state & GDK_CONTROL_MASK) == 0) { - for (auto &extra_cursor: multiple_cursors_extra_cursors) - get_buffer()->move_mark(extra_cursor.first, get_smart_end_iter(extra_cursor.first->get_iter())); - multiple_cursors_use = false; - return false; + return false; + } + if ((key->keyval == GDK_KEY_Down || key->keyval == GDK_KEY_KP_Down)) { + multiple_cursors_use = false; + for (auto &extra_cursor: multiple_cursors_extra_cursors) { + auto iter = extra_cursor.first->get_iter(); + auto line_offset = extra_cursor.second; + if (iter.forward_line()) { + auto end_line_iter = iter; + if (!end_line_iter.ends_line()) + end_line_iter.forward_to_line_end(); + iter.forward_chars(std::min(line_offset, end_line_iter.get_line_offset())); + get_buffer()->move_mark(extra_cursor.first, iter); + } } + return false; + } + // Smart Home-key, start of line + if ((key->keyval == GDK_KEY_Home || key->keyval == GDK_KEY_KP_Home) && (key->state & GDK_CONTROL_MASK) == 0) { + for (auto &extra_cursor: multiple_cursors_extra_cursors) + get_buffer()->move_mark(extra_cursor.first, get_smart_home_iter(extra_cursor.first->get_iter())); + multiple_cursors_use = false; + return false; + } + // Smart End-key, end of line + if ((key->keyval == GDK_KEY_End || key->keyval == GDK_KEY_KP_End) && (key->state & GDK_CONTROL_MASK) == 0) { + for (auto &extra_cursor: multiple_cursors_extra_cursors) + get_buffer()->move_mark(extra_cursor.first, get_smart_end_iter(extra_cursor.first->get_iter())); + multiple_cursors_use = false; return false; + } + + return false; } bool Source::View::on_button_press_event(GdkEventButton *event) { - // Select range when double clicking - if (event->type == GDK_2BUTTON_PRESS) { - Gtk::TextIter start, end; - get_buffer()->get_selection_bounds(start, end); - auto iter = start; - while ((*iter >= 48 && *iter <= 57) || (*iter >= 65 && *iter <= 90) || (*iter >= 97 && *iter <= 122) || - *iter == 95) { - start = iter; - if (!iter.backward_char()) - break; - } - while ((*end >= 48 && *end <= 57) || (*end >= 65 && *end <= 90) || (*end >= 97 && *end <= 122) || *end == 95) { - if (!end.forward_char()) - break; - } - get_buffer()->select_range(start, end); - return true; - } + // Select range when double clicking + if (event->type == GDK_2BUTTON_PRESS) { + Gtk::TextIter start, end; + get_buffer()->get_selection_bounds(start, end); + auto iter = start; + while ((*iter >= 48 && *iter <= 57) || (*iter >= 65 && *iter <= 90) || (*iter >= 97 && *iter <= 122) || + *iter == 95) { + start = iter; + if (!iter.backward_char()) + break; + } + while ((*end >= 48 && *end <= 57) || (*end >= 65 && *end <= 90) || (*end >= 97 && *end <= 122) || *end == 95) { + if (!end.forward_char()) + break; + } + get_buffer()->select_range(start, end); + return true; + } - // Go to implementation or declaration - if ((event->type == GDK_BUTTON_PRESS) && (event->button == 1)) { + // Go to implementation or declaration + if ((event->type == GDK_BUTTON_PRESS) && (event->button == 1)) { #ifdef __APPLE__ - GdkModifierType mask=GDK_MOD2_MASK; + GdkModifierType mask=GDK_MOD2_MASK; #else - GdkModifierType mask = GDK_CONTROL_MASK; + GdkModifierType mask = GDK_CONTROL_MASK; #endif - if (event->state & mask) { - int x, y; - window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, event->x, event->y, x, y); - Gtk::TextIter iter; - get_iter_at_location(iter, x, y); - if (iter) - get_buffer()->place_cursor(iter); - - Menu::get().actions["source_goto_declaration_or_implementation"]->activate(); - return true; - } + if (event->state & mask) { + int x, y; + window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, event->x, event->y, x, y); + Gtk::TextIter iter; + get_iter_at_location(iter, x, y); + if (iter) + get_buffer()->place_cursor(iter); + + Menu::get().actions["source_goto_declaration_or_implementation"]->activate(); + return true; } + } - // Open right click menu - if ((event->type == GDK_BUTTON_PRESS) && (event->button == 3)) { - hide_tooltips(); - if (!get_buffer()->get_has_selection()) { - int x, y; - window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, event->x, event->y, x, y); - Gtk::TextIter iter; - get_iter_at_location(iter, x, y); - if (iter) - get_buffer()->place_cursor(iter); - Menu::get().right_click_line_menu->popup(event->button, event->time); - } else { - Menu::get().right_click_selected_menu->popup(event->button, event->time); - } - return true; + // Open right click menu + if ((event->type == GDK_BUTTON_PRESS) && (event->button == 3)) { + hide_tooltips(); + if (!get_buffer()->get_has_selection()) { + int x, y; + window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, event->x, event->y, x, y); + Gtk::TextIter iter; + get_iter_at_location(iter, x, y); + if (iter) + get_buffer()->place_cursor(iter); + Menu::get().right_click_line_menu->popup(event->button, event->time); + } else { + Menu::get().right_click_selected_menu->popup(event->button, event->time); } - return Gsv::View::on_button_press_event(event); + return true; + } + return Gsv::View::on_button_press_event(event); } bool Source::View::on_motion_notify_event(GdkEventMotion *event) { - // Workaround for drag-and-drop crash on MacOS - // TODO 2018: check if this bug has been fixed + // Workaround for drag-and-drop crash on MacOS + // TODO 2018: check if this bug has been fixed #ifdef __APPLE__ - if((event->state & GDK_BUTTON1_MASK) == 0) - return Gsv::View::on_motion_notify_event(event); - else { - int x, y; - window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, event->x, event->y, x, y); - Gtk::TextIter iter; - get_iter_at_location(iter, x, y); - get_buffer()->select_range(get_buffer()->get_insert()->get_iter(), iter); - return true; - } + if((event->state & GDK_BUTTON1_MASK) == 0) +return Gsv::View::on_motion_notify_event(event); +else { +int x, y; +window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, event->x, event->y, x, y); +Gtk::TextIter iter; +get_iter_at_location(iter, x, y); +get_buffer()->select_range(get_buffer()->get_insert()->get_iter(), iter); +return true; +} #else - return Gsv::View::on_motion_notify_event(event); + return Gsv::View::on_motion_notify_event(event); #endif } std::pair<char, unsigned> Source::View::find_tab_char_and_size() { - std::unordered_map<char, size_t> tab_chars; - std::unordered_map<unsigned, size_t> tab_sizes; - auto iter = get_buffer()->begin(); - long tab_count = -1; - long last_tab_count = 0; - bool single_quoted = false; - bool double_quoted = false; - //For bracket languages, TODO: add more language ids - if (is_bracket_language && !(language && language->get_id() == "html")) { - bool line_comment = false; - bool comment = false; - bool bracket_last_line = false; - char last_char = 0; - long last_tab_diff = -1; - while (iter) { - if (iter.starts_line()) { - line_comment = false; - single_quoted = false; - double_quoted = false; - tab_count = 0; - if (last_char == '{') - bracket_last_line = true; - else - bracket_last_line = false; - } - if (bracket_last_line && tab_count != -1) { - if (*iter == ' ') { - tab_chars[' ']++; - tab_count++; - } else if (*iter == '\t') { - tab_chars['\t']++; - tab_count++; - } else { - auto line_iter = iter; - char last_line_char = 0; - while (line_iter && !line_iter.ends_line()) { - if (*line_iter != ' ' && *line_iter != '\t') - last_line_char = *line_iter; - if (*line_iter == '(') - break; - line_iter.forward_char(); - } - if (last_line_char == ':' || *iter == '#') { - tab_count = 0; - if ((iter.get_line() + 1) < get_buffer()->get_line_count()) { - iter = get_buffer()->get_iter_at_line(iter.get_line() + 1); - continue; - } - } else if (!iter.ends_line()) { - if (tab_count != last_tab_count) - tab_sizes[std::abs(tab_count - last_tab_count)]++; - last_tab_diff = std::abs(tab_count - last_tab_count); - last_tab_count = tab_count; - last_char = 0; - } - } - } - - auto prev_iter = iter; - prev_iter.backward_char(); - auto prev_prev_iter = prev_iter; - prev_prev_iter.backward_char(); - if (!double_quoted && *iter == '\'' && !(*prev_iter == '\\' && *prev_prev_iter != '\\')) - single_quoted = !single_quoted; - else if (!single_quoted && *iter == '\"' && !(*prev_iter == '\\' && *prev_prev_iter != '\\')) - double_quoted = !double_quoted; - else if (!single_quoted && !double_quoted) { - auto next_iter = iter; - next_iter.forward_char(); - if (*iter == '/' && *next_iter == '/') - line_comment = true; - else if (*iter == '/' && *next_iter == '*') - comment = true; - else if (*iter == '*' && *next_iter == '/') { - iter.forward_char(); - iter.forward_char(); - comment = false; - } - } - if (!single_quoted && !double_quoted && !comment && !line_comment && *iter != ' ' && *iter != '\t' && - !iter.ends_line()) - last_char = *iter; - if (!single_quoted && !double_quoted && !comment && !line_comment && *iter == '}' && tab_count != -1 && - last_tab_diff != -1) - last_tab_count -= last_tab_diff; - if (*iter != ' ' && *iter != '\t') - tab_count = -1; - - iter.forward_char(); - } - } else { - long para_count = 0; - while (iter) { - if (iter.starts_line()) - tab_count = 0; - if (tab_count != -1 && para_count == 0 && single_quoted == false && double_quoted == false) { - if (*iter == ' ') { - tab_chars[' ']++; - tab_count++; - } else if (*iter == '\t') { - tab_chars['\t']++; - tab_count++; - } else if (!iter.ends_line()) { - if (tab_count != last_tab_count) - tab_sizes[std::abs(tab_count - last_tab_count)]++; - last_tab_count = tab_count; - } - } - auto prev_iter = iter; - prev_iter.backward_char(); - auto prev_prev_iter = prev_iter; - prev_prev_iter.backward_char(); - if (!double_quoted && *iter == '\'' && !(*prev_iter == '\\' && *prev_prev_iter != '\\')) - single_quoted = !single_quoted; - else if (!single_quoted && *iter == '\"' && !(*prev_iter == '\\' && *prev_prev_iter != '\\')) - double_quoted = !double_quoted; - else if (!single_quoted && !double_quoted) { - if (*iter == '(') - para_count++; - else if (*iter == ')') - para_count--; - } - if (*iter != ' ' && *iter != '\t') - tab_count = -1; - - iter.forward_char(); - } + std::unordered_map<char, size_t> tab_chars; + std::unordered_map<unsigned, size_t> tab_sizes; + auto iter = get_buffer()->begin(); + long tab_count = -1; + long last_tab_count = 0; + bool single_quoted = false; + bool double_quoted = false; + //For bracket languages, TODO: add more language ids + if (is_bracket_language && !(language && language->get_id() == "html")) { + bool line_comment = false; + bool comment = false; + bool bracket_last_line = false; + char last_char = 0; + long last_tab_diff = -1; + while (iter) { + if (iter.starts_line()) { + line_comment = false; + single_quoted = false; + double_quoted = false; + tab_count = 0; + if (last_char == '{') + bracket_last_line = true; + else + bracket_last_line = false; + } + if (bracket_last_line && tab_count != -1) { + if (*iter == ' ') { + tab_chars[' ']++; + tab_count++; + } else if (*iter == '\t') { + tab_chars['\t']++; + tab_count++; + } else { + auto line_iter = iter; + char last_line_char = 0; + while (line_iter && !line_iter.ends_line()) { + if (*line_iter != ' ' && *line_iter != '\t') + last_line_char = *line_iter; + if (*line_iter == '(') + break; + line_iter.forward_char(); + } + if (last_line_char == ':' || *iter == '#') { + tab_count = 0; + if ((iter.get_line() + 1) < get_buffer()->get_line_count()) { + iter = get_buffer()->get_iter_at_line(iter.get_line() + 1); + continue; + } + } else if (!iter.ends_line()) { + if (tab_count != last_tab_count) + tab_sizes[std::abs(tab_count - last_tab_count)]++; + last_tab_diff = std::abs(tab_count - last_tab_count); + last_tab_count = tab_count; + last_char = 0; + } + } + } + + auto prev_iter = iter; + prev_iter.backward_char(); + auto prev_prev_iter = prev_iter; + prev_prev_iter.backward_char(); + if (!double_quoted && *iter == '\'' && !(*prev_iter == '\\' && *prev_prev_iter != '\\')) + single_quoted = !single_quoted; + else if (!single_quoted && *iter == '\"' && !(*prev_iter == '\\' && *prev_prev_iter != '\\')) + double_quoted = !double_quoted; + else if (!single_quoted && !double_quoted) { + auto next_iter = iter; + next_iter.forward_char(); + if (*iter == '/' && *next_iter == '/') + line_comment = true; + else if (*iter == '/' && *next_iter == '*') + comment = true; + else if (*iter == '*' && *next_iter == '/') { + iter.forward_char(); + iter.forward_char(); + comment = false; + } + } + if (!single_quoted && !double_quoted && !comment && !line_comment && *iter != ' ' && *iter != '\t' && + !iter.ends_line()) + last_char = *iter; + if (!single_quoted && !double_quoted && !comment && !line_comment && *iter == '}' && tab_count != -1 && + last_tab_diff != -1) + last_tab_count -= last_tab_diff; + if (*iter != ' ' && *iter != '\t') + tab_count = -1; + + iter.forward_char(); + } + } else { + long para_count = 0; + while (iter) { + if (iter.starts_line()) + tab_count = 0; + if (tab_count != -1 && para_count == 0 && single_quoted == false && double_quoted == false) { + if (*iter == ' ') { + tab_chars[' ']++; + tab_count++; + } else if (*iter == '\t') { + tab_chars['\t']++; + tab_count++; + } else if (!iter.ends_line()) { + if (tab_count != last_tab_count) + tab_sizes[std::abs(tab_count - last_tab_count)]++; + last_tab_count = tab_count; + } + } + auto prev_iter = iter; + prev_iter.backward_char(); + auto prev_prev_iter = prev_iter; + prev_prev_iter.backward_char(); + if (!double_quoted && *iter == '\'' && !(*prev_iter == '\\' && *prev_prev_iter != '\\')) + single_quoted = !single_quoted; + else if (!single_quoted && *iter == '\"' && !(*prev_iter == '\\' && *prev_prev_iter != '\\')) + double_quoted = !double_quoted; + else if (!single_quoted && !double_quoted) { + if (*iter == '(') + para_count++; + else if (*iter == ')') + para_count--; + } + if (*iter != ' ' && *iter != '\t') + tab_count = -1; + + iter.forward_char(); } + } - char found_tab_char = 0; - size_t occurences = 0; - for (auto &tab_char: tab_chars) { - if (tab_char.second > occurences) { - found_tab_char = tab_char.first; - occurences = tab_char.second; - } + char found_tab_char = 0; + size_t occurences = 0; + for (auto &tab_char: tab_chars) { + if (tab_char.second > occurences) { + found_tab_char = tab_char.first; + occurences = tab_char.second; } - unsigned found_tab_size = 0; - occurences = 0; - for (auto &tab_size: tab_sizes) { - if (tab_size.second > occurences) { - found_tab_size = tab_size.first; - occurences = tab_size.second; - } + } + unsigned found_tab_size = 0; + occurences = 0; + for (auto &tab_size: tab_sizes) { + if (tab_size.second > occurences) { + found_tab_size = tab_size.first; + occurences = tab_size.second; } - return {found_tab_char, found_tab_size}; + } + return {found_tab_char, found_tab_size}; } @@ -2903,83 +2903,83 @@ std::pair<char, unsigned> Source::View::find_tab_char_and_size() { //// GenericView //// ///////////////////// Source::GenericView::GenericView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language) - : BaseView(file_path, language), View(file_path, language, true) { - configure(); - spellcheck_all = true; - - if (language) - get_source_buffer()->set_language(language); - - auto completion = get_completion(); - completion->property_show_headers() = false; - completion->property_show_icons() = false; - completion->property_accelerators() = 0; - - auto completion_words = Gsv::CompletionWords::create("", Glib::RefPtr<Gdk::Pixbuf>()); - completion_words->register_provider(get_buffer()); - completion->add_provider(completion_words); - - if (language) { - auto language_manager = LanguageManager::get_default(); - auto search_paths = language_manager->get_search_path(); - bool found_language_file = false; - boost::filesystem::path language_file; - for (auto &search_path: search_paths) { - boost::filesystem::path p( - static_cast<std::string>(search_path) + '/' + static_cast<std::string>(language->get_id()) + - ".lang"); - if (boost::filesystem::exists(p) && boost::filesystem::is_regular_file(p)) { - language_file = p; - found_language_file = true; - break; - } - } - if (found_language_file) { - auto completion_buffer_keywords = CompletionBuffer::create(); - boost::property_tree::ptree pt; - try { - boost::property_tree::xml_parser::read_xml(language_file.string(), pt); - } - catch (const std::exception &e) { - Terminal::get().print( - "Error: error parsing language file " + language_file.string() + ": " + e.what() + '\n', true); - } - bool has_context_class = false; - parse_language_file(completion_buffer_keywords, has_context_class, pt); - if (!has_context_class) - spellcheck_all = false; - completion_words->register_provider(completion_buffer_keywords); - } + : BaseView(file_path, language), View(file_path, language, true) { + configure(); + spellcheck_all = true; + + if (language) + get_source_buffer()->set_language(language); + + auto completion = get_completion(); + completion->property_show_headers() = false; + completion->property_show_icons() = false; + completion->property_accelerators() = 0; + + auto completion_words = Gsv::CompletionWords::create("", Glib::RefPtr<Gdk::Pixbuf>()); + completion_words->register_provider(get_buffer()); + completion->add_provider(completion_words); + + if (language) { + auto language_manager = LanguageManager::get_default(); + auto search_paths = language_manager->get_search_path(); + bool found_language_file = false; + boost::filesystem::path language_file; + for (auto &search_path: search_paths) { + boost::filesystem::path p( + static_cast<std::string>(search_path) + '/' + static_cast<std::string>(language->get_id()) + + ".lang"); + if (boost::filesystem::exists(p) && boost::filesystem::is_regular_file(p)) { + language_file = p; + found_language_file = true; + break; + } + } + if (found_language_file) { + auto completion_buffer_keywords = CompletionBuffer::create(); + boost::property_tree::ptree pt; + try { + boost::property_tree::xml_parser::read_xml(language_file.string(), pt); + } + catch (const std::exception &e) { + Terminal::get().print( + "Error: error parsing language file " + language_file.string() + ": " + e.what() + '\n', true); + } + bool has_context_class = false; + parse_language_file(completion_buffer_keywords, has_context_class, pt); + if (!has_context_class) + spellcheck_all = false; + completion_words->register_provider(completion_buffer_keywords); } + } } void Source::GenericView::parse_language_file(Glib::RefPtr<CompletionBuffer> &completion_buffer, bool &has_context_class, const boost::property_tree::ptree &pt) { - bool case_insensitive = false; - for (auto &node: pt) { - if (node.first == "<xmlcomment>") { - auto data = static_cast<std::string>(node.second.data()); - std::transform(data.begin(), data.end(), data.begin(), ::tolower); - if (data.find("case insensitive") != std::string::npos) - case_insensitive = true; - } else if (node.first == "keyword") { - auto data = static_cast<std::string>(node.second.data()); - completion_buffer->insert_at_cursor(data + '\n'); - if (case_insensitive) { - std::transform(data.begin(), data.end(), data.begin(), ::tolower); - completion_buffer->insert_at_cursor(data + '\n'); - } - } else if (!has_context_class && node.first == "context") { - auto class_attribute = node.second.get<std::string>("<xmlattr>.class", ""); - auto class_disabled_attribute = node.second.get<std::string>("<xmlattr>.class-disabled", ""); - if (class_attribute.size() > 0 || class_disabled_attribute.size() > 0) - has_context_class = true; - } - try { - parse_language_file(completion_buffer, has_context_class, node.second); - } - catch (const std::exception &e) { - } + bool case_insensitive = false; + for (auto &node: pt) { + if (node.first == "<xmlcomment>") { + auto data = static_cast<std::string>(node.second.data()); + std::transform(data.begin(), data.end(), data.begin(), ::tolower); + if (data.find("case insensitive") != std::string::npos) + case_insensitive = true; + } else if (node.first == "keyword") { + auto data = static_cast<std::string>(node.second.data()); + completion_buffer->insert_at_cursor(data + '\n'); + if (case_insensitive) { + std::transform(data.begin(), data.end(), data.begin(), ::tolower); + completion_buffer->insert_at_cursor(data + '\n'); + } + } else if (!has_context_class && node.first == "context") { + auto class_attribute = node.second.get<std::string>("<xmlattr>.class", ""); + auto class_disabled_attribute = node.second.get<std::string>("<xmlattr>.class-disabled", ""); + if (class_attribute.size() > 0 || class_disabled_attribute.size() > 0) + has_context_class = true; + } + try { + parse_language_file(completion_buffer, has_context_class, node.second); + } + catch (const std::exception &e) { } + } } diff --git a/src/source.h b/src/source.h index 48d52d2e..c261e036 100644 --- a/src/source.h +++ b/src/source.h @@ -11,213 +11,213 @@ #include <tuple> namespace Source { - /// Workaround for buggy Gsv::LanguageManager::get_default() - class LanguageManager { - public: - static Glib::RefPtr<Gsv::LanguageManager> get_default(); - }; + /// Workaround for buggy Gsv::LanguageManager::get_default() + class LanguageManager { + public: + static Glib::RefPtr<Gsv::LanguageManager> get_default(); + }; - /// Workaround for buggy Gsv::StyleSchemeManager::get_default() - class StyleSchemeManager { - public: - static Glib::RefPtr<Gsv::StyleSchemeManager> get_default(); - }; + /// Workaround for buggy Gsv::StyleSchemeManager::get_default() + class StyleSchemeManager { + public: + static Glib::RefPtr<Gsv::StyleSchemeManager> get_default(); + }; - Glib::RefPtr<Gsv::Language> guess_language(const boost::filesystem::path &file_path); + Glib::RefPtr<Gsv::Language> guess_language(const boost::filesystem::path &file_path); - class Offset { - public: - Offset() {} + class Offset { + public: + Offset() {} + + Offset(unsigned line, unsigned index, const boost::filesystem::path &file_path = "") : line(line), index(index), + file_path(file_path) {} - Offset(unsigned line, unsigned index, const boost::filesystem::path &file_path = "") : line(line), index(index), - file_path(file_path) {} + operator bool() { return !file_path.empty(); } - operator bool() { return !file_path.empty(); } + bool operator==(const Offset &o) { return (line == o.line && index == o.index); } - bool operator==(const Offset &o) { return (line == o.line && index == o.index); } + unsigned line; + unsigned index; + boost::filesystem::path file_path; + }; - unsigned line; - unsigned index; - boost::filesystem::path file_path; + class FixIt { + public: + enum class Type { + INSERT, REPLACE, ERASE }; - class FixIt { - public: - enum class Type { - INSERT, REPLACE, ERASE - }; + FixIt(const std::string &source, const std::pair<Offset, Offset> &offsets); - FixIt(const std::string &source, const std::pair<Offset, Offset> &offsets); + std::string string(Glib::RefPtr<Gtk::TextBuffer> buffer); - std::string string(Glib::RefPtr<Gtk::TextBuffer> buffer); + Type type; + std::string source; + std::pair<Offset, Offset> offsets; + }; - Type type; - std::string source; - std::pair<Offset, Offset> offsets; - }; + class View : public SpellCheckView, public DiffView { + public: + static std::unordered_set<View *> non_deleted_views; + static std::unordered_set<View *> views; - class View : public SpellCheckView, public DiffView { - public: - static std::unordered_set<View *> non_deleted_views; - static std::unordered_set<View *> views; + View(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language, + bool is_generic_view = false); - View(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language, - bool is_generic_view = false); + ~View(); - ~View(); + bool save() override; - bool save() override; + void configure() override; - void configure() override; + void search_highlight(const std::string &text, bool case_sensitive, bool regex); - void search_highlight(const std::string &text, bool case_sensitive, bool regex); + std::function<void(int number)> update_search_occurrences; - std::function<void(int number)> update_search_occurrences; + void search_forward(); - void search_forward(); + void search_backward(); - void search_backward(); + void replace_forward(const std::string &replacement); - void replace_forward(const std::string &replacement); + void replace_backward(const std::string &replacement); - void replace_backward(const std::string &replacement); + void replace_all(const std::string &replacement); - void replace_all(const std::string &replacement); + void paste(); - void paste(); + std::function<void()> non_interactive_completion; + std::function<void(bool)> format_style; + std::function<Offset()> get_declaration_location; + std::function<Offset()> get_type_declaration_location; + std::function<std::vector<Offset>()> get_implementation_locations; + std::function<std::vector<Offset>()> get_declaration_or_implementation_locations; + std::function<std::vector<std::pair<Offset, std::string> >()> get_usages; + std::function<std::string()> get_method; + std::function<std::vector<std::pair<Offset, std::string> >()> get_methods; + std::function<std::vector<std::string>()> get_token_data; + std::function<std::string()> get_token_spelling; + std::function<void(const std::string &text)> rename_similar_tokens; + std::function<void()> goto_next_diagnostic; + std::function<std::vector<FixIt>()> get_fix_its; + std::function<void()> toggle_comments; + std::function<std::tuple<Source::Offset, std::string, size_t>()> get_documentation_template; + std::function<void(int)> toggle_breakpoint; - std::function<void()> non_interactive_completion; - std::function<void(bool)> format_style; - std::function<Offset()> get_declaration_location; - std::function<Offset()> get_type_declaration_location; - std::function<std::vector<Offset>()> get_implementation_locations; - std::function<std::vector<Offset>()> get_declaration_or_implementation_locations; - std::function<std::vector<std::pair<Offset, std::string> >()> get_usages; - std::function<std::string()> get_method; - std::function<std::vector<std::pair<Offset, std::string> >()> get_methods; - std::function<std::vector<std::string>()> get_token_data; - std::function<std::string()> get_token_spelling; - std::function<void(const std::string &text)> rename_similar_tokens; - std::function<void()> goto_next_diagnostic; - std::function<std::vector<FixIt>()> get_fix_its; - std::function<void()> toggle_comments; - std::function<std::tuple<Source::Offset, std::string, size_t>()> get_documentation_template; - std::function<void(int)> toggle_breakpoint; + void hide_tooltips() override; - void hide_tooltips() override; + void hide_dialogs() override; - void hide_dialogs() override; + void set_tab_char_and_size(char tab_char, unsigned tab_size); - void set_tab_char_and_size(char tab_char, unsigned tab_size); + std::pair<char, unsigned> get_tab_char_and_size() { return {tab_char, tab_size}; } - std::pair<char, unsigned> get_tab_char_and_size() { return {tab_char, tab_size}; } + bool soft_reparse_needed = false; + bool full_reparse_needed = false; - bool soft_reparse_needed = false; - bool full_reparse_needed = false; + virtual void soft_reparse(bool delayed = false) { soft_reparse_needed = false; } - virtual void soft_reparse(bool delayed = false) { soft_reparse_needed = false; } + virtual void full_reparse() { full_reparse_needed = false; } - virtual void full_reparse() { full_reparse_needed = false; } + protected: + bool parsed = true; + Tooltips diagnostic_tooltips; + Tooltips type_tooltips; + sigc::connection delayed_tooltips_connection; - protected: - bool parsed = true; - Tooltips diagnostic_tooltips; - Tooltips type_tooltips; - sigc::connection delayed_tooltips_connection; + virtual void show_diagnostic_tooltips(const Gdk::Rectangle &rectangle) { diagnostic_tooltips.show(rectangle); } - virtual void show_diagnostic_tooltips(const Gdk::Rectangle &rectangle) { diagnostic_tooltips.show(rectangle); } + void + add_diagnostic_tooltip(const Gtk::TextIter &start, const Gtk::TextIter &end, std::string spelling, bool error); - void - add_diagnostic_tooltip(const Gtk::TextIter &start, const Gtk::TextIter &end, std::string spelling, bool error); + void clear_diagnostic_tooltips(); - void clear_diagnostic_tooltips(); + virtual void show_type_tooltips(const Gdk::Rectangle &rectangle) {} - virtual void show_type_tooltips(const Gdk::Rectangle &rectangle) {} + gdouble on_motion_last_x = 0.0; + gdouble on_motion_last_y = 0.0; - gdouble on_motion_last_x = 0.0; - gdouble on_motion_last_y = 0.0; + /// Usually returns at start of line, but not always + Gtk::TextIter find_start_of_sentence(Gtk::TextIter iter); - /// Usually returns at start of line, but not always - Gtk::TextIter find_start_of_sentence(Gtk::TextIter iter); + bool find_open_non_curly_bracket_backward(Gtk::TextIter iter, Gtk::TextIter &found_iter); - bool find_open_non_curly_bracket_backward(Gtk::TextIter iter, Gtk::TextIter &found_iter); + bool find_open_curly_bracket_backward(Gtk::TextIter iter, Gtk::TextIter &found_iter); - bool find_open_curly_bracket_backward(Gtk::TextIter iter, Gtk::TextIter &found_iter); + bool find_close_curly_bracket_forward(Gtk::TextIter iter, Gtk::TextIter &found_iter); - bool find_close_curly_bracket_forward(Gtk::TextIter iter, Gtk::TextIter &found_iter); + long symbol_count(Gtk::TextIter iter, unsigned int positive_char, unsigned int negative_char); - long symbol_count(Gtk::TextIter iter, unsigned int positive_char, unsigned int negative_char); + bool is_templated_function(Gtk::TextIter iter, Gtk::TextIter &parenthesis_end_iter); - bool is_templated_function(Gtk::TextIter iter, Gtk::TextIter &parenthesis_end_iter); + std::string get_token(Gtk::TextIter iter); - std::string get_token(Gtk::TextIter iter); + void cleanup_whitespace_characters_on_return(const Gtk::TextIter &iter); - void cleanup_whitespace_characters_on_return(const Gtk::TextIter &iter); + bool is_bracket_language = false; - bool is_bracket_language = false; + bool on_key_press_event(GdkEventKey *key) override; - bool on_key_press_event(GdkEventKey *key) override; + bool on_key_press_event_basic(GdkEventKey *key); - bool on_key_press_event_basic(GdkEventKey *key); + bool on_key_press_event_bracket_language(GdkEventKey *key); - bool on_key_press_event_bracket_language(GdkEventKey *key); + bool on_key_press_event_smart_brackets(GdkEventKey *key); - bool on_key_press_event_smart_brackets(GdkEventKey *key); + bool on_key_press_event_smart_inserts(GdkEventKey *key); - bool on_key_press_event_smart_inserts(GdkEventKey *key); + bool on_button_press_event(GdkEventButton *event) override; - bool on_button_press_event(GdkEventButton *event) override; + bool on_motion_notify_event(GdkEventMotion *motion_event) override; - bool on_motion_notify_event(GdkEventMotion *motion_event) override; + std::pair<char, unsigned> find_tab_char_and_size(); - std::pair<char, unsigned> find_tab_char_and_size(); + unsigned tab_size; + char tab_char; + std::string tab; - unsigned tab_size; - char tab_char; - std::string tab; + bool interactive_completion = true; - bool interactive_completion = true; + guint previous_non_modifier_keyval = 0; + private: + void setup_tooltip_and_dialog_events(); - guint previous_non_modifier_keyval = 0; - private: - void setup_tooltip_and_dialog_events(); + void setup_format_style(bool is_generic_view); - void setup_format_style(bool is_generic_view); + void cleanup_whitespace_characters(); - void cleanup_whitespace_characters(); + Gsv::DrawSpacesFlags parse_show_whitespace_characters(const std::string &text); - Gsv::DrawSpacesFlags parse_show_whitespace_characters(const std::string &text); + GtkSourceSearchContext *search_context; + GtkSourceSearchSettings *search_settings; - GtkSourceSearchContext *search_context; - GtkSourceSearchSettings *search_settings; + static void search_occurrences_updated(GtkWidget *widget, GParamSpec *property, gpointer data); - static void search_occurrences_updated(GtkWidget *widget, GParamSpec *property, gpointer data); + sigc::connection renderer_activate_connection; - sigc::connection renderer_activate_connection; + bool multiple_cursors_signals_set = false; + bool multiple_cursors_use = false; + std::vector<std::pair<Glib::RefPtr<Gtk::TextBuffer::Mark>, int>> multiple_cursors_extra_cursors; + Glib::RefPtr<Gtk::TextBuffer::Mark> multiple_cursors_last_insert; + int multiple_cursors_erase_backward_length; + int multiple_cursors_erase_forward_length; - bool multiple_cursors_signals_set = false; - bool multiple_cursors_use = false; - std::vector<std::pair<Glib::RefPtr<Gtk::TextBuffer::Mark>, int>> multiple_cursors_extra_cursors; - Glib::RefPtr<Gtk::TextBuffer::Mark> multiple_cursors_last_insert; - int multiple_cursors_erase_backward_length; - int multiple_cursors_erase_forward_length; + bool on_key_press_event_multiple_cursors(GdkEventKey *key); + }; - bool on_key_press_event_multiple_cursors(GdkEventKey *key); + class GenericView : public View { + private: + class CompletionBuffer : public Gtk::TextBuffer { + public: + static Glib::RefPtr<CompletionBuffer> create() { + return Glib::RefPtr<CompletionBuffer>(new CompletionBuffer()); + } }; - class GenericView : public View { - private: - class CompletionBuffer : public Gtk::TextBuffer { - public: - static Glib::RefPtr<CompletionBuffer> create() { - return Glib::RefPtr<CompletionBuffer>(new CompletionBuffer()); - } - }; + public: + GenericView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); - public: - GenericView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); - - void parse_language_file(Glib::RefPtr<CompletionBuffer> &completion_buffer, bool &has_context_class, - const boost::property_tree::ptree &pt); - }; + void parse_language_file(Glib::RefPtr<CompletionBuffer> &completion_buffer, bool &has_context_class, + const boost::property_tree::ptree &pt); + }; } diff --git a/src/source_base.cc b/src/source_base.cc index 20778a5b..c768bf5f 100644 --- a/src/source_base.cc +++ b/src/source_base.cc @@ -6,400 +6,400 @@ #include <fstream> Source::BaseView::BaseView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language) - : Gsv::View(), file_path(file_path), language(language), status_diagnostics(0, 0, 0) { - load(true); - get_buffer()->place_cursor(get_buffer()->get_iter_at_offset(0)); + : Gsv::View(), file_path(file_path), language(language), status_diagnostics(0, 0, 0) { + load(true); + get_buffer()->place_cursor(get_buffer()->get_iter_at_offset(0)); - signal_focus_in_event().connect([this](GdkEventFocus *event) { - if (this->last_write_time != static_cast<std::time_t>(-1)) - check_last_write_time(); - return false; - }); + signal_focus_in_event().connect([this](GdkEventFocus *event) { + if (this->last_write_time != static_cast<std::time_t>(-1)) + check_last_write_time(); + return false; + }); - monitor_file(); + monitor_file(); } Source::BaseView::~BaseView() { - monitor_changed_connection.disconnect(); - delayed_monitor_changed_connection.disconnect(); + monitor_changed_connection.disconnect(); + delayed_monitor_changed_connection.disconnect(); } bool Source::BaseView::load(bool not_undoable_action) { - boost::system::error_code ec; - last_write_time = boost::filesystem::last_write_time(file_path, ec); - if (ec) - last_write_time = static_cast<std::time_t>(-1); - - disable_spellcheck = true; - if (not_undoable_action) - get_source_buffer()->begin_not_undoable_action(); - - class Guard { - public: - Source::BaseView *view; - bool not_undoable_action; - - ~Guard() { - if (not_undoable_action) - view->get_source_buffer()->end_not_undoable_action(); - view->disable_spellcheck = false; - } - }; - Guard guard{this, not_undoable_action}; - - if (language) { - std::ifstream input(file_path.string(), std::ofstream::binary); - if (input) { - std::stringstream ss; - ss << input.rdbuf(); - Glib::ustring ustr = ss.str(); - - bool valid = true; - Glib::ustring::iterator iter; - while (!ustr.validate(iter)) { - auto next_char_iter = iter; - next_char_iter++; - ustr.replace(iter, next_char_iter, "?"); - valid = false; - } - - if (!valid) - Terminal::get().print("Warning: " + file_path.string() + - " is not a valid UTF-8 file. Saving might corrupt the file.\n"); - - if (get_buffer()->size() == 0) - get_buffer()->insert_at_cursor(ustr); - else - replace_text(ustr.raw()); - } else - return false; - } else { - std::ifstream input(file_path.string(), std::ofstream::binary); - if (input) { - std::stringstream ss; - ss << input.rdbuf(); - Glib::ustring ustr = ss.str(); - - if (ustr.validate()) { - if (get_buffer()->size() == 0) - get_buffer()->insert_at_cursor(ustr); - else - replace_text(ustr.raw()); - } else { - Terminal::get().print("Error: " + file_path.string() + " is not a valid UTF-8 file.\n", true); - return false; - } - } else - return false; + boost::system::error_code ec; + last_write_time = boost::filesystem::last_write_time(file_path, ec); + if (ec) + last_write_time = static_cast<std::time_t>(-1); + + disable_spellcheck = true; + if (not_undoable_action) + get_source_buffer()->begin_not_undoable_action(); + + class Guard { + public: + Source::BaseView *view; + bool not_undoable_action; + + ~Guard() { + if (not_undoable_action) + view->get_source_buffer()->end_not_undoable_action(); + view->disable_spellcheck = false; } + }; + Guard guard{this, not_undoable_action}; + + if (language) { + std::ifstream input(file_path.string(), std::ofstream::binary); + if (input) { + std::stringstream ss; + ss << input.rdbuf(); + Glib::ustring ustr = ss.str(); + + bool valid = true; + Glib::ustring::iterator iter; + while (!ustr.validate(iter)) { + auto next_char_iter = iter; + next_char_iter++; + ustr.replace(iter, next_char_iter, "?"); + valid = false; + } - get_buffer()->set_modified(false); - return true; + if (!valid) + Terminal::get().print("Warning: " + file_path.string() + + " is not a valid UTF-8 file. Saving might corrupt the file.\n"); + + if (get_buffer()->size() == 0) + get_buffer()->insert_at_cursor(ustr); + else + replace_text(ustr.raw()); + } else + return false; + } else { + std::ifstream input(file_path.string(), std::ofstream::binary); + if (input) { + std::stringstream ss; + ss << input.rdbuf(); + Glib::ustring ustr = ss.str(); + + if (ustr.validate()) { + if (get_buffer()->size() == 0) + get_buffer()->insert_at_cursor(ustr); + else + replace_text(ustr.raw()); + } else { + Terminal::get().print("Error: " + file_path.string() + " is not a valid UTF-8 file.\n", true); + return false; + } + } else + return false; + } + + get_buffer()->set_modified(false); + return true; } void Source::BaseView::replace_text(const std::string &new_text) { - get_buffer()->begin_user_action(); + get_buffer()->begin_user_action(); - if (get_buffer()->size() == 0) { - get_buffer()->insert_at_cursor(new_text); - get_buffer()->end_user_action(); - return; - } else if (new_text.empty()) { - get_buffer()->set_text(new_text); - get_buffer()->end_user_action(); - return; - } + if (get_buffer()->size() == 0) { + get_buffer()->insert_at_cursor(new_text); + get_buffer()->end_user_action(); + return; + } else if (new_text.empty()) { + get_buffer()->set_text(new_text); + get_buffer()->end_user_action(); + return; + } - auto iter = get_buffer()->get_insert()->get_iter(); - int cursor_line_nr = iter.get_line(); - int cursor_line_offset = iter.ends_line() ? std::numeric_limits<int>::max() : iter.get_line_offset(); + auto iter = get_buffer()->get_insert()->get_iter(); + int cursor_line_nr = iter.get_line(); + int cursor_line_offset = iter.ends_line() ? std::numeric_limits<int>::max() : iter.get_line_offset(); - std::vector<std::pair<const char *, const char *>> new_lines; + std::vector<std::pair<const char *, const char *>> new_lines; - const char *line_start = new_text.c_str(); - for (size_t i = 0; i < new_text.size(); ++i) { - if (new_text[i] == '\n') { - new_lines.emplace_back(line_start, &new_text[i] + 1); - line_start = &new_text[i] + 1; - } + const char *line_start = new_text.c_str(); + for (size_t i = 0; i < new_text.size(); ++i) { + if (new_text[i] == '\n') { + new_lines.emplace_back(line_start, &new_text[i] + 1); + line_start = &new_text[i] + 1; } - if (new_text.empty() || new_text.back() != '\n') - new_lines.emplace_back(line_start, &new_text[new_text.size()]); - - try { - auto hunks = Git::Repository::Diff::get_hunks(get_buffer()->get_text().raw(), new_text); - - for (auto it = hunks.rbegin(); it != hunks.rend(); ++it) { - bool place_cursor = false; - Gtk::TextIter start; - if (it->old_lines.second != 0) { - start = get_buffer()->get_iter_at_line(it->old_lines.first - 1); - auto end = get_buffer()->get_iter_at_line(it->old_lines.first - 1 + it->old_lines.second); - - if (cursor_line_nr >= start.get_line() && cursor_line_nr < end.get_line()) { - if (it->new_lines.second != 0) { - place_cursor = true; - int line_diff = cursor_line_nr - start.get_line(); - cursor_line_nr += static_cast<int>(0.5 + - (static_cast<float>(line_diff) / it->old_lines.second) * - it->new_lines.second) - line_diff; - } - } - - get_buffer()->erase(start, end); - start = get_buffer()->get_iter_at_line(it->old_lines.first - 1); - } else - start = get_buffer()->get_iter_at_line(it->old_lines.first); - if (it->new_lines.second != 0) { - get_buffer()->insert(start, new_lines[it->new_lines.first - 1].first, - new_lines[it->new_lines.first - 1 + it->new_lines.second - 1].second); - if (place_cursor) - place_cursor_at_line_offset(cursor_line_nr, cursor_line_offset); - } + } + if (new_text.empty() || new_text.back() != '\n') + new_lines.emplace_back(line_start, &new_text[new_text.size()]); + + try { + auto hunks = Git::Repository::Diff::get_hunks(get_buffer()->get_text().raw(), new_text); + + for (auto it = hunks.rbegin(); it != hunks.rend(); ++it) { + bool place_cursor = false; + Gtk::TextIter start; + if (it->old_lines.second != 0) { + start = get_buffer()->get_iter_at_line(it->old_lines.first - 1); + auto end = get_buffer()->get_iter_at_line(it->old_lines.first - 1 + it->old_lines.second); + + if (cursor_line_nr >= start.get_line() && cursor_line_nr < end.get_line()) { + if (it->new_lines.second != 0) { + place_cursor = true; + int line_diff = cursor_line_nr - start.get_line(); + cursor_line_nr += static_cast<int>(0.5 + + (static_cast<float>(line_diff) / it->old_lines.second) * + it->new_lines.second) - line_diff; + } } + + get_buffer()->erase(start, end); + start = get_buffer()->get_iter_at_line(it->old_lines.first - 1); + } else + start = get_buffer()->get_iter_at_line(it->old_lines.first); + if (it->new_lines.second != 0) { + get_buffer()->insert(start, new_lines[it->new_lines.first - 1].first, + new_lines[it->new_lines.first - 1 + it->new_lines.second - 1].second); + if (place_cursor) + place_cursor_at_line_offset(cursor_line_nr, cursor_line_offset); + } } - catch (...) { - Terminal::get().print("Error: Could not replace text in buffer\n", true); - } + } + catch (...) { + Terminal::get().print("Error: Could not replace text in buffer\n", true); + } - get_buffer()->end_user_action(); + get_buffer()->end_user_action(); } void Source::BaseView::rename(const boost::filesystem::path &path) { - file_path = path; + file_path = path; - if (update_status_file_path) - update_status_file_path(this); - if (update_tab_label) - update_tab_label(this); + if (update_status_file_path) + update_status_file_path(this); + if (update_tab_label) + update_tab_label(this); } void Source::BaseView::monitor_file() { #ifdef __APPLE__ // TODO: Gio file monitor is bugged on MacOS - class Recursive { - public: - static void f(BaseView *view, std::time_t last_write_time_) { - view->delayed_monitor_changed_connection.disconnect(); - view->delayed_monitor_changed_connection=Glib::signal_timeout().connect([view, last_write_time_]() { - boost::system::error_code ec; - auto last_write_time=boost::filesystem::last_write_time(view->file_path, ec); - if(last_write_time!=last_write_time_) - view->check_last_write_time(last_write_time); - Recursive::f(view, last_write_time); + class Recursive { + public: + static void f(BaseView *view, std::time_t last_write_time_) { + view->delayed_monitor_changed_connection.disconnect(); + view->delayed_monitor_changed_connection=Glib::signal_timeout().connect([view, last_write_time_]() { + boost::system::error_code ec; + auto last_write_time=boost::filesystem::last_write_time(view->file_path, ec); + if(last_write_time!=last_write_time_) + view->check_last_write_time(last_write_time); + Recursive::f(view, last_write_time); + return false; + }, 1000); + } + }; + delayed_monitor_changed_connection.disconnect(); + if(this->last_write_time!=static_cast<std::time_t>(-1)) + Recursive::f(this, last_write_time); +#else + if (this->last_write_time != static_cast<std::time_t>(-1)) { + monitor = Gio::File::create_for_path(file_path.string())->monitor_file( + Gio::FileMonitorFlags::FILE_MONITOR_NONE); + monitor_changed_connection.disconnect(); + monitor_changed_connection = monitor->signal_changed().connect([this](const Glib::RefPtr<Gio::File> &file, + const Glib::RefPtr<Gio::File> &, + Gio::FileMonitorEvent monitor_event) { + if (monitor_event != Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { + delayed_monitor_changed_connection.disconnect(); + delayed_monitor_changed_connection = Glib::signal_timeout().connect([this]() { + check_last_write_time(); return false; - }, 1000); + }, 500); } - }; - delayed_monitor_changed_connection.disconnect(); - if(this->last_write_time!=static_cast<std::time_t>(-1)) - Recursive::f(this, last_write_time); -#else - if (this->last_write_time != static_cast<std::time_t>(-1)) { - monitor = Gio::File::create_for_path(file_path.string())->monitor_file( - Gio::FileMonitorFlags::FILE_MONITOR_NONE); - monitor_changed_connection.disconnect(); - monitor_changed_connection = monitor->signal_changed().connect([this](const Glib::RefPtr<Gio::File> &file, - const Glib::RefPtr<Gio::File> &, - Gio::FileMonitorEvent monitor_event) { - if (monitor_event != Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { - delayed_monitor_changed_connection.disconnect(); - delayed_monitor_changed_connection = Glib::signal_timeout().connect([this]() { - check_last_write_time(); - return false; - }, 500); - } - }); - } + }); + } #endif } void Source::BaseView::check_last_write_time(std::time_t last_write_time_) { - if (this->last_write_time == static_cast<std::time_t>(-1)) - return; + if (this->last_write_time == static_cast<std::time_t>(-1)) + return; - if (Config::get().source.auto_reload_changed_files && !get_buffer()->get_modified()) { - boost::system::error_code ec; - auto last_write_time = last_write_time_ != static_cast<std::time_t>(-1) ? last_write_time_ - : boost::filesystem::last_write_time( - file_path, ec); - if (!ec && last_write_time != this->last_write_time) { - if (load()) - return; - } - } else if (has_focus()) { - boost::system::error_code ec; - auto last_write_time = last_write_time_ != static_cast<std::time_t>(-1) ? last_write_time_ - : boost::filesystem::last_write_time( - file_path, ec); - if (!ec && last_write_time != this->last_write_time) - Info::get().print("Caution: " + file_path.filename().string() + " was changed outside of juCi++"); + if (Config::get().source.auto_reload_changed_files && !get_buffer()->get_modified()) { + boost::system::error_code ec; + auto last_write_time = last_write_time_ != static_cast<std::time_t>(-1) ? last_write_time_ + : boost::filesystem::last_write_time( + file_path, ec); + if (!ec && last_write_time != this->last_write_time) { + if (load()) + return; } + } else if (has_focus()) { + boost::system::error_code ec; + auto last_write_time = last_write_time_ != static_cast<std::time_t>(-1) ? last_write_time_ + : boost::filesystem::last_write_time( + file_path, ec); + if (!ec && last_write_time != this->last_write_time) + Info::get().print("Caution: " + file_path.filename().string() + " was changed outside of juCi++"); + } } Gtk::TextIter Source::BaseView::get_iter_at_line_pos(int line, int pos) { - return get_iter_at_line_index(line, pos); + return get_iter_at_line_index(line, pos); } Gtk::TextIter Source::BaseView::get_iter_at_line_offset(int line, int offset) { - line = std::min(line, get_buffer()->get_line_count() - 1); - if (line < 0) - line = 0; - auto iter = get_iter_at_line_end(line); - offset = std::min(offset, iter.get_line_offset()); - if (offset < 0) - offset = 0; - return get_buffer()->get_iter_at_line_offset(line, offset); + line = std::min(line, get_buffer()->get_line_count() - 1); + if (line < 0) + line = 0; + auto iter = get_iter_at_line_end(line); + offset = std::min(offset, iter.get_line_offset()); + if (offset < 0) + offset = 0; + return get_buffer()->get_iter_at_line_offset(line, offset); } Gtk::TextIter Source::BaseView::get_iter_at_line_index(int line, int index) { - line = std::min(line, get_buffer()->get_line_count() - 1); - if (line < 0) - line = 0; - auto iter = get_iter_at_line_end(line); - index = std::min(index, iter.get_line_index()); - if (index < 0) - index = 0; - return get_buffer()->get_iter_at_line_index(line, index); + line = std::min(line, get_buffer()->get_line_count() - 1); + if (line < 0) + line = 0; + auto iter = get_iter_at_line_end(line); + index = std::min(index, iter.get_line_index()); + if (index < 0) + index = 0; + return get_buffer()->get_iter_at_line_index(line, index); } Gtk::TextIter Source::BaseView::get_iter_at_line_end(int line_nr) { - if (line_nr >= get_buffer()->get_line_count()) - return get_buffer()->end(); - else if (line_nr + 1 < get_buffer()->get_line_count()) { - auto iter = get_buffer()->get_iter_at_line(line_nr + 1); - iter.backward_char(); - if (!iter.ends_line()) // for CR+LF - iter.backward_char(); - return iter; - } else { - auto iter = get_buffer()->get_iter_at_line(line_nr); - while (!iter.ends_line() && iter.forward_char()) {} - return iter; - } + if (line_nr >= get_buffer()->get_line_count()) + return get_buffer()->end(); + else if (line_nr + 1 < get_buffer()->get_line_count()) { + auto iter = get_buffer()->get_iter_at_line(line_nr + 1); + iter.backward_char(); + if (!iter.ends_line()) // for CR+LF + iter.backward_char(); + return iter; + } else { + auto iter = get_buffer()->get_iter_at_line(line_nr); + while (!iter.ends_line() && iter.forward_char()) {} + return iter; + } } Gtk::TextIter Source::BaseView::get_iter_for_dialog() { - auto iter = get_buffer()->get_insert()->get_iter(); - Gdk::Rectangle visible_rect; - get_visible_rect(visible_rect); - Gdk::Rectangle iter_rect; + auto iter = get_buffer()->get_insert()->get_iter(); + Gdk::Rectangle visible_rect; + get_visible_rect(visible_rect); + Gdk::Rectangle iter_rect; + get_iter_location(iter, iter_rect); + iter_rect.set_width(1); + if (iter.get_line_offset() >= 80) { + get_iter_at_location(iter, visible_rect.get_x(), iter_rect.get_y()); get_iter_location(iter, iter_rect); - iter_rect.set_width(1); - if (iter.get_line_offset() >= 80) { - get_iter_at_location(iter, visible_rect.get_x(), iter_rect.get_y()); - get_iter_location(iter, iter_rect); - } - if (!visible_rect.intersects(iter_rect)) - get_iter_at_location(iter, visible_rect.get_x(), visible_rect.get_y() + visible_rect.get_height() / 3); - return iter; + } + if (!visible_rect.intersects(iter_rect)) + get_iter_at_location(iter, visible_rect.get_x(), visible_rect.get_y() + visible_rect.get_height() / 3); + return iter; } void Source::BaseView::place_cursor_at_line_pos(int line, int pos) { - get_buffer()->place_cursor(get_iter_at_line_pos(line, pos)); + get_buffer()->place_cursor(get_iter_at_line_pos(line, pos)); } void Source::BaseView::place_cursor_at_line_offset(int line, int offset) { - get_buffer()->place_cursor(get_iter_at_line_offset(line, offset)); + get_buffer()->place_cursor(get_iter_at_line_offset(line, offset)); } void Source::BaseView::place_cursor_at_line_index(int line, int index) { - get_buffer()->place_cursor(get_iter_at_line_index(line, index)); + get_buffer()->place_cursor(get_iter_at_line_index(line, index)); } Gtk::TextIter Source::BaseView::get_smart_home_iter(const Gtk::TextIter &iter) { - auto start_line_iter = get_buffer()->get_iter_at_line(iter.get_line()); - auto start_sentence_iter = start_line_iter; - while (!start_sentence_iter.ends_line() && - (*start_sentence_iter == ' ' || *start_sentence_iter == '\t') && - start_sentence_iter.forward_char()) {} - - if (iter > start_sentence_iter || iter == start_line_iter) - return start_sentence_iter; - else - return start_line_iter; + auto start_line_iter = get_buffer()->get_iter_at_line(iter.get_line()); + auto start_sentence_iter = start_line_iter; + while (!start_sentence_iter.ends_line() && + (*start_sentence_iter == ' ' || *start_sentence_iter == '\t') && + start_sentence_iter.forward_char()) {} + + if (iter > start_sentence_iter || iter == start_line_iter) + return start_sentence_iter; + else + return start_line_iter; } Gtk::TextIter Source::BaseView::get_smart_end_iter(const Gtk::TextIter &iter) { - auto end_line_iter = get_iter_at_line_end(iter.get_line()); - auto end_sentence_iter = end_line_iter; - while (!end_sentence_iter.starts_line() && - (*end_sentence_iter == ' ' || *end_sentence_iter == '\t' || end_sentence_iter.ends_line()) && - end_sentence_iter.backward_char()) {} - if (!end_sentence_iter.ends_line() && *end_sentence_iter != ' ' && *end_sentence_iter != '\t') - end_sentence_iter.forward_char(); - - if (iter == end_line_iter) - return end_sentence_iter; - else - return end_line_iter; + auto end_line_iter = get_iter_at_line_end(iter.get_line()); + auto end_sentence_iter = end_line_iter; + while (!end_sentence_iter.starts_line() && + (*end_sentence_iter == ' ' || *end_sentence_iter == '\t' || end_sentence_iter.ends_line()) && + end_sentence_iter.backward_char()) {} + if (!end_sentence_iter.ends_line() && *end_sentence_iter != ' ' && *end_sentence_iter != '\t') + end_sentence_iter.forward_char(); + + if (iter == end_line_iter) + return end_sentence_iter; + else + return end_line_iter; } std::string Source::BaseView::get_line(const Gtk::TextIter &iter) { - auto line_start_it = get_buffer()->get_iter_at_line(iter.get_line()); - auto line_end_it = get_iter_at_line_end(iter.get_line()); - std::string line(get_buffer()->get_text(line_start_it, line_end_it)); - return line; + auto line_start_it = get_buffer()->get_iter_at_line(iter.get_line()); + auto line_end_it = get_iter_at_line_end(iter.get_line()); + std::string line(get_buffer()->get_text(line_start_it, line_end_it)); + return line; } std::string Source::BaseView::get_line(Glib::RefPtr<Gtk::TextBuffer::Mark> mark) { - return get_line(mark->get_iter()); + return get_line(mark->get_iter()); } std::string Source::BaseView::get_line(int line_nr) { - return get_line(get_buffer()->get_iter_at_line(line_nr)); + return get_line(get_buffer()->get_iter_at_line(line_nr)); } std::string Source::BaseView::get_line() { - return get_line(get_buffer()->get_insert()); + return get_line(get_buffer()->get_insert()); } std::string Source::BaseView::get_line_before(const Gtk::TextIter &iter) { - auto line_it = get_buffer()->get_iter_at_line(iter.get_line()); - std::string line(get_buffer()->get_text(line_it, iter)); - return line; + auto line_it = get_buffer()->get_iter_at_line(iter.get_line()); + std::string line(get_buffer()->get_text(line_it, iter)); + return line; } std::string Source::BaseView::get_line_before(Glib::RefPtr<Gtk::TextBuffer::Mark> mark) { - return get_line_before(mark->get_iter()); + return get_line_before(mark->get_iter()); } std::string Source::BaseView::get_line_before() { - return get_line_before(get_buffer()->get_insert()); + return get_line_before(get_buffer()->get_insert()); } Gtk::TextIter Source::BaseView::get_tabs_end_iter(const Gtk::TextIter &iter) { - return get_tabs_end_iter(iter.get_line()); + return get_tabs_end_iter(iter.get_line()); } Gtk::TextIter Source::BaseView::get_tabs_end_iter(Glib::RefPtr<Gtk::TextBuffer::Mark> mark) { - return get_tabs_end_iter(mark->get_iter()); + return get_tabs_end_iter(mark->get_iter()); } Gtk::TextIter Source::BaseView::get_tabs_end_iter(int line_nr) { - auto sentence_iter = get_buffer()->get_iter_at_line(line_nr); - while ((*sentence_iter == ' ' || *sentence_iter == '\t') && !sentence_iter.ends_line() && - sentence_iter.forward_char()) {} - return sentence_iter; + auto sentence_iter = get_buffer()->get_iter_at_line(line_nr); + while ((*sentence_iter == ' ' || *sentence_iter == '\t') && !sentence_iter.ends_line() && + sentence_iter.forward_char()) {} + return sentence_iter; } Gtk::TextIter Source::BaseView::get_tabs_end_iter() { - return get_tabs_end_iter(get_buffer()->get_insert()); + return get_tabs_end_iter(get_buffer()->get_insert()); } void Source::BaseView::place_cursor_at_next_diagnostic() { - auto insert_offset = get_buffer()->get_insert()->get_iter().get_offset(); - for (auto offset: diagnostic_offsets) { - if (offset > insert_offset) { - get_buffer()->place_cursor(get_buffer()->get_iter_at_offset(offset)); - scroll_to(get_buffer()->get_insert(), 0.0, 1.0, 0.5); - return; - } - } - if (diagnostic_offsets.size() == 0) - Info::get().print("No diagnostics found in current buffer"); - else { - auto iter = get_buffer()->get_iter_at_offset(*diagnostic_offsets.begin()); - get_buffer()->place_cursor(iter); - scroll_to(get_buffer()->get_insert(), 0.0, 1.0, 0.5); + auto insert_offset = get_buffer()->get_insert()->get_iter().get_offset(); + for (auto offset: diagnostic_offsets) { + if (offset > insert_offset) { + get_buffer()->place_cursor(get_buffer()->get_iter_at_offset(offset)); + scroll_to(get_buffer()->get_insert(), 0.0, 1.0, 0.5); + return; } + } + if (diagnostic_offsets.size() == 0) + Info::get().print("No diagnostics found in current buffer"); + else { + auto iter = get_buffer()->get_iter_at_offset(*diagnostic_offsets.begin()); + get_buffer()->place_cursor(iter); + scroll_to(get_buffer()->get_insert(), 0.0, 1.0, 0.5); + } } diff --git a/src/source_base.h b/src/source_base.h index 34d4ca90..a13ee67f 100644 --- a/src/source_base.h +++ b/src/source_base.h @@ -6,113 +6,113 @@ #include <boost/filesystem.hpp> namespace Source { - class BaseView : public Gsv::View { - public: - BaseView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); + class BaseView : public Gsv::View { + public: + BaseView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); - ~BaseView(); + ~BaseView(); - boost::filesystem::path file_path; + boost::filesystem::path file_path; - Glib::RefPtr<Gsv::Language> language; + Glib::RefPtr<Gsv::Language> language; - bool load(bool not_undoable_action = false); + bool load(bool not_undoable_action = false); - /// Set new text more optimally and without unnecessary scrolling - void replace_text(const std::string &new_text); + /// Set new text more optimally and without unnecessary scrolling + void replace_text(const std::string &new_text); - virtual void rename(const boost::filesystem::path &path); + virtual void rename(const boost::filesystem::path &path); - virtual bool save() = 0; + virtual bool save() = 0; - Glib::RefPtr<Gio::FileMonitor> monitor; - sigc::connection monitor_changed_connection; - sigc::connection delayed_monitor_changed_connection; + Glib::RefPtr<Gio::FileMonitor> monitor; + sigc::connection monitor_changed_connection; + sigc::connection delayed_monitor_changed_connection; - virtual void configure() = 0; + virtual void configure() = 0; - virtual void hide_tooltips() = 0; + virtual void hide_tooltips() = 0; - virtual void hide_dialogs() = 0; + virtual void hide_dialogs() = 0; - std::function<void(BaseView *view, bool center, bool show_tooltips)> scroll_to_cursor_delayed = []( - BaseView *view, bool center, bool show_tooltips) {}; + std::function<void(BaseView *view, bool center, bool show_tooltips)> scroll_to_cursor_delayed = []( + BaseView *view, bool center, bool show_tooltips) {}; - /// Safely returns iter given line and an offset using either byte index or character offset. Defaults to using byte index. - virtual Gtk::TextIter get_iter_at_line_pos(int line, int pos); + /// Safely returns iter given line and an offset using either byte index or character offset. Defaults to using byte index. + virtual Gtk::TextIter get_iter_at_line_pos(int line, int pos); - /// Safely returns iter given line and character offset - Gtk::TextIter get_iter_at_line_offset(int line, int offset); + /// Safely returns iter given line and character offset + Gtk::TextIter get_iter_at_line_offset(int line, int offset); - /// Safely returns iter given line and byte index - Gtk::TextIter get_iter_at_line_index(int line, int index); + /// Safely returns iter given line and byte index + Gtk::TextIter get_iter_at_line_index(int line, int index); - Gtk::TextIter get_iter_at_line_end(int line_nr); + Gtk::TextIter get_iter_at_line_end(int line_nr); - Gtk::TextIter get_iter_for_dialog(); + Gtk::TextIter get_iter_for_dialog(); - /// Safely places cursor at line using get_iter_at_line_pos. - void place_cursor_at_line_pos(int line, int pos); + /// Safely places cursor at line using get_iter_at_line_pos. + void place_cursor_at_line_pos(int line, int pos); - /// Safely places cursor at line offset - void place_cursor_at_line_offset(int line, int offset); + /// Safely places cursor at line offset + void place_cursor_at_line_offset(int line, int offset); - /// Safely places cursor at line index - void place_cursor_at_line_index(int line, int index); + /// Safely places cursor at line index + void place_cursor_at_line_index(int line, int index); - protected: - std::time_t last_write_time; + protected: + std::time_t last_write_time; - void monitor_file(); + void monitor_file(); - void check_last_write_time(std::time_t last_write_time_ = static_cast<std::time_t>(-1)); + void check_last_write_time(std::time_t last_write_time_ = static_cast<std::time_t>(-1)); - /// Move iter to line start. Depending on iter position, before or after indentation. - /// Works with wrapped lines. - Gtk::TextIter get_smart_home_iter(const Gtk::TextIter &iter); + /// Move iter to line start. Depending on iter position, before or after indentation. + /// Works with wrapped lines. + Gtk::TextIter get_smart_home_iter(const Gtk::TextIter &iter); - /// Move iter to line end. Depending on iter position, before or after indentation. - /// Works with wrapped lines. - /// Note that smart end goes FIRST to end of line to avoid hiding empty chars after expressions. - Gtk::TextIter get_smart_end_iter(const Gtk::TextIter &iter); + /// Move iter to line end. Depending on iter position, before or after indentation. + /// Works with wrapped lines. + /// Note that smart end goes FIRST to end of line to avoid hiding empty chars after expressions. + Gtk::TextIter get_smart_end_iter(const Gtk::TextIter &iter); - std::string get_line(const Gtk::TextIter &iter); + std::string get_line(const Gtk::TextIter &iter); - std::string get_line(Glib::RefPtr<Gtk::TextBuffer::Mark> mark); + std::string get_line(Glib::RefPtr<Gtk::TextBuffer::Mark> mark); - std::string get_line(int line_nr); + std::string get_line(int line_nr); - std::string get_line(); + std::string get_line(); - std::string get_line_before(const Gtk::TextIter &iter); + std::string get_line_before(const Gtk::TextIter &iter); - std::string get_line_before(Glib::RefPtr<Gtk::TextBuffer::Mark> mark); + std::string get_line_before(Glib::RefPtr<Gtk::TextBuffer::Mark> mark); - std::string get_line_before(); + std::string get_line_before(); - Gtk::TextIter get_tabs_end_iter(const Gtk::TextIter &iter); + Gtk::TextIter get_tabs_end_iter(const Gtk::TextIter &iter); - Gtk::TextIter get_tabs_end_iter(Glib::RefPtr<Gtk::TextBuffer::Mark> mark); + Gtk::TextIter get_tabs_end_iter(Glib::RefPtr<Gtk::TextBuffer::Mark> mark); - Gtk::TextIter get_tabs_end_iter(int line_nr); + Gtk::TextIter get_tabs_end_iter(int line_nr); - Gtk::TextIter get_tabs_end_iter(); + Gtk::TextIter get_tabs_end_iter(); - std::set<int> diagnostic_offsets; + std::set<int> diagnostic_offsets; - void place_cursor_at_next_diagnostic(); + void place_cursor_at_next_diagnostic(); - public: - std::function<void(BaseView *view)> update_tab_label; - std::function<void(BaseView *view)> update_status_location; - std::function<void(BaseView *view)> update_status_file_path; - std::function<void(BaseView *view)> update_status_diagnostics; - std::function<void(BaseView *view)> update_status_state; - std::tuple<size_t, size_t, size_t> status_diagnostics; - std::string status_state; - std::function<void(BaseView *view)> update_status_branch; - std::string status_branch; + public: + std::function<void(BaseView *view)> update_tab_label; + std::function<void(BaseView *view)> update_status_location; + std::function<void(BaseView *view)> update_status_file_path; + std::function<void(BaseView *view)> update_status_diagnostics; + std::function<void(BaseView *view)> update_status_state; + std::tuple<size_t, size_t, size_t> status_diagnostics; + std::string status_state; + std::function<void(BaseView *view)> update_status_branch; + std::string status_branch; - bool disable_spellcheck = false; - }; + bool disable_spellcheck = false; + }; } diff --git a/src/source_clang.cc b/src/source_clang.cc index 68c39bbe..d52b0b7a 100644 --- a/src/source_clang.cc +++ b/src/source_clang.cc @@ -19,1899 +19,1899 @@ clangmm::Index Source::ClangViewParse::clang_index(0, 0); Source::ClangViewParse::ClangViewParse(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language) : - BaseView(file_path, language), Source::View(file_path, language) { - Usages::Clang::erase_cache(file_path); + BaseView(file_path, language), Source::View(file_path, language) { + Usages::Clang::erase_cache(file_path); - auto tag_table = get_buffer()->get_tag_table(); - for (auto &item : clang_types()) { - if (!tag_table->lookup(item.second)) { - get_buffer()->create_tag(item.second); - } + auto tag_table = get_buffer()->get_tag_table(); + for (auto &item : clang_types()) { + if (!tag_table->lookup(item.second)) { + get_buffer()->create_tag(item.second); } - configure(); + } + configure(); - if (get_buffer()->size() == 0 && (language->get_id() == "chdr" || language->get_id() == "cpphdr")) { - disable_spellcheck = true; - get_buffer()->insert_at_cursor("#pragma once\n"); - disable_spellcheck = false; - Info::get().print("Added \"#pragma once\" to empty C/C++ header file"); - } + if (get_buffer()->size() == 0 && (language->get_id() == "chdr" || language->get_id() == "cpphdr")) { + disable_spellcheck = true; + get_buffer()->insert_at_cursor("#pragma once\n"); + disable_spellcheck = false; + Info::get().print("Added \"#pragma once\" to empty C/C++ header file"); + } - parse_initialize(); + parse_initialize(); - get_buffer()->signal_changed().connect([this]() { - soft_reparse(true); - }); + get_buffer()->signal_changed().connect([this]() { + soft_reparse(true); + }); } bool Source::ClangViewParse::save() { - if (!Source::View::save()) - return false; + if (!Source::View::save()) + return false; - if (language->get_id() == "chdr" || language->get_id() == "cpphdr") { - for (auto &view: views) { - if (auto clang_view = dynamic_cast<Source::ClangView *>(view)) { - if (this != clang_view) - clang_view->soft_reparse_needed = true; - } - } + if (language->get_id() == "chdr" || language->get_id() == "cpphdr") { + for (auto &view: views) { + if (auto clang_view = dynamic_cast<Source::ClangView *>(view)) { + if (this != clang_view) + clang_view->soft_reparse_needed = true; + } } - return true; + } + return true; } void Source::ClangViewParse::configure() { - Source::View::configure(); - - auto scheme = get_source_buffer()->get_style_scheme(); - auto tag_table = get_buffer()->get_tag_table(); - for (auto &item : clang_types()) { - auto tag = get_buffer()->get_tag_table()->lookup(item.second); - if (tag) { - auto style = scheme->get_style(item.second); - if (style) { - if (style->property_foreground_set()) - tag->property_foreground() = style->property_foreground(); - if (style->property_background_set()) - tag->property_background() = style->property_background(); - if (style->property_strikethrough_set()) - tag->property_strikethrough() = style->property_strikethrough(); - // // if (style->property_bold_set()) tag->property_weight() = style->property_bold(); - // // if (style->property_italic_set()) tag->property_italic() = style->property_italic(); - // // if (style->property_line_background_set()) tag->property_line_background() = style->property_line_background(); - // // if (style->property_underline_set()) tag->property_underline() = style->property_underline(); - } - } + Source::View::configure(); + + auto scheme = get_source_buffer()->get_style_scheme(); + auto tag_table = get_buffer()->get_tag_table(); + for (auto &item : clang_types()) { + auto tag = get_buffer()->get_tag_table()->lookup(item.second); + if (tag) { + auto style = scheme->get_style(item.second); + if (style) { + if (style->property_foreground_set()) + tag->property_foreground() = style->property_foreground(); + if (style->property_background_set()) + tag->property_background() = style->property_background(); + if (style->property_strikethrough_set()) + tag->property_strikethrough() = style->property_strikethrough(); + // // if (style->property_bold_set()) tag->property_weight() = style->property_bold(); + // // if (style->property_italic_set()) tag->property_italic() = style->property_italic(); + // // if (style->property_line_background_set()) tag->property_line_background() = style->property_line_background(); + // // if (style->property_underline_set()) tag->property_underline() = style->property_underline(); + } } + } } void Source::ClangViewParse::parse_initialize() { - hide_tooltips(); - parsed = false; - if (parse_thread.joinable()) - parse_thread.join(); - parse_state = ParseState::PROCESSING; - parse_process_state = ParseProcessState::STARTING; - - auto buffer = get_buffer()->get_text(); - //Remove includes for first parse for initial syntax highlighting - std::size_t pos = 0; - while ((pos = buffer.find("#include", pos)) != std::string::npos) { - auto start_pos = pos; - pos = buffer.find('\n', pos + 8); - if (pos == std::string::npos) - break; - if (start_pos == 0 || buffer[start_pos - 1] == '\n') { - buffer.replace(start_pos, pos - start_pos, pos - start_pos, ' '); - } - pos++; + hide_tooltips(); + parsed = false; + if (parse_thread.joinable()) + parse_thread.join(); + parse_state = ParseState::PROCESSING; + parse_process_state = ParseProcessState::STARTING; + + auto buffer = get_buffer()->get_text(); + //Remove includes for first parse for initial syntax highlighting + std::size_t pos = 0; + while ((pos = buffer.find("#include", pos)) != std::string::npos) { + auto start_pos = pos; + pos = buffer.find('\n', pos + 8); + if (pos == std::string::npos) + break; + if (start_pos == 0 || buffer[start_pos - 1] == '\n') { + buffer.replace(start_pos, pos - start_pos, pos - start_pos, ' '); } - auto &buffer_raw = const_cast<std::string &>(buffer.raw()); - if (language && (language->get_id() == "chdr" || language->get_id() == "cpphdr")) - clangmm::remove_include_guard(buffer_raw); - - auto build = Project::Build::create(file_path); - if (build->project_path.empty()) - Info::get().print(file_path.filename().string() + ": could not find a supported build system"); - build->update_default(); - auto arguments = CompileCommands::get_arguments(build->get_default_path(), file_path); - clang_tu = std::make_unique<clangmm::TranslationUnit>(clang_index, file_path.string(), arguments, buffer_raw); - clang_tokens = clang_tu->get_tokens(); - clang_tokens_offsets.clear(); - clang_tokens_offsets.reserve(clang_tokens->size()); - for (auto &token: *clang_tokens) - clang_tokens_offsets.emplace_back(token.get_source_range().get_offsets()); - update_syntax(); - - status_state = "parsing..."; - if (update_status_state) - update_status_state(this); - parse_thread = std::thread([this]() { - while (true) { - while (parse_state == ParseState::PROCESSING && parse_process_state != ParseProcessState::STARTING && - parse_process_state != ParseProcessState::PROCESSING) - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - if (parse_state != ParseState::PROCESSING) - break; - auto expected = ParseProcessState::STARTING; - std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock); - if (parse_process_state.compare_exchange_strong(expected, ParseProcessState::PREPROCESSING)) { - dispatcher.post([this] { - auto expected = ParseProcessState::PREPROCESSING; - std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock); - if (parse_lock.try_lock()) { - if (parse_process_state.compare_exchange_strong(expected, ParseProcessState::PROCESSING)) - parse_thread_buffer = get_buffer()->get_text(); - parse_lock.unlock(); - } else - parse_process_state.compare_exchange_strong(expected, ParseProcessState::STARTING); - }); - } else if (parse_process_state == ParseProcessState::PROCESSING && parse_lock.try_lock()) { - auto &parse_thread_buffer_raw = const_cast<std::string &>(parse_thread_buffer.raw()); - if (this->language && (this->language->get_id() == "chdr" || this->language->get_id() == "cpphdr")) - clangmm::remove_include_guard(parse_thread_buffer_raw); - auto status = clang_tu->reparse(parse_thread_buffer_raw); - if (status == 0) { - auto expected = ParseProcessState::PROCESSING; - if (parse_process_state.compare_exchange_strong(expected, ParseProcessState::POSTPROCESSING)) { - clang_tokens = clang_tu->get_tokens(); - clang_tokens_offsets.clear(); - clang_tokens_offsets.reserve(clang_tokens->size()); - for (auto &token: *clang_tokens) - clang_tokens_offsets.emplace_back(token.get_source_range().get_offsets()); - clang_diagnostics = clang_tu->get_diagnostics(); - parse_lock.unlock(); - dispatcher.post([this] { - std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock); - if (parse_lock.try_lock()) { - auto expected = ParseProcessState::POSTPROCESSING; - if (parse_process_state.compare_exchange_strong(expected, ParseProcessState::IDLE)) { - update_syntax(); - update_diagnostics(); - parsed = true; - status_state = ""; - if (update_status_state) - update_status_state(this); - } - parse_lock.unlock(); - } - }); - } else - parse_lock.unlock(); - } else { - parse_state = ParseState::STOP; - parse_lock.unlock(); - dispatcher.post([this] { - Terminal::get().print("Error: failed to reparse " + this->file_path.string() + ".\n", true); - status_state = ""; - if (update_status_state) - update_status_state(this); - status_diagnostics = std::make_tuple(0, 0, 0); - if (update_status_diagnostics) - update_status_diagnostics(this); - }); + pos++; + } + auto &buffer_raw = const_cast<std::string &>(buffer.raw()); + if (language && (language->get_id() == "chdr" || language->get_id() == "cpphdr")) + clangmm::remove_include_guard(buffer_raw); + + auto build = Project::Build::create(file_path); + if (build->project_path.empty()) + Info::get().print(file_path.filename().string() + ": could not find a supported build system"); + build->update_default(); + auto arguments = CompileCommands::get_arguments(build->get_default_path(), file_path); + clang_tu = std::make_unique<clangmm::TranslationUnit>(clang_index, file_path.string(), arguments, buffer_raw); + clang_tokens = clang_tu->get_tokens(); + clang_tokens_offsets.clear(); + clang_tokens_offsets.reserve(clang_tokens->size()); + for (auto &token: *clang_tokens) + clang_tokens_offsets.emplace_back(token.get_source_range().get_offsets()); + update_syntax(); + + status_state = "parsing..."; + if (update_status_state) + update_status_state(this); + parse_thread = std::thread([this]() { + while (true) { + while (parse_state == ParseState::PROCESSING && parse_process_state != ParseProcessState::STARTING && + parse_process_state != ParseProcessState::PROCESSING) + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + if (parse_state != ParseState::PROCESSING) + break; + auto expected = ParseProcessState::STARTING; + std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock); + if (parse_process_state.compare_exchange_strong(expected, ParseProcessState::PREPROCESSING)) { + dispatcher.post([this] { + auto expected = ParseProcessState::PREPROCESSING; + std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock); + if (parse_lock.try_lock()) { + if (parse_process_state.compare_exchange_strong(expected, ParseProcessState::PROCESSING)) + parse_thread_buffer = get_buffer()->get_text(); + parse_lock.unlock(); + } else + parse_process_state.compare_exchange_strong(expected, ParseProcessState::STARTING); + }); + } else if (parse_process_state == ParseProcessState::PROCESSING && parse_lock.try_lock()) { + auto &parse_thread_buffer_raw = const_cast<std::string &>(parse_thread_buffer.raw()); + if (this->language && (this->language->get_id() == "chdr" || this->language->get_id() == "cpphdr")) + clangmm::remove_include_guard(parse_thread_buffer_raw); + auto status = clang_tu->reparse(parse_thread_buffer_raw); + if (status == 0) { + auto expected = ParseProcessState::PROCESSING; + if (parse_process_state.compare_exchange_strong(expected, ParseProcessState::POSTPROCESSING)) { + clang_tokens = clang_tu->get_tokens(); + clang_tokens_offsets.clear(); + clang_tokens_offsets.reserve(clang_tokens->size()); + for (auto &token: *clang_tokens) + clang_tokens_offsets.emplace_back(token.get_source_range().get_offsets()); + clang_diagnostics = clang_tu->get_diagnostics(); + parse_lock.unlock(); + dispatcher.post([this] { + std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock); + if (parse_lock.try_lock()) { + auto expected = ParseProcessState::POSTPROCESSING; + if (parse_process_state.compare_exchange_strong(expected, ParseProcessState::IDLE)) { + update_syntax(); + update_diagnostics(); + parsed = true; + status_state = ""; + if (update_status_state) + update_status_state(this); } - } + parse_lock.unlock(); + } + }); + } else + parse_lock.unlock(); + } else { + parse_state = ParseState::STOP; + parse_lock.unlock(); + dispatcher.post([this] { + Terminal::get().print("Error: failed to reparse " + this->file_path.string() + ".\n", true); + status_state = ""; + if (update_status_state) + update_status_state(this); + status_diagnostics = std::make_tuple(0, 0, 0); + if (update_status_diagnostics) + update_status_diagnostics(this); + }); } - }); + } + } + }); } void Source::ClangViewParse::soft_reparse(bool delayed) { - soft_reparse_needed = false; + soft_reparse_needed = false; + parsed = false; + if (parse_state != ParseState::PROCESSING) + return; + parse_process_state = ParseProcessState::IDLE; + delayed_reparse_connection.disconnect(); + delayed_reparse_connection = Glib::signal_timeout().connect([this]() { parsed = false; - if (parse_state != ParseState::PROCESSING) - return; - parse_process_state = ParseProcessState::IDLE; - delayed_reparse_connection.disconnect(); - delayed_reparse_connection = Glib::signal_timeout().connect([this]() { - parsed = false; - auto expected = ParseProcessState::IDLE; - if (parse_process_state.compare_exchange_strong(expected, ParseProcessState::STARTING)) { - status_state = "parsing..."; - if (update_status_state) - update_status_state(this); - } - return false; - }, delayed ? 1000 : 0); + auto expected = ParseProcessState::IDLE; + if (parse_process_state.compare_exchange_strong(expected, ParseProcessState::STARTING)) { + status_state = "parsing..."; + if (update_status_state) + update_status_state(this); + } + return false; + }, delayed ? 1000 : 0); } const std::unordered_map<int, std::string> &Source::ClangViewParse::clang_types() { - static std::unordered_map<int, std::string> types{ - {8, "def:function"}, - {21, "def:function"}, - {22, "def:identifier"}, - {24, "def:function"}, - {25, "def:function"}, - {43, "def:type"}, - {44, "def:type"}, - {45, "def:type"}, - {46, "def:identifier"}, - {109, "def:string"}, - {702, "def:statement"}, - {705, "def:comment"} - }; - return types; + static std::unordered_map<int, std::string> types{ + {8, "def:function"}, + {21, "def:function"}, + {22, "def:identifier"}, + {24, "def:function"}, + {25, "def:function"}, + {43, "def:type"}, + {44, "def:type"}, + {45, "def:type"}, + {46, "def:identifier"}, + {109, "def:string"}, + {702, "def:statement"}, + {705, "def:comment"} + }; + return types; } void Source::ClangViewParse::update_syntax() { - auto buffer = get_buffer(); - const auto apply_tag = [this, buffer](const std::pair<clangmm::Offset, clangmm::Offset> &offsets, int type) { - auto type_it = clang_types().find(type); - if (type_it != clang_types().end()) { - last_syntax_tags.emplace(type_it->second); - Gtk::TextIter begin_iter = buffer->get_iter_at_line_index(offsets.first.line - 1, offsets.first.index - 1); - Gtk::TextIter end_iter = buffer->get_iter_at_line_index(offsets.second.line - 1, offsets.second.index - 1); - buffer->apply_tag_by_name(type_it->second, begin_iter, end_iter); - } - }; - - for (auto &tag: last_syntax_tags) - buffer->remove_tag_by_name(tag, buffer->begin(), buffer->end()); - last_syntax_tags.clear(); - - for (size_t c = 0; c < clang_tokens->size(); ++c) { - auto &token = (*clang_tokens)[c]; - auto &token_offsets = clang_tokens_offsets[c]; - //if(token.get_kind()==clangmm::Token::Kind::Token_Punctuation) - //ranges.emplace_back(token_offset, static_cast<int>(token.get_cursor().get_kind())); - auto token_kind = token.get_kind(); - if (token_kind == clangmm::Token::Kind::Keyword) - apply_tag(token_offsets, 702); - else if (token_kind == clangmm::Token::Kind::Identifier) { - auto cursor_kind = token.get_cursor().get_kind(); - if (cursor_kind == clangmm::Cursor::Kind::DeclRefExpr || - cursor_kind == clangmm::Cursor::Kind::MemberRefExpr) - cursor_kind = token.get_cursor().get_referenced().get_kind(); - if (cursor_kind != clangmm::Cursor::Kind::PreprocessingDirective) - apply_tag(token_offsets, static_cast<int>(cursor_kind)); - } else if (token_kind == clangmm::Token::Kind::Literal) - apply_tag(token_offsets, static_cast<int>(clangmm::Cursor::Kind::StringLiteral)); - else if (token_kind == clangmm::Token::Kind::Comment) - apply_tag(token_offsets, 705); + auto buffer = get_buffer(); + const auto apply_tag = [this, buffer](const std::pair<clangmm::Offset, clangmm::Offset> &offsets, int type) { + auto type_it = clang_types().find(type); + if (type_it != clang_types().end()) { + last_syntax_tags.emplace(type_it->second); + Gtk::TextIter begin_iter = buffer->get_iter_at_line_index(offsets.first.line - 1, offsets.first.index - 1); + Gtk::TextIter end_iter = buffer->get_iter_at_line_index(offsets.second.line - 1, offsets.second.index - 1); + buffer->apply_tag_by_name(type_it->second, begin_iter, end_iter); } + }; + + for (auto &tag: last_syntax_tags) + buffer->remove_tag_by_name(tag, buffer->begin(), buffer->end()); + last_syntax_tags.clear(); + + for (size_t c = 0; c < clang_tokens->size(); ++c) { + auto &token = (*clang_tokens)[c]; + auto &token_offsets = clang_tokens_offsets[c]; + //if(token.get_kind()==clangmm::Token::Kind::Token_Punctuation) + //ranges.emplace_back(token_offset, static_cast<int>(token.get_cursor().get_kind())); + auto token_kind = token.get_kind(); + if (token_kind == clangmm::Token::Kind::Keyword) + apply_tag(token_offsets, 702); + else if (token_kind == clangmm::Token::Kind::Identifier) { + auto cursor_kind = token.get_cursor().get_kind(); + if (cursor_kind == clangmm::Cursor::Kind::DeclRefExpr || + cursor_kind == clangmm::Cursor::Kind::MemberRefExpr) + cursor_kind = token.get_cursor().get_referenced().get_kind(); + if (cursor_kind != clangmm::Cursor::Kind::PreprocessingDirective) + apply_tag(token_offsets, static_cast<int>(cursor_kind)); + } else if (token_kind == clangmm::Token::Kind::Literal) + apply_tag(token_offsets, static_cast<int>(clangmm::Cursor::Kind::StringLiteral)); + else if (token_kind == clangmm::Token::Kind::Comment) + apply_tag(token_offsets, 705); + } } void Source::ClangViewParse::update_diagnostics() { - clear_diagnostic_tooltips(); - fix_its.clear(); - size_t num_warnings = 0; - size_t num_errors = 0; - size_t num_fix_its = 0; - for (auto &diagnostic: clang_diagnostics) { - if (diagnostic.path == file_path.string()) { - int line = diagnostic.offsets.first.line - 1; - if (line < 0 || line >= get_buffer()->get_line_count()) - line = get_buffer()->get_line_count() - 1; - auto start = get_iter_at_line_end(line); - int index = diagnostic.offsets.first.index - 1; - if (index >= 0 && index < start.get_line_index()) - start = get_buffer()->get_iter_at_line_index(line, index); - if (start.ends_line()) { - while (!start.is_start() && start.ends_line()) - start.backward_char(); - } - diagnostic_offsets.emplace(start.get_offset()); - - line = diagnostic.offsets.second.line - 1; - if (line < 0 || line >= get_buffer()->get_line_count()) - line = get_buffer()->get_line_count() - 1; - auto end = get_iter_at_line_end(line); - index = diagnostic.offsets.second.index - 1; - if (index >= 0 && index < end.get_line_index()) - end = get_buffer()->get_iter_at_line_index(line, index); - - bool error = false; - std::string severity_tag_name; - if (diagnostic.severity <= clangmm::Diagnostic::Severity::Warning) { - severity_tag_name = "def:warning"; - num_warnings++; - } else { - severity_tag_name = "def:error"; - num_errors++; - error = true; - } - - std::string fix_its_string; - unsigned fix_its_count = 0; - for (auto &fix_it: diagnostic.fix_its) { - auto clang_offsets = fix_it.offsets; - std::pair<Offset, Offset> offsets; - offsets.first.line = clang_offsets.first.line - 1; - offsets.first.index = clang_offsets.first.index - 1; - offsets.second.line = clang_offsets.second.line - 1; - offsets.second.index = clang_offsets.second.index - 1; - - fix_its.emplace_back(fix_it.source, offsets); - - if (fix_its_string.size() > 0) - fix_its_string += '\n'; - fix_its_string += fix_its.back().string(get_buffer()); - fix_its_count++; - num_fix_its++; - } - - if (fix_its_count == 1) - fix_its_string.insert(0, "Fix-it:\n"); - else if (fix_its_count > 1) - fix_its_string.insert(0, "Fix-its:\n"); - - if (!fix_its_string.empty()) - diagnostic.spelling += "\n\n" + fix_its_string; - - add_diagnostic_tooltip(start, end, diagnostic.spelling, error); - } + clear_diagnostic_tooltips(); + fix_its.clear(); + size_t num_warnings = 0; + size_t num_errors = 0; + size_t num_fix_its = 0; + for (auto &diagnostic: clang_diagnostics) { + if (diagnostic.path == file_path.string()) { + int line = diagnostic.offsets.first.line - 1; + if (line < 0 || line >= get_buffer()->get_line_count()) + line = get_buffer()->get_line_count() - 1; + auto start = get_iter_at_line_end(line); + int index = diagnostic.offsets.first.index - 1; + if (index >= 0 && index < start.get_line_index()) + start = get_buffer()->get_iter_at_line_index(line, index); + if (start.ends_line()) { + while (!start.is_start() && start.ends_line()) + start.backward_char(); + } + diagnostic_offsets.emplace(start.get_offset()); + + line = diagnostic.offsets.second.line - 1; + if (line < 0 || line >= get_buffer()->get_line_count()) + line = get_buffer()->get_line_count() - 1; + auto end = get_iter_at_line_end(line); + index = diagnostic.offsets.second.index - 1; + if (index >= 0 && index < end.get_line_index()) + end = get_buffer()->get_iter_at_line_index(line, index); + + bool error = false; + std::string severity_tag_name; + if (diagnostic.severity <= clangmm::Diagnostic::Severity::Warning) { + severity_tag_name = "def:warning"; + num_warnings++; + } else { + severity_tag_name = "def:error"; + num_errors++; + error = true; + } + + std::string fix_its_string; + unsigned fix_its_count = 0; + for (auto &fix_it: diagnostic.fix_its) { + auto clang_offsets = fix_it.offsets; + std::pair<Offset, Offset> offsets; + offsets.first.line = clang_offsets.first.line - 1; + offsets.first.index = clang_offsets.first.index - 1; + offsets.second.line = clang_offsets.second.line - 1; + offsets.second.index = clang_offsets.second.index - 1; + + fix_its.emplace_back(fix_it.source, offsets); + + if (fix_its_string.size() > 0) + fix_its_string += '\n'; + fix_its_string += fix_its.back().string(get_buffer()); + fix_its_count++; + num_fix_its++; + } + + if (fix_its_count == 1) + fix_its_string.insert(0, "Fix-it:\n"); + else if (fix_its_count > 1) + fix_its_string.insert(0, "Fix-its:\n"); + + if (!fix_its_string.empty()) + diagnostic.spelling += "\n\n" + fix_its_string; + + add_diagnostic_tooltip(start, end, diagnostic.spelling, error); } - status_diagnostics = std::make_tuple(num_warnings, num_errors, num_fix_its); - if (update_status_diagnostics) - update_status_diagnostics(this); + } + status_diagnostics = std::make_tuple(num_warnings, num_errors, num_fix_its); + if (update_status_diagnostics) + update_status_diagnostics(this); } void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle) { - if (parsed) { - Gtk::TextIter iter; - int location_x, location_y; - window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, rectangle.get_x(), rectangle.get_y(), location_x, - location_y); - location_x += (rectangle.get_width() - 1) / 2; - get_iter_at_location(iter, location_x, location_y); - Gdk::Rectangle iter_rectangle; - get_iter_location(iter, iter_rectangle); - if (iter.ends_line() && location_x > iter_rectangle.get_x()) - return; + if (parsed) { + Gtk::TextIter iter; + int location_x, location_y; + window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, rectangle.get_x(), rectangle.get_y(), location_x, + location_y); + location_x += (rectangle.get_width() - 1) / 2; + get_iter_at_location(iter, location_x, location_y); + Gdk::Rectangle iter_rectangle; + get_iter_location(iter, iter_rectangle); + if (iter.ends_line() && location_x > iter_rectangle.get_x()) + return; - auto line = static_cast<unsigned>(iter.get_line()); - auto index = static_cast<unsigned>(iter.get_line_index()); - type_tooltips.clear(); - for (size_t c = clang_tokens->size() - 1; c != static_cast<size_t>(-1); --c) { - auto &token = (*clang_tokens)[c]; - auto &token_offsets = clang_tokens_offsets[c]; - if (token.is_identifier() || token.get_spelling() == "auto") { - if (line == token_offsets.first.line - 1 && index >= token_offsets.first.index - 1 && - index <= token_offsets.second.index - 1) { - auto cursor = token.get_cursor(); - auto referenced = cursor.get_referenced(); - if (referenced) { - auto start = get_buffer()->get_iter_at_line_index(token_offsets.first.line - 1, - token_offsets.first.index - 1); - auto end = get_buffer()->get_iter_at_line_index(token_offsets.second.line - 1, - token_offsets.second.index - 1); - auto create_tooltip_buffer = [this, &token, &start, &end]() { - auto tooltip_buffer = Gtk::TextBuffer::create(get_buffer()->get_tag_table()); - tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), - "Type: " + token.get_cursor().get_type_description()); - auto brief_comment = token.get_cursor().get_brief_comments(); - if (brief_comment != "") - tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), - "\n\n" + brief_comment); + auto line = static_cast<unsigned>(iter.get_line()); + auto index = static_cast<unsigned>(iter.get_line_index()); + type_tooltips.clear(); + for (size_t c = clang_tokens->size() - 1; c != static_cast<size_t>(-1); --c) { + auto &token = (*clang_tokens)[c]; + auto &token_offsets = clang_tokens_offsets[c]; + if (token.is_identifier() || token.get_spelling() == "auto") { + if (line == token_offsets.first.line - 1 && index >= token_offsets.first.index - 1 && + index <= token_offsets.second.index - 1) { + auto cursor = token.get_cursor(); + auto referenced = cursor.get_referenced(); + if (referenced) { + auto start = get_buffer()->get_iter_at_line_index(token_offsets.first.line - 1, + token_offsets.first.index - 1); + auto end = get_buffer()->get_iter_at_line_index(token_offsets.second.line - 1, + token_offsets.second.index - 1); + auto create_tooltip_buffer = [this, &token, &start, &end]() { + auto tooltip_buffer = Gtk::TextBuffer::create(get_buffer()->get_tag_table()); + tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), + "Type: " + token.get_cursor().get_type_description()); + auto brief_comment = token.get_cursor().get_brief_comments(); + if (brief_comment != "") + tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), + "\n\n" + brief_comment); #ifdef JUCI_ENABLE_DEBUG - if(Debug::LLDB::get().is_stopped()) { - auto referenced=token.get_cursor().get_referenced(); - auto location=referenced.get_source_location(); - Glib::ustring value_type="Value"; - - auto iter=start; - while((*iter>='a' && *iter<='z') || (*iter>='A' && *iter<='Z') || (*iter>='0' && *iter<='9') || *iter=='_' || *iter=='.') { - start=iter; - if(!iter.backward_char()) - break; - if(*iter=='>') { - if(!(iter.backward_char() && *iter=='-' && iter.backward_char())) - break; - } - else if(*iter==':') { - if(!(iter.backward_char() && *iter==':' && iter.backward_char())) - break; - } - } - auto spelling=get_buffer()->get_text(start, end).raw(); - - Glib::ustring debug_value; - auto cursor_kind=referenced.get_kind(); - if(cursor_kind!=clangmm::Cursor::Kind::FunctionDecl && cursor_kind!=clangmm::Cursor::Kind::CXXMethod && - cursor_kind!=clangmm::Cursor::Kind::Constructor && cursor_kind!=clangmm::Cursor::Kind::Destructor && - cursor_kind!=clangmm::Cursor::Kind::FunctionTemplate && cursor_kind!=clangmm::Cursor::Kind::ConversionFunction) { - debug_value=Debug::LLDB::get().get_value(spelling, location.get_path(), location.get_offset().line, location.get_offset().index); - } - if(debug_value.empty()) { - value_type="Return value"; - auto offsets=token.get_source_range().get_offsets(); - debug_value=Debug::LLDB::get().get_return_value(token.get_source_location().get_path(), offsets.first.line, offsets.first.index); - } - if(!debug_value.empty()) { - size_t pos=debug_value.find(" = "); - if(pos!=Glib::ustring::npos) { - Glib::ustring::iterator iter; - while(!debug_value.validate(iter)) { - auto next_char_iter=iter; - next_char_iter++; - debug_value.replace(iter, next_char_iter, "?"); - } - tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), "\n\n"+value_type+": "+debug_value.substr(pos+3, debug_value.size()-(pos+3)-1)); - } - } - } + if(Debug::LLDB::get().is_stopped()) { +auto referenced=token.get_cursor().get_referenced(); +auto location=referenced.get_source_location(); +Glib::ustring value_type="Value"; + +auto iter=start; +while((*iter>='a' && *iter<='z') || (*iter>='A' && *iter<='Z') || (*iter>='0' && *iter<='9') || *iter=='_' || *iter=='.') { +start=iter; +if(!iter.backward_char()) +break; +if(*iter=='>') { +if(!(iter.backward_char() && *iter=='-' && iter.backward_char())) +break; +} +else if(*iter==':') { +if(!(iter.backward_char() && *iter==':' && iter.backward_char())) +break; +} +} +auto spelling=get_buffer()->get_text(start, end).raw(); + +Glib::ustring debug_value; +auto cursor_kind=referenced.get_kind(); +if(cursor_kind!=clangmm::Cursor::Kind::FunctionDecl && cursor_kind!=clangmm::Cursor::Kind::CXXMethod && +cursor_kind!=clangmm::Cursor::Kind::Constructor && cursor_kind!=clangmm::Cursor::Kind::Destructor && +cursor_kind!=clangmm::Cursor::Kind::FunctionTemplate && cursor_kind!=clangmm::Cursor::Kind::ConversionFunction) { +debug_value=Debug::LLDB::get().get_value(spelling, location.get_path(), location.get_offset().line, location.get_offset().index); +} +if(debug_value.empty()) { +value_type="Return value"; +auto offsets=token.get_source_range().get_offsets(); +debug_value=Debug::LLDB::get().get_return_value(token.get_source_location().get_path(), offsets.first.line, offsets.first.index); +} +if(!debug_value.empty()) { +size_t pos=debug_value.find(" = "); +if(pos!=Glib::ustring::npos) { +Glib::ustring::iterator iter; +while(!debug_value.validate(iter)) { +auto next_char_iter=iter; +next_char_iter++; +debug_value.replace(iter, next_char_iter, "?"); +} +tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), "\n\n"+value_type+": "+debug_value.substr(pos+3, debug_value.size()-(pos+3)-1)); +} +} +} #endif - return tooltip_buffer; - }; + return tooltip_buffer; + }; - type_tooltips.emplace_back(create_tooltip_buffer, this, get_buffer()->create_mark(start), - get_buffer()->create_mark(end)); - type_tooltips.show(); - return; - } - } - } + type_tooltips.emplace_back(create_tooltip_buffer, this, get_buffer()->create_mark(start), + get_buffer()->create_mark(end)); + type_tooltips.show(); + return; + } } + } } + } } Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language) : - BaseView(file_path, language), Source::ClangViewParse(file_path, language), - autocomplete(this, interactive_completion, last_keyval, true) { - non_interactive_completion = [this] { - if (CompletionDialog::get() && CompletionDialog::get()->is_visible()) - return; - autocomplete.run(); - }; - - autocomplete.is_processing = [this] { - return parse_state == ParseState::PROCESSING; - }; + BaseView(file_path, language), Source::ClangViewParse(file_path, language), + autocomplete(this, interactive_completion, last_keyval, true) { + non_interactive_completion = [this] { + if (CompletionDialog::get() && CompletionDialog::get()->is_visible()) + return; + autocomplete.run(); + }; + + autocomplete.is_processing = [this] { + return parse_state == ParseState::PROCESSING; + }; + + autocomplete.reparse = [this] { + selected_completion_string = nullptr; + code_complete_results = nullptr; + soft_reparse(true); + }; + + autocomplete.cancel_reparse = [this] { + delayed_reparse_connection.disconnect(); + }; - autocomplete.reparse = [this] { - selected_completion_string = nullptr; - code_complete_results = nullptr; - soft_reparse(true); - }; + autocomplete.get_parse_lock = [this]() { + return std::make_unique<std::lock_guard<std::mutex>>(parse_mutex); + }; - autocomplete.cancel_reparse = [this] { - delayed_reparse_connection.disconnect(); - }; + autocomplete.stop_parse = [this]() { + parse_process_state = ParseProcessState::IDLE; + }; + + // Activate argument completions + get_buffer()->signal_changed().connect([this] { + if (!interactive_completion) + return; + if (CompletionDialog::get() && CompletionDialog::get()->is_visible()) + return; + if (!has_focus()) + return; + if (show_arguments) + autocomplete.stop(); + show_arguments = false; + delayed_show_arguments_connection.disconnect(); + delayed_show_arguments_connection = Glib::signal_timeout().connect([this]() { + if (get_buffer()->get_has_selection()) + return false; + if (CompletionDialog::get() && CompletionDialog::get()->is_visible()) + return false; + if (!has_focus()) + return false; + if (is_possible_parameter()) { + autocomplete.stop(); + autocomplete.run(); + } + return false; + }, 500); + }, false); + + // Remove argument completions + signal_key_press_event().connect([this](GdkEventKey *key) { + if (show_arguments && CompletionDialog::get() && CompletionDialog::get()->is_visible() && + key->keyval != GDK_KEY_Down && key->keyval != GDK_KEY_Up && + key->keyval != GDK_KEY_Return && key->keyval != GDK_KEY_KP_Enter && + key->keyval != GDK_KEY_ISO_Left_Tab && key->keyval != GDK_KEY_Tab && + (key->keyval < GDK_KEY_Shift_L || key->keyval > GDK_KEY_Hyper_R)) { + get_buffer()->erase(CompletionDialog::get()->start_mark->get_iter(), + get_buffer()->get_insert()->get_iter()); + CompletionDialog::get()->hide(); + } + return false; + }, false); - autocomplete.get_parse_lock = [this]() { - return std::make_unique<std::lock_guard<std::mutex>>(parse_mutex); - }; + autocomplete.is_continue_key = [](guint keyval) { + if ((keyval >= '0' && keyval <= '9') || (keyval >= 'a' && keyval <= 'z') || (keyval >= 'A' && keyval <= 'Z') || + keyval == '_') + return true; - autocomplete.stop_parse = [this]() { - parse_process_state = ParseProcessState::IDLE; - }; + return false; + }; - // Activate argument completions - get_buffer()->signal_changed().connect([this] { - if (!interactive_completion) - return; - if (CompletionDialog::get() && CompletionDialog::get()->is_visible()) - return; - if (!has_focus()) - return; - if (show_arguments) - autocomplete.stop(); - show_arguments = false; - delayed_show_arguments_connection.disconnect(); - delayed_show_arguments_connection = Glib::signal_timeout().connect([this]() { - if (get_buffer()->get_has_selection()) - return false; - if (CompletionDialog::get() && CompletionDialog::get()->is_visible()) - return false; - if (!has_focus()) - return false; - if (is_possible_parameter()) { - autocomplete.stop(); - autocomplete.run(); - } - return false; - }, 500); - }, false); - - // Remove argument completions - signal_key_press_event().connect([this](GdkEventKey *key) { - if (show_arguments && CompletionDialog::get() && CompletionDialog::get()->is_visible() && - key->keyval != GDK_KEY_Down && key->keyval != GDK_KEY_Up && - key->keyval != GDK_KEY_Return && key->keyval != GDK_KEY_KP_Enter && - key->keyval != GDK_KEY_ISO_Left_Tab && key->keyval != GDK_KEY_Tab && - (key->keyval < GDK_KEY_Shift_L || key->keyval > GDK_KEY_Hyper_R)) { - get_buffer()->erase(CompletionDialog::get()->start_mark->get_iter(), - get_buffer()->get_insert()->get_iter()); - CompletionDialog::get()->hide(); - } - return false; - }, false); + autocomplete.is_restart_key = [this](guint keyval) { + auto iter = get_buffer()->get_insert()->get_iter(); + iter.backward_chars(2); + if (keyval == '.' || (keyval == ':' && *iter == ':') || (keyval == '>' && *iter == '-')) + return true; + return false; + }; - autocomplete.is_continue_key = [](guint keyval) { - if ((keyval >= '0' && keyval <= '9') || (keyval >= 'a' && keyval <= 'z') || (keyval >= 'A' && keyval <= 'Z') || - keyval == '_') - return true; + autocomplete.run_check = [this]() { + auto iter = get_buffer()->get_insert()->get_iter(); + iter.backward_char(); + if (!is_code_iter(iter)) + return false; + + show_arguments = false; + + std::string line = " " + get_line_before(); + const static std::regex dot_or_arrow("^.*[a-zA-Z0-9_\\)\\]\\>](\\.|->)([a-zA-Z0-9_]*)$"); + const static std::regex colon_colon("^.*::([a-zA-Z0-9_]*)$"); + const static std::regex part_of_symbol("^.*[^a-zA-Z0-9_]+([a-zA-Z0-9_]{3,})$"); + std::smatch sm; + if (std::regex_match(line, sm, dot_or_arrow)) { + { + std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); + autocomplete.prefix = sm[2].str(); + } + if (autocomplete.prefix.size() == 0 || autocomplete.prefix[0] < '0' || autocomplete.prefix[0] > '9') + return true; + } else if (std::regex_match(line, sm, colon_colon)) { + { + std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); + autocomplete.prefix = sm[1].str(); + } + if (autocomplete.prefix.size() == 0 || autocomplete.prefix[0] < '0' || autocomplete.prefix[0] > '9') + return true; + } else if (std::regex_match(line, sm, part_of_symbol)) { + { + std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); + autocomplete.prefix = sm[1].str(); + } + if (autocomplete.prefix.size() == 0 || autocomplete.prefix[0] < '0' || autocomplete.prefix[0] > '9') + return true; + } else if (is_possible_parameter()) { + show_arguments = true; + std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); + autocomplete.prefix = ""; + return true; + } else if (!interactive_completion) { + auto end_iter = get_buffer()->get_insert()->get_iter(); + auto iter = end_iter; + while (iter.backward_char() && autocomplete.is_continue_key(*iter)) {} + if (iter != end_iter) + iter.forward_char(); + std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); + autocomplete.prefix = get_buffer()->get_text(iter, end_iter); + return true; + } - return false; - }; + return false; + }; - autocomplete.is_restart_key = [this](guint keyval) { - auto iter = get_buffer()->get_insert()->get_iter(); - iter.backward_chars(2); - if (keyval == '.' || (keyval == ':' && *iter == ':') || (keyval == '>' && *iter == '-')) - return true; - return false; - }; + autocomplete.before_add_rows = [this] { + status_state = "autocomplete..."; + if (update_status_state) + update_status_state(this); + }; - autocomplete.run_check = [this]() { - auto iter = get_buffer()->get_insert()->get_iter(); - iter.backward_char(); - if (!is_code_iter(iter)) - return false; + autocomplete.after_add_rows = [this] { + status_state = ""; + if (update_status_state) + update_status_state(this); + }; + + autocomplete.on_add_rows_error = [this] { + Terminal::get().print("Error: autocomplete failed, reparsing " + this->file_path.string() + "\n", true); + selected_completion_string = nullptr; + code_complete_results = nullptr; + full_reparse(); + }; + + autocomplete.add_rows = [this](std::string &buffer, int line_number, int column) { + if (this->language && (this->language->get_id() == "chdr" || this->language->get_id() == "cpphdr")) + clangmm::remove_include_guard(buffer); + code_complete_results = std::make_unique<clangmm::CodeCompleteResults>( + clang_tu->get_code_completions(buffer, line_number, column)); + if (code_complete_results->cx_results == nullptr) { + auto expected = ParseState::PROCESSING; + parse_state.compare_exchange_strong(expected, ParseState::RESTARTING); + return; + } - show_arguments = false; - - std::string line = " " + get_line_before(); - const static std::regex dot_or_arrow("^.*[a-zA-Z0-9_\\)\\]\\>](\\.|->)([a-zA-Z0-9_]*)$"); - const static std::regex colon_colon("^.*::([a-zA-Z0-9_]*)$"); - const static std::regex part_of_symbol("^.*[^a-zA-Z0-9_]+([a-zA-Z0-9_]{3,})$"); - std::smatch sm; - if (std::regex_match(line, sm, dot_or_arrow)) { - { - std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); - autocomplete.prefix = sm[2].str(); + if (autocomplete.state == Autocomplete::State::STARTING) { + std::string prefix_copy; + { + std::lock_guard<std::mutex> lock(autocomplete.prefix_mutex); + prefix_copy = autocomplete.prefix; + } + + completion_strings.clear(); + for (unsigned i = 0; i < code_complete_results->size(); ++i) { + auto result = code_complete_results->get(i); + if (result.available()) { + std::string text; + if (show_arguments) { + class Recursive { + public: + static void f(const clangmm::CompletionString &completion_string, std::string &text) { + for (unsigned i = 0; i < completion_string.get_num_chunks(); ++i) { + auto kind = static_cast<clangmm::CompletionChunkKind>(clang_getCompletionChunkKind( + completion_string.cx_completion_string, i)); + if (kind == clangmm::CompletionChunk_Optional) + f(clangmm::CompletionString(clang_getCompletionChunkCompletionString( + completion_string.cx_completion_string, i)), text); + else if (kind == clangmm::CompletionChunk_CurrentParameter) { + auto chunk_cstr = clangmm::String( + clang_getCompletionChunkText(completion_string.cx_completion_string, + i)); + text += chunk_cstr.c_str; + } + } + } + }; + Recursive::f(result, text); + if (!text.empty()) { + bool already_added = false; + for (auto &row: autocomplete.rows) { + if (row == text) { + already_added = true; + break; + } + } + if (!already_added) { + autocomplete.rows.emplace_back(std::move(text)); + completion_strings.emplace_back(result.cx_completion_string); + } } - if (autocomplete.prefix.size() == 0 || autocomplete.prefix[0] < '0' || autocomplete.prefix[0] > '9') - return true; - } else if (std::regex_match(line, sm, colon_colon)) { - { - std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); - autocomplete.prefix = sm[1].str(); + } else { + std::string return_text; + bool match = false; + for (unsigned i = 0; i < result.get_num_chunks(); ++i) { + auto kind = static_cast<clangmm::CompletionChunkKind>(clang_getCompletionChunkKind( + result.cx_completion_string, i)); + if (kind != clangmm::CompletionChunk_Informative) { + auto chunk_cstr = clangmm::String( + clang_getCompletionChunkText(result.cx_completion_string, i)); + if (kind == clangmm::CompletionChunk_TypedText) { + if (strlen(chunk_cstr.c_str) >= prefix_copy.size() && + prefix_copy.compare(0, prefix_copy.size(), chunk_cstr.c_str, + prefix_copy.size()) == 0) + match = true; + else + break; + } + if (kind == clangmm::CompletionChunk_ResultType) + return_text = std::string(" → ") + chunk_cstr.c_str; + else + text += chunk_cstr.c_str; + } } - if (autocomplete.prefix.size() == 0 || autocomplete.prefix[0] < '0' || autocomplete.prefix[0] > '9') - return true; - } else if (std::regex_match(line, sm, part_of_symbol)) { - { - std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); - autocomplete.prefix = sm[1].str(); + if (match && !text.empty()) { + if (!return_text.empty()) + text += return_text; + autocomplete.rows.emplace_back(std::move(text)); + completion_strings.emplace_back(result.cx_completion_string); } - if (autocomplete.prefix.size() == 0 || autocomplete.prefix[0] < '0' || autocomplete.prefix[0] > '9') - return true; - } else if (is_possible_parameter()) { - show_arguments = true; - std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); - autocomplete.prefix = ""; - return true; - } else if (!interactive_completion) { - auto end_iter = get_buffer()->get_insert()->get_iter(); - auto iter = end_iter; - while (iter.backward_char() && autocomplete.is_continue_key(*iter)) {} - if (iter != end_iter) - iter.forward_char(); - std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); - autocomplete.prefix = get_buffer()->get_text(iter, end_iter); - return true; + } } + } + } + }; - return false; - }; - - autocomplete.before_add_rows = [this] { - status_state = "autocomplete..."; - if (update_status_state) - update_status_state(this); - }; - - autocomplete.after_add_rows = [this] { - status_state = ""; - if (update_status_state) - update_status_state(this); - }; - - autocomplete.on_add_rows_error = [this] { - Terminal::get().print("Error: autocomplete failed, reparsing " + this->file_path.string() + "\n", true); - selected_completion_string = nullptr; - code_complete_results = nullptr; - full_reparse(); - }; - - autocomplete.add_rows = [this](std::string &buffer, int line_number, int column) { - if (this->language && (this->language->get_id() == "chdr" || this->language->get_id() == "cpphdr")) - clangmm::remove_include_guard(buffer); - code_complete_results = std::make_unique<clangmm::CodeCompleteResults>( - clang_tu->get_code_completions(buffer, line_number, column)); - if (code_complete_results->cx_results == nullptr) { - auto expected = ParseState::PROCESSING; - parse_state.compare_exchange_strong(expected, ParseState::RESTARTING); - return; + autocomplete.on_show = [this] { + hide_tooltips(); + }; + + autocomplete.on_hide = [this] { + selected_completion_string = nullptr; + code_complete_results = nullptr; + }; + + autocomplete.on_changed = [this](unsigned int index, const std::string &text) { + selected_completion_string = completion_strings[index]; + }; + + autocomplete.on_select = [this](unsigned int index, const std::string &text, bool hide_window) { + std::string row; + auto pos = text.find(" → "); + if (pos != std::string::npos) + row = text.substr(0, pos); + else + row = text; + //erase existing variable or function before insert iter + get_buffer()->erase(CompletionDialog::get()->start_mark->get_iter(), get_buffer()->get_insert()->get_iter()); + //do not insert template argument or function parameters if they already exist + auto iter = get_buffer()->get_insert()->get_iter(); + if (*iter == '<' || *iter == '(') { + auto bracket_pos = row.find(*iter); + if (bracket_pos != std::string::npos) { + row = row.substr(0, bracket_pos); + } + } + //Fixes for the most commonly used stream manipulators + auto manipulators_map = autocomplete_manipulators_map(); + auto it = manipulators_map.find(row); + if (it != manipulators_map.end()) + row = it->second; + //Do not insert template argument, function parameters or ':' unless hide_window is true + if (!hide_window) { + for (size_t c = 0; c < row.size(); ++c) { + if (row[c] == '<' || row[c] == '(' || row[c] == ':') { + row.erase(c); + break; } - - if (autocomplete.state == Autocomplete::State::STARTING) { - std::string prefix_copy; - { - std::lock_guard<std::mutex> lock(autocomplete.prefix_mutex); - prefix_copy = autocomplete.prefix; - } - - completion_strings.clear(); - for (unsigned i = 0; i < code_complete_results->size(); ++i) { - auto result = code_complete_results->get(i); - if (result.available()) { - std::string text; - if (show_arguments) { - class Recursive { - public: - static void f(const clangmm::CompletionString &completion_string, std::string &text) { - for (unsigned i = 0; i < completion_string.get_num_chunks(); ++i) { - auto kind = static_cast<clangmm::CompletionChunkKind>(clang_getCompletionChunkKind( - completion_string.cx_completion_string, i)); - if (kind == clangmm::CompletionChunk_Optional) - f(clangmm::CompletionString(clang_getCompletionChunkCompletionString( - completion_string.cx_completion_string, i)), text); - else if (kind == clangmm::CompletionChunk_CurrentParameter) { - auto chunk_cstr = clangmm::String( - clang_getCompletionChunkText(completion_string.cx_completion_string, - i)); - text += chunk_cstr.c_str; - } - } - } - }; - Recursive::f(result, text); - if (!text.empty()) { - bool already_added = false; - for (auto &row: autocomplete.rows) { - if (row == text) { - already_added = true; - break; - } - } - if (!already_added) { - autocomplete.rows.emplace_back(std::move(text)); - completion_strings.emplace_back(result.cx_completion_string); - } - } - } else { - std::string return_text; - bool match = false; - for (unsigned i = 0; i < result.get_num_chunks(); ++i) { - auto kind = static_cast<clangmm::CompletionChunkKind>(clang_getCompletionChunkKind( - result.cx_completion_string, i)); - if (kind != clangmm::CompletionChunk_Informative) { - auto chunk_cstr = clangmm::String( - clang_getCompletionChunkText(result.cx_completion_string, i)); - if (kind == clangmm::CompletionChunk_TypedText) { - if (strlen(chunk_cstr.c_str) >= prefix_copy.size() && - prefix_copy.compare(0, prefix_copy.size(), chunk_cstr.c_str, - prefix_copy.size()) == 0) - match = true; - else - break; - } - if (kind == clangmm::CompletionChunk_ResultType) - return_text = std::string(" → ") + chunk_cstr.c_str; - else - text += chunk_cstr.c_str; - } - } - if (match && !text.empty()) { - if (!return_text.empty()) - text += return_text; - autocomplete.rows.emplace_back(std::move(text)); - completion_strings.emplace_back(result.cx_completion_string); - } - } - } - } + } + } + get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), row); + //if selection is finalized, select text inside template arguments or function parameters + if (hide_window) { + size_t start_pos = std::string::npos; + size_t end_pos = std::string::npos; + if (show_arguments) { + start_pos = 0; + end_pos = row.size(); + } else { + auto para_pos = row.find('('); + auto angle_pos = row.find('<'); + if (angle_pos < para_pos) { + start_pos = angle_pos + 1; + end_pos = row.find('>'); + } else if (para_pos != std::string::npos) { + start_pos = para_pos + 1; + end_pos = row.size() - 1; } - }; - - autocomplete.on_show = [this] { - hide_tooltips(); - }; - - autocomplete.on_hide = [this] { - selected_completion_string = nullptr; - code_complete_results = nullptr; - }; - - autocomplete.on_changed = [this](unsigned int index, const std::string &text) { - selected_completion_string = completion_strings[index]; - }; - - autocomplete.on_select = [this](unsigned int index, const std::string &text, bool hide_window) { - std::string row; - auto pos = text.find(" → "); - if (pos != std::string::npos) - row = text.substr(0, pos); - else - row = text; - //erase existing variable or function before insert iter - get_buffer()->erase(CompletionDialog::get()->start_mark->get_iter(), get_buffer()->get_insert()->get_iter()); - //do not insert template argument or function parameters if they already exist - auto iter = get_buffer()->get_insert()->get_iter(); - if (*iter == '<' || *iter == '(') { - auto bracket_pos = row.find(*iter); - if (bracket_pos != std::string::npos) { - row = row.substr(0, bracket_pos); - } + if (start_pos == std::string::npos || end_pos == std::string::npos) { + if ((start_pos = row.find('\"')) != std::string::npos) { + end_pos = row.find('\"', start_pos + 1); + ++start_pos; + } } - //Fixes for the most commonly used stream manipulators - auto manipulators_map = autocomplete_manipulators_map(); - auto it = manipulators_map.find(row); - if (it != manipulators_map.end()) - row = it->second; - //Do not insert template argument, function parameters or ':' unless hide_window is true - if (!hide_window) { - for (size_t c = 0; c < row.size(); ++c) { - if (row[c] == '<' || row[c] == '(' || row[c] == ':') { - row.erase(c); - break; - } + } + if (start_pos == std::string::npos || end_pos == std::string::npos) { + if ((start_pos = row.find(' ')) != std::string::npos) { + std::vector<std::string> parameters = {"expression", "arguments", "identifier", "type name", + "qualifier::name", "macro", "condition"}; + for (auto ¶meter: parameters) { + if ((start_pos = row.find(parameter, start_pos + 1)) != std::string::npos) { + end_pos = start_pos + parameter.size(); + break; } + } } - get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), row); - //if selection is finalized, select text inside template arguments or function parameters - if (hide_window) { - size_t start_pos = std::string::npos; - size_t end_pos = std::string::npos; - if (show_arguments) { - start_pos = 0; - end_pos = row.size(); - } else { - auto para_pos = row.find('('); - auto angle_pos = row.find('<'); - if (angle_pos < para_pos) { - start_pos = angle_pos + 1; - end_pos = row.find('>'); - } else if (para_pos != std::string::npos) { - start_pos = para_pos + 1; - end_pos = row.size() - 1; - } - if (start_pos == std::string::npos || end_pos == std::string::npos) { - if ((start_pos = row.find('\"')) != std::string::npos) { - end_pos = row.find('\"', start_pos + 1); - ++start_pos; - } - } - } - if (start_pos == std::string::npos || end_pos == std::string::npos) { - if ((start_pos = row.find(' ')) != std::string::npos) { - std::vector<std::string> parameters = {"expression", "arguments", "identifier", "type name", - "qualifier::name", "macro", "condition"}; - for (auto ¶meter: parameters) { - if ((start_pos = row.find(parameter, start_pos + 1)) != std::string::npos) { - end_pos = start_pos + parameter.size(); - break; - } - } - } - } - - if (start_pos != std::string::npos && end_pos != std::string::npos) { - int start_offset = CompletionDialog::get()->start_mark->get_iter().get_offset() + start_pos; - int end_offset = CompletionDialog::get()->start_mark->get_iter().get_offset() + end_pos; - auto size = get_buffer()->size(); - if (start_offset != end_offset && start_offset < size && end_offset < size) - get_buffer()->select_range(get_buffer()->get_iter_at_offset(start_offset), - get_buffer()->get_iter_at_offset(end_offset)); - } else { - //new autocomplete after for instance when selecting "std::" - auto iter = get_buffer()->get_insert()->get_iter(); - if (iter.backward_char() && *iter == ':') { - autocomplete.run(); - return; - } - } + } + + if (start_pos != std::string::npos && end_pos != std::string::npos) { + int start_offset = CompletionDialog::get()->start_mark->get_iter().get_offset() + start_pos; + int end_offset = CompletionDialog::get()->start_mark->get_iter().get_offset() + end_pos; + auto size = get_buffer()->size(); + if (start_offset != end_offset && start_offset < size && end_offset < size) + get_buffer()->select_range(get_buffer()->get_iter_at_offset(start_offset), + get_buffer()->get_iter_at_offset(end_offset)); + } else { + //new autocomplete after for instance when selecting "std::" + auto iter = get_buffer()->get_insert()->get_iter(); + if (iter.backward_char() && *iter == ':') { + autocomplete.run(); + return; } - }; + } + } + }; - autocomplete.get_tooltip = [this](unsigned int index) { - return clangmm::to_string(clang_getCompletionBriefComment(completion_strings[index])); - }; + autocomplete.get_tooltip = [this](unsigned int index) { + return clangmm::to_string(clang_getCompletionBriefComment(completion_strings[index])); + }; } bool Source::ClangViewAutocomplete::is_possible_parameter() { - auto iter = get_buffer()->get_insert()->get_iter(); - if (iter.backward_char() && - (!interactive_completion || last_keyval == '(' || last_keyval == ',' || last_keyval == ' ' || - last_keyval == GDK_KEY_Return || last_keyval == GDK_KEY_KP_Enter)) { - while ((*iter == ' ' || *iter == '\t' || *iter == '\n' || *iter == '\r') && iter.backward_char()) {} - if (*iter == '(' || *iter == ',') - return true; - } - return false; + auto iter = get_buffer()->get_insert()->get_iter(); + if (iter.backward_char() && + (!interactive_completion || last_keyval == '(' || last_keyval == ',' || last_keyval == ' ' || + last_keyval == GDK_KEY_Return || last_keyval == GDK_KEY_KP_Enter)) { + while ((*iter == ' ' || *iter == '\t' || *iter == '\n' || *iter == '\r') && iter.backward_char()) {} + if (*iter == '(' || *iter == ',') + return true; + } + return false; } const std::unordered_map<std::string, std::string> &Source::ClangViewAutocomplete::autocomplete_manipulators_map() { - //TODO: feel free to add more - static std::unordered_map<std::string, std::string> map = { - {"endl(basic_ostream<_CharT, _Traits> &__os)", "endl"}, - {"flush(basic_ostream<_CharT, _Traits> &__os)", "flush"}, - {"hex(std::ios_base &__str)", "hex"}, //clang++ headers - {"hex(std::ios_base &__base)", "hex"}, //g++ headers - {"dec(std::ios_base &__str)", "dec"}, - {"dec(std::ios_base &__base)", "dec"} - }; - return map; + //TODO: feel free to add more + static std::unordered_map<std::string, std::string> map = { + {"endl(basic_ostream<_CharT, _Traits> &__os)", "endl"}, + {"flush(basic_ostream<_CharT, _Traits> &__os)", "flush"}, + {"hex(std::ios_base &__str)", "hex"}, //clang++ headers + {"hex(std::ios_base &__base)", "hex"}, //g++ headers + {"dec(std::ios_base &__str)", "dec"}, + {"dec(std::ios_base &__base)", "dec"} + }; + return map; } Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language) : - BaseView(file_path, language), Source::ClangViewParse(file_path, language) { - similar_identifiers_tag = get_buffer()->create_tag(); - similar_identifiers_tag->property_weight() = Pango::WEIGHT_ULTRAHEAVY; - - get_buffer()->signal_changed().connect([this]() { - if (last_tagged_identifier) { - for (auto &mark: similar_identifiers_marks) { - get_buffer()->remove_tag(similar_identifiers_tag, mark.first->get_iter(), mark.second->get_iter()); - get_buffer()->delete_mark(mark.first); - get_buffer()->delete_mark(mark.second); - } - similar_identifiers_marks.clear(); - last_tagged_identifier = Identifier(); - } - }); + BaseView(file_path, language), Source::ClangViewParse(file_path, language) { + similar_identifiers_tag = get_buffer()->create_tag(); + similar_identifiers_tag->property_weight() = Pango::WEIGHT_ULTRAHEAVY; + + get_buffer()->signal_changed().connect([this]() { + if (last_tagged_identifier) { + for (auto &mark: similar_identifiers_marks) { + get_buffer()->remove_tag(similar_identifiers_tag, mark.first->get_iter(), mark.second->get_iter()); + get_buffer()->delete_mark(mark.first); + get_buffer()->delete_mark(mark.second); + } + similar_identifiers_marks.clear(); + last_tagged_identifier = Identifier(); + } + }); - get_token_spelling = [this]() { - if (!parsed) { - Info::get().print("Buffer is parsing"); - return std::string(); - } - auto identifier = get_identifier(); - if (identifier.spelling.empty() || - identifier.spelling == "::" || identifier.spelling == "," || identifier.spelling == "=" || - identifier.spelling == "(" || identifier.spelling == ")" || - identifier.spelling == "[" || identifier.spelling == "]") { - Info::get().print("No valid symbol found at current cursor location"); - return std::string(); - } - return identifier.spelling; - }; + get_token_spelling = [this]() { + if (!parsed) { + Info::get().print("Buffer is parsing"); + return std::string(); + } + auto identifier = get_identifier(); + if (identifier.spelling.empty() || + identifier.spelling == "::" || identifier.spelling == "," || identifier.spelling == "=" || + identifier.spelling == "(" || identifier.spelling == ")" || + identifier.spelling == "[" || identifier.spelling == "]") { + Info::get().print("No valid symbol found at current cursor location"); + return std::string(); + } + return identifier.spelling; + }; - rename_similar_tokens = [this](const std::string &text) { - if (!parsed) { - Info::get().print("Buffer is parsing"); - return; + rename_similar_tokens = [this](const std::string &text) { + if (!parsed) { + Info::get().print("Buffer is parsing"); + return; + } + auto identifier = get_identifier(); + if (identifier) { + wait_parsing(); + + std::vector<clangmm::TranslationUnit *> translation_units; + translation_units.emplace_back(clang_tu.get()); + for (auto &view: views) { + if (view != this) { + if (auto clang_view = dynamic_cast<Source::ClangView *>(view)) + translation_units.emplace_back(clang_view->clang_tu.get()); } - auto identifier = get_identifier(); - if (identifier) { - wait_parsing(); - - std::vector<clangmm::TranslationUnit *> translation_units; - translation_units.emplace_back(clang_tu.get()); - for (auto &view: views) { - if (view != this) { - if (auto clang_view = dynamic_cast<Source::ClangView *>(view)) - translation_units.emplace_back(clang_view->clang_tu.get()); - } + } + + auto build = Project::Build::create(this->file_path); + auto usages = Usages::Clang::get_usages(build->project_path, build->get_default_path(), + build->get_debug_path(), identifier.spelling, identifier.cursor, + translation_units); + + std::vector<Source::View *> renamed_views; + std::vector<Usages::Clang::Usages *> usages_renamed; + for (auto &usage: usages) { + size_t line_c = usage.lines.size() - 1; + auto view_it = views.end(); + for (auto it = views.begin(); it != views.end(); ++it) { + if ((*it)->file_path == usage.path) { + view_it = it; + break; + } + } + if (view_it != views.end()) { + (*view_it)->get_buffer()->begin_user_action(); + for (auto offset_it = usage.offsets.rbegin(); offset_it != usage.offsets.rend(); ++offset_it) { + auto start_iter = (*view_it)->get_buffer()->get_iter_at_line_index(offset_it->first.line - 1, + offset_it->first.index - 1); + auto end_iter = (*view_it)->get_buffer()->get_iter_at_line_index(offset_it->second.line - 1, + offset_it->second.index - 1); + (*view_it)->get_buffer()->erase(start_iter, end_iter); + start_iter = (*view_it)->get_buffer()->get_iter_at_line_index(offset_it->first.line - 1, + offset_it->first.index - 1); + (*view_it)->get_buffer()->insert(start_iter, text); + if (offset_it->first.index - 1 < usage.lines[line_c].size()) + usage.lines[line_c].replace(offset_it->first.index - 1, + offset_it->second.index - offset_it->first.index, text); + --line_c; + } + (*view_it)->get_buffer()->end_user_action(); + (*view_it)->save(); + renamed_views.emplace_back(*view_it); + usages_renamed.emplace_back(&usage); + } else { + std::string buffer; + { + std::ifstream stream(usage.path.string(), std::ifstream::binary); + if (stream) + buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>()); + } + std::ofstream stream(usage.path.string(), std::ifstream::binary); + if (!buffer.empty() && stream) { + std::vector<size_t> lines_start_pos = {0}; + for (size_t c = 0; c < buffer.size(); ++c) { + if (buffer[c] == '\n') + lines_start_pos.emplace_back(c + 1); } - - auto build = Project::Build::create(this->file_path); - auto usages = Usages::Clang::get_usages(build->project_path, build->get_default_path(), - build->get_debug_path(), identifier.spelling, identifier.cursor, - translation_units); - - std::vector<Source::View *> renamed_views; - std::vector<Usages::Clang::Usages *> usages_renamed; - for (auto &usage: usages) { - size_t line_c = usage.lines.size() - 1; - auto view_it = views.end(); - for (auto it = views.begin(); it != views.end(); ++it) { - if ((*it)->file_path == usage.path) { - view_it = it; - break; - } - } - if (view_it != views.end()) { - (*view_it)->get_buffer()->begin_user_action(); - for (auto offset_it = usage.offsets.rbegin(); offset_it != usage.offsets.rend(); ++offset_it) { - auto start_iter = (*view_it)->get_buffer()->get_iter_at_line_index(offset_it->first.line - 1, - offset_it->first.index - 1); - auto end_iter = (*view_it)->get_buffer()->get_iter_at_line_index(offset_it->second.line - 1, - offset_it->second.index - 1); - (*view_it)->get_buffer()->erase(start_iter, end_iter); - start_iter = (*view_it)->get_buffer()->get_iter_at_line_index(offset_it->first.line - 1, - offset_it->first.index - 1); - (*view_it)->get_buffer()->insert(start_iter, text); - if (offset_it->first.index - 1 < usage.lines[line_c].size()) - usage.lines[line_c].replace(offset_it->first.index - 1, - offset_it->second.index - offset_it->first.index, text); - --line_c; - } - (*view_it)->get_buffer()->end_user_action(); - (*view_it)->save(); - renamed_views.emplace_back(*view_it); - usages_renamed.emplace_back(&usage); - } else { - std::string buffer; - { - std::ifstream stream(usage.path.string(), std::ifstream::binary); - if (stream) - buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>()); - } - std::ofstream stream(usage.path.string(), std::ifstream::binary); - if (!buffer.empty() && stream) { - std::vector<size_t> lines_start_pos = {0}; - for (size_t c = 0; c < buffer.size(); ++c) { - if (buffer[c] == '\n') - lines_start_pos.emplace_back(c + 1); - } - for (auto offset_it = usage.offsets.rbegin(); offset_it != usage.offsets.rend(); ++offset_it) { - auto start_line = offset_it->first.line - 1; - auto end_line = offset_it->second.line - 1; - if (start_line < lines_start_pos.size() && end_line < lines_start_pos.size()) { - auto start = lines_start_pos[start_line] + offset_it->first.index - 1; - auto end = lines_start_pos[end_line] + offset_it->second.index - 1; - if (start < buffer.size() && end <= buffer.size()) - buffer.replace(start, end - start, text); - } - if (offset_it->first.index - 1 < usage.lines[line_c].size()) - usage.lines[line_c].replace(offset_it->first.index - 1, - offset_it->second.index - offset_it->first.index, text); - --line_c; - } - stream.write(buffer.data(), buffer.size()); - usages_renamed.emplace_back(&usage); - } else - Terminal::get().print("Error: could not write to file " + usage.path.string() + '\n', true); - } + for (auto offset_it = usage.offsets.rbegin(); offset_it != usage.offsets.rend(); ++offset_it) { + auto start_line = offset_it->first.line - 1; + auto end_line = offset_it->second.line - 1; + if (start_line < lines_start_pos.size() && end_line < lines_start_pos.size()) { + auto start = lines_start_pos[start_line] + offset_it->first.index - 1; + auto end = lines_start_pos[end_line] + offset_it->second.index - 1; + if (start < buffer.size() && end <= buffer.size()) + buffer.replace(start, end - start, text); + } + if (offset_it->first.index - 1 < usage.lines[line_c].size()) + usage.lines[line_c].replace(offset_it->first.index - 1, + offset_it->second.index - offset_it->first.index, text); + --line_c; } + stream.write(buffer.data(), buffer.size()); + usages_renamed.emplace_back(&usage); + } else + Terminal::get().print("Error: could not write to file " + usage.path.string() + '\n', true); + } + } + + if (!usages_renamed.empty()) { + Terminal::get().print("Renamed "); + Terminal::get().print(identifier.spelling, true); + Terminal::get().print(" to "); + Terminal::get().print(text, true); + Terminal::get().print(" at:\n"); + } + for (auto &usage: usages_renamed) { + size_t line_c = 0; + for (auto &offset: usage->offsets) { + Terminal::get().print( + filesystem::get_short_path(usage->path).string() + ':' + std::to_string(offset.first.line) + + ':' + std::to_string(offset.first.index) + ": "); + auto &line = usage->lines[line_c]; + auto index = offset.first.index - 1; + unsigned start = 0; + for (auto &chr: line) { + if (chr != ' ' && chr != '\t') + break; + ++start; + } + if (start < line.size() && index + text.size() < line.size()) { + Terminal::get().print(line.substr(start, index - start)); + Terminal::get().print(line.substr(index, text.size()), true); + Terminal::get().print(line.substr(index + text.size())); + } + Terminal::get().print("\n"); + ++line_c; + } + } - if (!usages_renamed.empty()) { - Terminal::get().print("Renamed "); - Terminal::get().print(identifier.spelling, true); - Terminal::get().print(" to "); - Terminal::get().print(text, true); - Terminal::get().print(" at:\n"); - } - for (auto &usage: usages_renamed) { - size_t line_c = 0; - for (auto &offset: usage->offsets) { - Terminal::get().print( - filesystem::get_short_path(usage->path).string() + ':' + std::to_string(offset.first.line) + - ':' + std::to_string(offset.first.index) + ": "); - auto &line = usage->lines[line_c]; - auto index = offset.first.index - 1; - unsigned start = 0; - for (auto &chr: line) { - if (chr != ' ' && chr != '\t') - break; - ++start; - } - if (start < line.size() && index + text.size() < line.size()) { - Terminal::get().print(line.substr(start, index - start)); - Terminal::get().print(line.substr(index, text.size()), true); - Terminal::get().print(line.substr(index + text.size())); - } - Terminal::get().print("\n"); - ++line_c; - } - } + for (auto &view: renamed_views) + view->soft_reparse_needed = false; + } + }; - for (auto &view: renamed_views) - view->soft_reparse_needed = false; + get_buffer()->signal_mark_set().connect( + [this](const Gtk::TextBuffer::iterator &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { + if (mark->get_name() == "insert") { + delayed_tag_similar_identifiers_connection.disconnect(); + delayed_tag_similar_identifiers_connection = Glib::signal_timeout().connect([this]() { + auto identifier = get_identifier(); + tag_similar_identifiers(identifier); + return false; + }, 100); } - }; - - get_buffer()->signal_mark_set().connect( - [this](const Gtk::TextBuffer::iterator &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { - if (mark->get_name() == "insert") { - delayed_tag_similar_identifiers_connection.disconnect(); - delayed_tag_similar_identifiers_connection = Glib::signal_timeout().connect([this]() { - auto identifier = get_identifier(); - tag_similar_identifiers(identifier); - return false; - }, 100); - } - }); + }); + + auto declaration_location = [this]() { + auto identifier = get_identifier(); + if (identifier) { + auto source_location = identifier.cursor.get_canonical().get_source_location(); + auto offset = source_location.get_offset(); + return Offset(offset.line - 1, offset.index - 1, source_location.get_path()); + } else { + // If cursor is at an include line, return offset to included file + const static std::regex include_regex("^[ \t]*#[ \t]*include[ \t]*[<\"]([^<>\"]+)[>\"].*$"); + std::smatch sm; + auto line = get_line(); + if (std::regex_match(line, sm, include_regex)) { + struct ClientData { + boost::filesystem::path &file_path; + std::string found_include; + int line_nr; + std::string sm_str; + }; + ClientData client_data{this->file_path, std::string(), + get_buffer()->get_insert()->get_iter().get_line(), sm[1].str()}; + + // Attempt to find the 100% correct include file first + clang_getInclusions(clang_tu->cx_tu, + [](CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, + CXClientData client_data_) { + auto client_data = static_cast<ClientData *>(client_data_); + if (client_data->found_include.empty() && include_len > 0) { + auto source_location = clangmm::SourceLocation(inclusion_stack[0]); + if (static_cast<int>(source_location.get_offset().line) - 1 == + client_data->line_nr && + filesystem::get_normal_path(source_location.get_path()) == + client_data->file_path) + client_data->found_include = clangmm::to_string( + clang_getFileName(included_file)); + } + }, &client_data); + + if (!client_data.found_include.empty()) + return Offset(0, 0, client_data.found_include); + + // Find a matching include file if no include was found previously + clang_getInclusions(clang_tu->cx_tu, + [](CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, + CXClientData client_data_) { + auto client_data = static_cast<ClientData *>(client_data_); + if (client_data->found_include.empty()) { + for (unsigned c = 1; c < include_len; ++c) { + auto source_location = clangmm::SourceLocation(inclusion_stack[c]); + if (static_cast<int>(source_location.get_offset().line) - 1 <= + client_data->line_nr && + filesystem::get_normal_path(source_location.get_path()) == + client_data->file_path) { + auto included_file_str = clangmm::to_string( + clang_getFileName(included_file)); + if (included_file_str.size() >= client_data->sm_str.size() && + included_file_str.compare( + included_file_str.size() - client_data->sm_str.size(), + client_data->sm_str.size(), client_data->sm_str) == 0) { + client_data->found_include = included_file_str; + break; + } + } + } + } + }, &client_data); + + if (!client_data.found_include.empty()) + return Offset(0, 0, client_data.found_include); + } + } + return Offset(); + }; + + get_declaration_location = [this, declaration_location]() { + if (!parsed) { + if (selected_completion_string) { + auto completion_cursor = clangmm::CompletionString(selected_completion_string).get_cursor( + clang_tu->cx_tu); + if (completion_cursor) { + auto source_location = completion_cursor.get_source_location(); + auto source_location_offset = source_location.get_offset(); + if (CompletionDialog::get()) + CompletionDialog::get()->hide(); + auto offset = Offset(source_location_offset.line - 1, source_location_offset.index - 1, + source_location.get_path()); + + // Workaround for bug in ArchLinux's clang_getFileName() + // TODO: remove the workaround when this is fixed + auto include_path = filesystem::get_normal_path(offset.file_path); + boost::system::error_code ec; + if (!boost::filesystem::exists(include_path, ec)) + offset.file_path = "/usr/include" / include_path; - auto declaration_location = [this]() { - auto identifier = get_identifier(); - if (identifier) { - auto source_location = identifier.cursor.get_canonical().get_source_location(); - auto offset = source_location.get_offset(); - return Offset(offset.line - 1, offset.index - 1, source_location.get_path()); + return offset; } else { - // If cursor is at an include line, return offset to included file - const static std::regex include_regex("^[ \t]*#[ \t]*include[ \t]*[<\"]([^<>\"]+)[>\"].*$"); - std::smatch sm; - auto line = get_line(); - if (std::regex_match(line, sm, include_regex)) { - struct ClientData { - boost::filesystem::path &file_path; - std::string found_include; - int line_nr; - std::string sm_str; - }; - ClientData client_data{this->file_path, std::string(), - get_buffer()->get_insert()->get_iter().get_line(), sm[1].str()}; - - // Attempt to find the 100% correct include file first - clang_getInclusions(clang_tu->cx_tu, - [](CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, - CXClientData client_data_) { - auto client_data = static_cast<ClientData *>(client_data_); - if (client_data->found_include.empty() && include_len > 0) { - auto source_location = clangmm::SourceLocation(inclusion_stack[0]); - if (static_cast<int>(source_location.get_offset().line) - 1 == - client_data->line_nr && - filesystem::get_normal_path(source_location.get_path()) == - client_data->file_path) - client_data->found_include = clangmm::to_string( - clang_getFileName(included_file)); - } - }, &client_data); - - if (!client_data.found_include.empty()) - return Offset(0, 0, client_data.found_include); - - // Find a matching include file if no include was found previously - clang_getInclusions(clang_tu->cx_tu, - [](CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, - CXClientData client_data_) { - auto client_data = static_cast<ClientData *>(client_data_); - if (client_data->found_include.empty()) { - for (unsigned c = 1; c < include_len; ++c) { - auto source_location = clangmm::SourceLocation(inclusion_stack[c]); - if (static_cast<int>(source_location.get_offset().line) - 1 <= - client_data->line_nr && - filesystem::get_normal_path(source_location.get_path()) == - client_data->file_path) { - auto included_file_str = clangmm::to_string( - clang_getFileName(included_file)); - if (included_file_str.size() >= client_data->sm_str.size() && - included_file_str.compare( - included_file_str.size() - client_data->sm_str.size(), - client_data->sm_str.size(), client_data->sm_str) == 0) { - client_data->found_include = included_file_str; - break; - } - } - } - } - }, &client_data); - - if (!client_data.found_include.empty()) - return Offset(0, 0, client_data.found_include); - } + Info::get().print("No declaration found"); + return Offset(); } - return Offset(); - }; - - get_declaration_location = [this, declaration_location]() { - if (!parsed) { - if (selected_completion_string) { - auto completion_cursor = clangmm::CompletionString(selected_completion_string).get_cursor( - clang_tu->cx_tu); - if (completion_cursor) { - auto source_location = completion_cursor.get_source_location(); - auto source_location_offset = source_location.get_offset(); - if (CompletionDialog::get()) - CompletionDialog::get()->hide(); - auto offset = Offset(source_location_offset.line - 1, source_location_offset.index - 1, - source_location.get_path()); - - // Workaround for bug in ArchLinux's clang_getFileName() - // TODO: remove the workaround when this is fixed - auto include_path = filesystem::get_normal_path(offset.file_path); - boost::system::error_code ec; - if (!boost::filesystem::exists(include_path, ec)) - offset.file_path = "/usr/include" / include_path; - - return offset; - } else { - Info::get().print("No declaration found"); - return Offset(); - } - } + } - Info::get().print("Buffer is parsing"); - return Offset(); - } - auto offset = declaration_location(); - if (!offset) - Info::get().print("No declaration found"); - - // Workaround for bug in ArchLinux's clang_getFileName() - // TODO: remove the workaround when this is fixed - auto include_path = filesystem::get_normal_path(offset.file_path); - boost::system::error_code ec; - if (!boost::filesystem::exists(include_path, ec)) + Info::get().print("Buffer is parsing"); + return Offset(); + } + auto offset = declaration_location(); + if (!offset) + Info::get().print("No declaration found"); + + // Workaround for bug in ArchLinux's clang_getFileName() + // TODO: remove the workaround when this is fixed + auto include_path = filesystem::get_normal_path(offset.file_path); + boost::system::error_code ec; + if (!boost::filesystem::exists(include_path, ec)) + offset.file_path = "/usr/include" / include_path; + + return offset; + }; + + get_type_declaration_location = [this]() { + if (!parsed) { + Info::get().print("Buffer is parsing"); + return Offset(); + } + auto identifier = get_identifier(); + if (identifier) { + auto type_cursor = identifier.cursor.get_type().get_cursor(); + if (type_cursor) { + auto source_location = type_cursor.get_source_location(); + auto path = source_location.get_path(); + if (!path.empty()) { + auto source_location_offset = source_location.get_offset(); + auto offset = Offset(source_location_offset.line - 1, source_location_offset.index - 1, path); + + // Workaround for bug in ArchLinux's clang_getFileName() + // TODO: remove the workaround when this is fixed + auto include_path = filesystem::get_normal_path(offset.file_path); + boost::system::error_code ec; + if (!boost::filesystem::exists(include_path, ec)) offset.file_path = "/usr/include" / include_path; - return offset; - }; - - get_type_declaration_location = [this]() { - if (!parsed) { - Info::get().print("Buffer is parsing"); - return Offset(); - } - auto identifier = get_identifier(); - if (identifier) { - auto type_cursor = identifier.cursor.get_type().get_cursor(); - if (type_cursor) { - auto source_location = type_cursor.get_source_location(); - auto path = source_location.get_path(); - if (!path.empty()) { - auto source_location_offset = source_location.get_offset(); - auto offset = Offset(source_location_offset.line - 1, source_location_offset.index - 1, path); - - // Workaround for bug in ArchLinux's clang_getFileName() - // TODO: remove the workaround when this is fixed - auto include_path = filesystem::get_normal_path(offset.file_path); - boost::system::error_code ec; - if (!boost::filesystem::exists(include_path, ec)) - offset.file_path = "/usr/include" / include_path; - - return offset; - } - } + return offset; } - Info::get().print("No type declaration found"); - return Offset(); - }; - - auto implementation_locations = [this](const Identifier &identifier) { - std::vector<Offset> offsets; - if (identifier) { - wait_parsing(); - - //First, look for a definition cursor that is equal - auto identifier_usr = identifier.cursor.get_usr(); - for (auto &view: views) { - if (auto clang_view = dynamic_cast<Source::ClangView *>(view)) { - for (auto &token: *clang_view->clang_tokens) { - auto cursor = token.get_cursor(); - auto cursor_kind = cursor.get_kind(); - if ((cursor_kind == clangmm::Cursor::Kind::FunctionDecl || - cursor_kind == clangmm::Cursor::Kind::CXXMethod || - cursor_kind == clangmm::Cursor::Kind::Constructor || - cursor_kind == clangmm::Cursor::Kind::Destructor || - cursor_kind == clangmm::Cursor::Kind::FunctionTemplate || - cursor_kind == clangmm::Cursor::Kind::ConversionFunction) && - token.is_identifier()) { - auto token_spelling = token.get_spelling(); - if (identifier.kind == cursor.get_kind() && identifier.spelling == token_spelling && - identifier_usr == cursor.get_usr()) { - if (clang_isCursorDefinition(cursor.cx_cursor)) { - Offset offset; - auto location = cursor.get_source_location(); - auto clang_offset = location.get_offset(); - offset.file_path = location.get_path(); - offset.line = clang_offset.line - 1; - offset.index = clang_offset.index - 1; - offsets.emplace_back(offset); - } - } - } - } - } - } - if (!offsets.empty()) - return offsets; - - //If no implementation was found, try using clang_getCursorDefinition - auto definition = identifier.cursor.get_definition(); - if (definition) { - auto location = definition.get_source_location(); - Offset offset; - offset.file_path = location.get_path(); - auto clang_offset = location.get_offset(); - offset.line = clang_offset.line - 1; - offset.index = clang_offset.index - 1; - offsets.emplace_back(offset); - return offsets; - } - - //If no implementation was found, use declaration if it is a function template - auto canonical = identifier.cursor.get_canonical(); - auto cursor = clang_tu->get_cursor(canonical.get_source_location()); - if (cursor && cursor.get_kind() == clangmm::Cursor::Kind::FunctionTemplate) { - auto location = cursor.get_source_location(); - Offset offset; - offset.file_path = location.get_path(); - auto clang_offset = location.get_offset(); - offset.line = clang_offset.line - 1; - offset.index = clang_offset.index - 1; - offsets.emplace_back(offset); - return offsets; - } - - //If no implementation was found, try using Ctags - auto name = identifier.cursor.get_spelling(); - auto parent = identifier.cursor.get_semantic_parent(); - while (parent && parent.get_kind() != clangmm::Cursor::Kind::TranslationUnit) { - auto spelling = parent.get_spelling() + "::"; - name.insert(0, spelling); - parent = parent.get_semantic_parent(); - } - auto ctags_locations = Ctags::get_locations(this->file_path, name, - identifier.cursor.get_type_description()); - if (!ctags_locations.empty()) { - for (auto &ctags_location: ctags_locations) { - Offset offset; - offset.file_path = ctags_location.file_path; - offset.line = ctags_location.line; - offset.index = ctags_location.index; - offsets.emplace_back(offset); + } + } + Info::get().print("No type declaration found"); + return Offset(); + }; + + auto implementation_locations = [this](const Identifier &identifier) { + std::vector<Offset> offsets; + if (identifier) { + wait_parsing(); + + //First, look for a definition cursor that is equal + auto identifier_usr = identifier.cursor.get_usr(); + for (auto &view: views) { + if (auto clang_view = dynamic_cast<Source::ClangView *>(view)) { + for (auto &token: *clang_view->clang_tokens) { + auto cursor = token.get_cursor(); + auto cursor_kind = cursor.get_kind(); + if ((cursor_kind == clangmm::Cursor::Kind::FunctionDecl || + cursor_kind == clangmm::Cursor::Kind::CXXMethod || + cursor_kind == clangmm::Cursor::Kind::Constructor || + cursor_kind == clangmm::Cursor::Kind::Destructor || + cursor_kind == clangmm::Cursor::Kind::FunctionTemplate || + cursor_kind == clangmm::Cursor::Kind::ConversionFunction) && + token.is_identifier()) { + auto token_spelling = token.get_spelling(); + if (identifier.kind == cursor.get_kind() && identifier.spelling == token_spelling && + identifier_usr == cursor.get_usr()) { + if (clang_isCursorDefinition(cursor.cx_cursor)) { + Offset offset; + auto location = cursor.get_source_location(); + auto clang_offset = location.get_offset(); + offset.file_path = location.get_path(); + offset.line = clang_offset.line - 1; + offset.index = clang_offset.index - 1; + offsets.emplace_back(offset); } - return offsets; + } } + } } + } + if (!offsets.empty()) return offsets; - }; - - get_implementation_locations = [this, implementation_locations]() { - if (!parsed) { - if (selected_completion_string) { - auto completion_cursor = clangmm::CompletionString(selected_completion_string).get_cursor( - clang_tu->cx_tu); - if (completion_cursor) { - auto offsets = implementation_locations( - Identifier(completion_cursor.get_token_spelling(), completion_cursor)); - if (offsets.empty()) { - Info::get().print("No implementation found"); - return std::vector<Offset>(); - } - if (CompletionDialog::get()) - CompletionDialog::get()->hide(); - - // Workaround for bug in ArchLinux's clang_getFileName() - // TODO: remove the workaround when this is fixed - for (auto &offset: offsets) { - auto include_path = filesystem::get_normal_path(offset.file_path); - boost::system::error_code ec; - if (!boost::filesystem::exists(include_path, ec)) - offset.file_path = "/usr/include" / include_path; - } - return offsets; - } else { - Info::get().print("No implementation found"); - return std::vector<Offset>(); - } - } - - Info::get().print("Buffer is parsing"); - return std::vector<Offset>(); + //If no implementation was found, try using clang_getCursorDefinition + auto definition = identifier.cursor.get_definition(); + if (definition) { + auto location = definition.get_source_location(); + Offset offset; + offset.file_path = location.get_path(); + auto clang_offset = location.get_offset(); + offset.line = clang_offset.line - 1; + offset.index = clang_offset.index - 1; + offsets.emplace_back(offset); + return offsets; + } + + //If no implementation was found, use declaration if it is a function template + auto canonical = identifier.cursor.get_canonical(); + auto cursor = clang_tu->get_cursor(canonical.get_source_location()); + if (cursor && cursor.get_kind() == clangmm::Cursor::Kind::FunctionTemplate) { + auto location = cursor.get_source_location(); + Offset offset; + offset.file_path = location.get_path(); + auto clang_offset = location.get_offset(); + offset.line = clang_offset.line - 1; + offset.index = clang_offset.index - 1; + offsets.emplace_back(offset); + return offsets; + } + + //If no implementation was found, try using Ctags + auto name = identifier.cursor.get_spelling(); + auto parent = identifier.cursor.get_semantic_parent(); + while (parent && parent.get_kind() != clangmm::Cursor::Kind::TranslationUnit) { + auto spelling = parent.get_spelling() + "::"; + name.insert(0, spelling); + parent = parent.get_semantic_parent(); + } + auto ctags_locations = Ctags::get_locations(this->file_path, name, + identifier.cursor.get_type_description()); + if (!ctags_locations.empty()) { + for (auto &ctags_location: ctags_locations) { + Offset offset; + offset.file_path = ctags_location.file_path; + offset.line = ctags_location.line; + offset.index = ctags_location.index; + offsets.emplace_back(offset); } - auto offsets = implementation_locations(get_identifier()); - if (offsets.empty()) + return offsets; + } + } + return offsets; + }; + + get_implementation_locations = [this, implementation_locations]() { + if (!parsed) { + if (selected_completion_string) { + auto completion_cursor = clangmm::CompletionString(selected_completion_string).get_cursor( + clang_tu->cx_tu); + if (completion_cursor) { + auto offsets = implementation_locations( + Identifier(completion_cursor.get_token_spelling(), completion_cursor)); + if (offsets.empty()) { Info::get().print("No implementation found"); + return std::vector<Offset>(); + } + if (CompletionDialog::get()) + CompletionDialog::get()->hide(); - // Workaround for bug in ArchLinux's clang_getFileName() - // TODO: remove the workaround when this is fixed - for (auto &offset: offsets) { + // Workaround for bug in ArchLinux's clang_getFileName() + // TODO: remove the workaround when this is fixed + for (auto &offset: offsets) { auto include_path = filesystem::get_normal_path(offset.file_path); boost::system::error_code ec; if (!boost::filesystem::exists(include_path, ec)) - offset.file_path = "/usr/include" / include_path; - } + offset.file_path = "/usr/include" / include_path; + } - return offsets; - }; - - get_declaration_or_implementation_locations = [this, declaration_location, implementation_locations]() { - if (!parsed) { - Info::get().print("Buffer is parsing"); - return std::vector<Offset>(); - } - - std::vector<Offset> offsets; - - bool is_implementation = false; - auto iter = get_buffer()->get_insert()->get_iter(); - auto line = static_cast<unsigned>(iter.get_line()); - auto index = static_cast<unsigned>(iter.get_line_index()); - for (size_t c = 0; c < clang_tokens->size(); ++c) { - auto &token = (*clang_tokens)[c]; - if (token.is_identifier()) { - auto &token_offsets = clang_tokens_offsets[c]; - if (line == token_offsets.first.line - 1 && index >= token_offsets.first.index - 1 && - index <= token_offsets.second.index - 1) { - if (clang_isCursorDefinition(token.get_cursor().cx_cursor) > 0) - is_implementation = true; - break; - } - } - } - // If cursor is at implementation, return declaration_location - if (is_implementation) { - auto offset = declaration_location(); - if (offset) - offsets.emplace_back(offset); + return offsets; } else { - auto implementation_offsets = implementation_locations(get_identifier()); - if (!implementation_offsets.empty()) { - offsets = std::move(implementation_offsets); - } else { - auto offset = declaration_location(); - if (offset) - offsets.emplace_back(offset); - } + Info::get().print("No implementation found"); + return std::vector<Offset>(); } + } + + Info::get().print("Buffer is parsing"); + return std::vector<Offset>(); + } + auto offsets = implementation_locations(get_identifier()); + if (offsets.empty()) + Info::get().print("No implementation found"); + + // Workaround for bug in ArchLinux's clang_getFileName() + // TODO: remove the workaround when this is fixed + for (auto &offset: offsets) { + auto include_path = filesystem::get_normal_path(offset.file_path); + boost::system::error_code ec; + if (!boost::filesystem::exists(include_path, ec)) + offset.file_path = "/usr/include" / include_path; + } - if (offsets.empty()) - Info::get().print("No declaration or implementation found"); + return offsets; + }; - // Workaround for bug in ArchLinux's clang_getFileName() - // TODO: remove the workaround when this is fixed - for (auto &offset: offsets) { - auto include_path = filesystem::get_normal_path(offset.file_path); - boost::system::error_code ec; - if (!boost::filesystem::exists(include_path, ec)) - offset.file_path = "/usr/include" / include_path; - } + get_declaration_or_implementation_locations = [this, declaration_location, implementation_locations]() { + if (!parsed) { + Info::get().print("Buffer is parsing"); + return std::vector<Offset>(); + } - return offsets; - }; + std::vector<Offset> offsets; - get_usages = [this]() { - std::vector<std::pair<Offset, std::string> > usages; - if (!parsed) { - Info::get().print("Buffer is parsing"); - return usages; + bool is_implementation = false; + auto iter = get_buffer()->get_insert()->get_iter(); + auto line = static_cast<unsigned>(iter.get_line()); + auto index = static_cast<unsigned>(iter.get_line_index()); + for (size_t c = 0; c < clang_tokens->size(); ++c) { + auto &token = (*clang_tokens)[c]; + if (token.is_identifier()) { + auto &token_offsets = clang_tokens_offsets[c]; + if (line == token_offsets.first.line - 1 && index >= token_offsets.first.index - 1 && + index <= token_offsets.second.index - 1) { + if (clang_isCursorDefinition(token.get_cursor().cx_cursor) > 0) + is_implementation = true; + break; } - auto identifier = get_identifier(); - if (identifier) { - wait_parsing(); - - auto embolden_token = [](std::string &line, unsigned token_start_pos, unsigned token_end_pos) { - //markup token as bold - size_t pos = 0; - while ((pos = line.find('&', pos)) != std::string::npos) { - size_t pos2 = line.find(';', pos + 2); - if (token_start_pos > pos) { - token_start_pos += pos2 - pos; - token_end_pos += pos2 - pos; - } else if (token_end_pos > pos) - token_end_pos += pos2 - pos; - else - break; - pos = pos2 + 1; - } - line.insert(token_end_pos, "</b>"); - line.insert(token_start_pos, "<b>"); - - size_t start_pos = 0; - while (start_pos < line.size() && (line[start_pos] == ' ' || line[start_pos] == '\t')) - ++start_pos; - if (start_pos > 0) - line.erase(0, start_pos); - }; + } + } + // If cursor is at implementation, return declaration_location + if (is_implementation) { + auto offset = declaration_location(); + if (offset) + offsets.emplace_back(offset); + } else { + auto implementation_offsets = implementation_locations(get_identifier()); + if (!implementation_offsets.empty()) { + offsets = std::move(implementation_offsets); + } else { + auto offset = declaration_location(); + if (offset) + offsets.emplace_back(offset); + } + } - std::vector<clangmm::TranslationUnit *> translation_units; - translation_units.emplace_back(clang_tu.get()); - for (auto &view: views) { - if (view != this) { - if (auto clang_view = dynamic_cast<Source::ClangView *>(view)) - translation_units.emplace_back(clang_view->clang_tu.get()); - } - } + if (offsets.empty()) + Info::get().print("No declaration or implementation found"); - auto build = Project::Build::create(this->file_path); - auto usages_clang = Usages::Clang::get_usages(build->project_path, build->get_default_path(), - build->get_debug_path(), {identifier.spelling}, - {identifier.cursor}, translation_units); - for (auto &usage: usages_clang) { - for (size_t c = 0; c < usage.offsets.size(); ++c) { - std::string line = Glib::Markup::escape_text(usage.lines[c]); - embolden_token(line, usage.offsets[c].first.index - 1, usage.offsets[c].second.index - 1); - usages.emplace_back( - Offset(usage.offsets[c].first.line - 1, usage.offsets[c].first.index - 1, usage.path), - line); - } - } - } + // Workaround for bug in ArchLinux's clang_getFileName() + // TODO: remove the workaround when this is fixed + for (auto &offset: offsets) { + auto include_path = filesystem::get_normal_path(offset.file_path); + boost::system::error_code ec; + if (!boost::filesystem::exists(include_path, ec)) + offset.file_path = "/usr/include" / include_path; + } - if (usages.empty()) - Info::get().print("No symbol found at current cursor location"); - return usages; - }; + return offsets; + }; - get_method = [this] { - if (!parsed) { - Info::get().print("Buffer is parsing"); - return std::string(); + get_usages = [this]() { + std::vector<std::pair<Offset, std::string> > usages; + if (!parsed) { + Info::get().print("Buffer is parsing"); + return usages; + } + auto identifier = get_identifier(); + if (identifier) { + wait_parsing(); + + auto embolden_token = [](std::string &line, unsigned token_start_pos, unsigned token_end_pos) { + //markup token as bold + size_t pos = 0; + while ((pos = line.find('&', pos)) != std::string::npos) { + size_t pos2 = line.find(';', pos + 2); + if (token_start_pos > pos) { + token_start_pos += pos2 - pos; + token_end_pos += pos2 - pos; + } else if (token_end_pos > pos) + token_end_pos += pos2 - pos; + else + break; + pos = pos2 + 1; } - auto iter = get_buffer()->get_insert()->get_iter(); - auto line = static_cast<unsigned>(iter.get_line()); - auto index = static_cast<unsigned>(iter.get_line_index()); - for (size_t c = clang_tokens->size() - 1; c != static_cast<size_t>(-1); --c) { - auto &token = (*clang_tokens)[c]; - if (token.is_identifier()) { - auto &token_offsets = clang_tokens_offsets[c]; - if (line == token_offsets.first.line - 1 && index >= token_offsets.first.index - 1 && - index <= token_offsets.second.index - 1) { - auto token_spelling = token.get_spelling(); - if (!token_spelling.empty() && - (token_spelling.size() > 1 || (token_spelling.back() >= 'a' && token_spelling.back() <= 'z') || - (token_spelling.back() >= 'A' && token_spelling.back() <= 'Z') || - token_spelling.back() == '_')) { - auto cursor = token.get_cursor(); - auto kind = cursor.get_kind(); - if (kind == clangmm::Cursor::Kind::FunctionDecl || kind == clangmm::Cursor::Kind::CXXMethod || - kind == clangmm::Cursor::Kind::Constructor || kind == clangmm::Cursor::Kind::Destructor || - kind == clangmm::Cursor::Kind::ConversionFunction) { - auto referenced = cursor.get_referenced(); - if (referenced && referenced == cursor) { - std::string result; - std::string specifier; - if (kind == clangmm::Cursor::Kind::FunctionDecl || - kind == clangmm::Cursor::Kind::CXXMethod) { - auto start_offset = cursor.get_source_range().get_start().get_offset(); - auto end_offset = token_offsets.first; - - // To accurately get result type with needed namespace and class/struct names: - int angle_brackets = 0; - for (size_t c = 0; c < clang_tokens->size(); ++c) { - auto &token = (*clang_tokens)[c]; - auto &token_offsets = clang_tokens_offsets[c]; - if ((token_offsets.first.line == start_offset.line && - token_offsets.second.line != end_offset.line && - token_offsets.first.index >= start_offset.index) || - (token_offsets.first.line > start_offset.line && - token_offsets.second.line < end_offset.line) || - (token_offsets.first.line != start_offset.line && - token_offsets.second.line == end_offset.line && - token_offsets.second.index <= end_offset.index) || - (token_offsets.first.line == start_offset.line && - token_offsets.second.line == end_offset.line && - token_offsets.first.index >= start_offset.index && - token_offsets.second.index <= end_offset.index)) { - auto token_spelling = token.get_spelling(); - if (token.get_kind() == clangmm::Token::Kind::Identifier) { - if (c == 0 || (*clang_tokens)[c - 1].get_spelling() != "::") { - auto name = token_spelling; - auto parent = token.get_cursor().get_type().get_cursor().get_semantic_parent(); - while (parent && parent.get_kind() != - clangmm::Cursor::Kind::TranslationUnit) { - auto spelling = parent.get_token_spelling(); - name.insert(0, spelling + "::"); - parent = parent.get_semantic_parent(); - } - result += name; - } else - result += token_spelling; - } else if ((token_spelling == "*" || token_spelling == "&") && - !result.empty() && result.back() != '*' && result.back() != '&') - result += ' ' + token_spelling; - else if (token_spelling == "extern" || token_spelling == "static" || - token_spelling == "virtual" || token_spelling == "friend") - continue; - else if (token_spelling == "," || - (token_spelling.size() > 1 && token_spelling != "::" && - angle_brackets == 0)) - result += token_spelling + ' '; - else { - if (token_spelling == "<") - ++angle_brackets; - else if (token_spelling == ">") - --angle_brackets; - result += token_spelling; - } - } - } + line.insert(token_end_pos, "</b>"); + line.insert(token_start_pos, "<b>"); + + size_t start_pos = 0; + while (start_pos < line.size() && (line[start_pos] == ' ' || line[start_pos] == '\t')) + ++start_pos; + if (start_pos > 0) + line.erase(0, start_pos); + }; + + std::vector<clangmm::TranslationUnit *> translation_units; + translation_units.emplace_back(clang_tu.get()); + for (auto &view: views) { + if (view != this) { + if (auto clang_view = dynamic_cast<Source::ClangView *>(view)) + translation_units.emplace_back(clang_view->clang_tu.get()); + } + } + + auto build = Project::Build::create(this->file_path); + auto usages_clang = Usages::Clang::get_usages(build->project_path, build->get_default_path(), + build->get_debug_path(), {identifier.spelling}, + {identifier.cursor}, translation_units); + for (auto &usage: usages_clang) { + for (size_t c = 0; c < usage.offsets.size(); ++c) { + std::string line = Glib::Markup::escape_text(usage.lines[c]); + embolden_token(line, usage.offsets[c].first.index - 1, usage.offsets[c].second.index - 1); + usages.emplace_back( + Offset(usage.offsets[c].first.line - 1, usage.offsets[c].first.index - 1, usage.path), + line); + } + } + } - if (!result.empty() && result.back() != '*' && result.back() != '&' && - result.back() != ' ') - result += ' '; + if (usages.empty()) + Info::get().print("No symbol found at current cursor location"); + return usages; + }; - if (clang_CXXMethod_isConst(cursor.cx_cursor)) - specifier = " const"; - } + get_method = [this] { + if (!parsed) { + Info::get().print("Buffer is parsing"); + return std::string(); + } + auto iter = get_buffer()->get_insert()->get_iter(); + auto line = static_cast<unsigned>(iter.get_line()); + auto index = static_cast<unsigned>(iter.get_line_index()); + for (size_t c = clang_tokens->size() - 1; c != static_cast<size_t>(-1); --c) { + auto &token = (*clang_tokens)[c]; + if (token.is_identifier()) { + auto &token_offsets = clang_tokens_offsets[c]; + if (line == token_offsets.first.line - 1 && index >= token_offsets.first.index - 1 && + index <= token_offsets.second.index - 1) { + auto token_spelling = token.get_spelling(); + if (!token_spelling.empty() && + (token_spelling.size() > 1 || (token_spelling.back() >= 'a' && token_spelling.back() <= 'z') || + (token_spelling.back() >= 'A' && token_spelling.back() <= 'Z') || + token_spelling.back() == '_')) { + auto cursor = token.get_cursor(); + auto kind = cursor.get_kind(); + if (kind == clangmm::Cursor::Kind::FunctionDecl || kind == clangmm::Cursor::Kind::CXXMethod || + kind == clangmm::Cursor::Kind::Constructor || kind == clangmm::Cursor::Kind::Destructor || + kind == clangmm::Cursor::Kind::ConversionFunction) { + auto referenced = cursor.get_referenced(); + if (referenced && referenced == cursor) { + std::string result; + std::string specifier; + if (kind == clangmm::Cursor::Kind::FunctionDecl || + kind == clangmm::Cursor::Kind::CXXMethod) { + auto start_offset = cursor.get_source_range().get_start().get_offset(); + auto end_offset = token_offsets.first; + + // To accurately get result type with needed namespace and class/struct names: + int angle_brackets = 0; + for (size_t c = 0; c < clang_tokens->size(); ++c) { + auto &token = (*clang_tokens)[c]; + auto &token_offsets = clang_tokens_offsets[c]; + if ((token_offsets.first.line == start_offset.line && + token_offsets.second.line != end_offset.line && + token_offsets.first.index >= start_offset.index) || + (token_offsets.first.line > start_offset.line && + token_offsets.second.line < end_offset.line) || + (token_offsets.first.line != start_offset.line && + token_offsets.second.line == end_offset.line && + token_offsets.second.index <= end_offset.index) || + (token_offsets.first.line == start_offset.line && + token_offsets.second.line == end_offset.line && + token_offsets.first.index >= start_offset.index && + token_offsets.second.index <= end_offset.index)) { + auto token_spelling = token.get_spelling(); + if (token.get_kind() == clangmm::Token::Kind::Identifier) { + if (c == 0 || (*clang_tokens)[c - 1].get_spelling() != "::") { + auto name = token_spelling; + auto parent = token.get_cursor().get_type().get_cursor().get_semantic_parent(); + while (parent && parent.get_kind() != + clangmm::Cursor::Kind::TranslationUnit) { + auto spelling = parent.get_token_spelling(); + name.insert(0, spelling + "::"); + parent = parent.get_semantic_parent(); + } + result += name; + } else + result += token_spelling; + } else if ((token_spelling == "*" || token_spelling == "&") && + !result.empty() && result.back() != '*' && result.back() != '&') + result += ' ' + token_spelling; + else if (token_spelling == "extern" || token_spelling == "static" || + token_spelling == "virtual" || token_spelling == "friend") + continue; + else if (token_spelling == "," || + (token_spelling.size() > 1 && token_spelling != "::" && + angle_brackets == 0)) + result += token_spelling + ' '; + else { + if (token_spelling == "<") + ++angle_brackets; + else if (token_spelling == ">") + --angle_brackets; + result += token_spelling; + } + } + } - auto name = cursor.get_spelling(); - auto parent = cursor.get_semantic_parent(); - std::vector<std::string> semantic_parents; - while (parent && parent.get_kind() != clangmm::Cursor::Kind::TranslationUnit) { - auto spelling = parent.get_spelling() + "::"; - if (spelling != "::") { - semantic_parents.emplace_back(spelling); - name.insert(0, spelling); - } - parent = parent.get_semantic_parent(); - } + if (!result.empty() && result.back() != '*' && result.back() != '&' && + result.back() != ' ') + result += ' '; - std::string arguments; - for (auto &argument_cursor: cursor.get_arguments()) { - auto argument_type = argument_cursor.get_type().get_spelling(); - for (auto it = semantic_parents.rbegin(); it != semantic_parents.rend(); ++it) { - size_t pos = argument_type.find(*it); - if (pos == 0 || (pos != std::string::npos && argument_type[pos - 1] == ' ')) - argument_type.erase(pos, it->size()); - } - auto argument = argument_cursor.get_spelling(); - if (!arguments.empty()) - arguments += ", "; - arguments += argument_type; - if (!arguments.empty() && arguments.back() != '*' && arguments.back() != '&') - arguments += ' '; - arguments += argument; - } - return result + name + '(' + arguments + ")" + specifier + " {}"; - } - } - } + if (clang_CXXMethod_isConst(cursor.cx_cursor)) + specifier = " const"; } - } - } - Info::get().print("No method found at current cursor location"); - return std::string(); - }; - - get_methods = [this]() { - std::vector<std::pair<Offset, std::string> > methods; - if (!parsed) { - Info::get().print("Buffer is parsing"); - return methods; - } - clangmm::Offset last_offset{static_cast<unsigned>(-1), static_cast<unsigned>(-1)}; - for (auto &token: *clang_tokens) { - if (token.is_identifier()) { - auto cursor = token.get_cursor(); - auto kind = cursor.get_kind(); - if (kind == clangmm::Cursor::Kind::FunctionDecl || kind == clangmm::Cursor::Kind::CXXMethod || - kind == clangmm::Cursor::Kind::Constructor || kind == clangmm::Cursor::Kind::Destructor || - kind == clangmm::Cursor::Kind::FunctionTemplate || - kind == clangmm::Cursor::Kind::ConversionFunction) { - auto offset = cursor.get_source_location().get_offset(); - if (offset == last_offset) - continue; - last_offset = offset; - - std::string method; - if (kind != clangmm::Cursor::Kind::Constructor && kind != clangmm::Cursor::Kind::Destructor) { - method += cursor.get_type().get_result().get_spelling(); - auto pos = method.find(" "); - if (pos != std::string::npos) - method.erase(pos, 1); - method += " "; - } - method += cursor.get_display_name(); - std::string prefix; - auto parent = cursor.get_semantic_parent(); - while (parent && parent.get_kind() != clangmm::Cursor::Kind::TranslationUnit) { - prefix.insert(0, parent.get_display_name() + (prefix.empty() ? "" : "::")); - parent = parent.get_semantic_parent(); - } + auto name = cursor.get_spelling(); + auto parent = cursor.get_semantic_parent(); + std::vector<std::string> semantic_parents; + while (parent && parent.get_kind() != clangmm::Cursor::Kind::TranslationUnit) { + auto spelling = parent.get_spelling() + "::"; + if (spelling != "::") { + semantic_parents.emplace_back(spelling); + name.insert(0, spelling); + } + parent = parent.get_semantic_parent(); + } - method = Glib::Markup::escape_text(method); - //Add bold method token - size_t token_end_pos = method.find('('); - if (token_end_pos == std::string::npos) - continue; - auto token_start_pos = token_end_pos; - while (token_start_pos != 0 && method[token_start_pos] != ' ') - --token_start_pos; - method.insert(token_end_pos, "</b>"); - method.insert(token_start_pos, "<b>"); - - if (!prefix.empty()) - prefix += ':'; - prefix += std::to_string(offset.line) + ": "; - prefix = Glib::Markup::escape_text(prefix); - - methods.emplace_back(Offset(offset.line - 1, offset.index - 1), prefix + method); + std::string arguments; + for (auto &argument_cursor: cursor.get_arguments()) { + auto argument_type = argument_cursor.get_type().get_spelling(); + for (auto it = semantic_parents.rbegin(); it != semantic_parents.rend(); ++it) { + size_t pos = argument_type.find(*it); + if (pos == 0 || (pos != std::string::npos && argument_type[pos - 1] == ' ')) + argument_type.erase(pos, it->size()); + } + auto argument = argument_cursor.get_spelling(); + if (!arguments.empty()) + arguments += ", "; + arguments += argument_type; + if (!arguments.empty() && arguments.back() != '*' && arguments.back() != '&') + arguments += ' '; + arguments += argument; } + return result + name + '(' + arguments + ")" + specifier + " {}"; + } } + } } - if (methods.empty()) - Info::get().print("No methods found in current buffer"); - - return methods; - }; - - get_token_data = [this]() -> std::vector<std::string> { - clangmm::Cursor cursor; - - std::vector<std::string> data; - if (!parsed) { - if (selected_completion_string) { - cursor = clangmm::CompletionString(selected_completion_string).get_cursor(clang_tu->cx_tu); - if (!cursor) { - Info::get().print("No symbol found"); - return data; - } - } else { - Info::get().print("Buffer is parsing"); - return data; - } + } + } + Info::get().print("No method found at current cursor location"); + return std::string(); + }; + + get_methods = [this]() { + std::vector<std::pair<Offset, std::string> > methods; + if (!parsed) { + Info::get().print("Buffer is parsing"); + return methods; + } + clangmm::Offset last_offset{static_cast<unsigned>(-1), static_cast<unsigned>(-1)}; + for (auto &token: *clang_tokens) { + if (token.is_identifier()) { + auto cursor = token.get_cursor(); + auto kind = cursor.get_kind(); + if (kind == clangmm::Cursor::Kind::FunctionDecl || kind == clangmm::Cursor::Kind::CXXMethod || + kind == clangmm::Cursor::Kind::Constructor || kind == clangmm::Cursor::Kind::Destructor || + kind == clangmm::Cursor::Kind::FunctionTemplate || + kind == clangmm::Cursor::Kind::ConversionFunction) { + auto offset = cursor.get_source_location().get_offset(); + if (offset == last_offset) + continue; + last_offset = offset; + + std::string method; + if (kind != clangmm::Cursor::Kind::Constructor && kind != clangmm::Cursor::Kind::Destructor) { + method += cursor.get_type().get_result().get_spelling(); + auto pos = method.find(" "); + if (pos != std::string::npos) + method.erase(pos, 1); + method += " "; + } + method += cursor.get_display_name(); + + std::string prefix; + auto parent = cursor.get_semantic_parent(); + while (parent && parent.get_kind() != clangmm::Cursor::Kind::TranslationUnit) { + prefix.insert(0, parent.get_display_name() + (prefix.empty() ? "" : "::")); + parent = parent.get_semantic_parent(); + } + + method = Glib::Markup::escape_text(method); + //Add bold method token + size_t token_end_pos = method.find('('); + if (token_end_pos == std::string::npos) + continue; + auto token_start_pos = token_end_pos; + while (token_start_pos != 0 && method[token_start_pos] != ' ') + --token_start_pos; + method.insert(token_end_pos, "</b>"); + method.insert(token_start_pos, "<b>"); + + if (!prefix.empty()) + prefix += ':'; + prefix += std::to_string(offset.line) + ": "; + prefix = Glib::Markup::escape_text(prefix); + + methods.emplace_back(Offset(offset.line - 1, offset.index - 1), prefix + method); } + } + } + if (methods.empty()) + Info::get().print("No methods found in current buffer"); + + return methods; + }; + + get_token_data = [this]() -> std::vector<std::string> { + clangmm::Cursor cursor; + std::vector<std::string> data; + if (!parsed) { + if (selected_completion_string) { + cursor = clangmm::CompletionString(selected_completion_string).get_cursor(clang_tu->cx_tu); if (!cursor) { - auto identifier = get_identifier(); - if (identifier) - cursor = identifier.cursor.get_canonical(); + Info::get().print("No symbol found"); + return data; } + } else { + Info::get().print("Buffer is parsing"); + return data; + } + } - if (cursor) { - data.emplace_back("clang"); - - std::string symbol; - clangmm::Cursor last_cursor; - auto it = data.end(); - do { - auto token_spelling = cursor.get_token_spelling(); - if (!token_spelling.empty() && token_spelling != "__1" && token_spelling.compare(0, 5, "__cxx") != 0) { - it = data.emplace(it, token_spelling); - if (symbol.empty()) - symbol = token_spelling; - else - symbol.insert(0, token_spelling + "::"); - } - last_cursor = cursor; - cursor = cursor.get_semantic_parent(); - } while (cursor.get_kind() != clangmm::Cursor::Kind::TranslationUnit); - - if (last_cursor.get_kind() != clangmm::Cursor::Kind::Namespace) - data.emplace(++data.begin(), ""); + if (!cursor) { + auto identifier = get_identifier(); + if (identifier) + cursor = identifier.cursor.get_canonical(); + } - auto url = Documentation::CppReference::get_url(symbol); - if (!url.empty()) - return {url}; + if (cursor) { + data.emplace_back("clang"); + + std::string symbol; + clangmm::Cursor last_cursor; + auto it = data.end(); + do { + auto token_spelling = cursor.get_token_spelling(); + if (!token_spelling.empty() && token_spelling != "__1" && token_spelling.compare(0, 5, "__cxx") != 0) { + it = data.emplace(it, token_spelling); + if (symbol.empty()) + symbol = token_spelling; + else + symbol.insert(0, token_spelling + "::"); } + last_cursor = cursor; + cursor = cursor.get_semantic_parent(); + } while (cursor.get_kind() != clangmm::Cursor::Kind::TranslationUnit); - if (data.empty()) - Info::get().print("No symbol found at current cursor location"); + if (last_cursor.get_kind() != clangmm::Cursor::Kind::Namespace) + data.emplace(++data.begin(), ""); - return data; - }; + auto url = Documentation::CppReference::get_url(symbol); + if (!url.empty()) + return {url}; + } - goto_next_diagnostic = [this]() { - if (!parsed) { - Info::get().print("Buffer is parsing"); - return; - } - place_cursor_at_next_diagnostic(); - }; + if (data.empty()) + Info::get().print("No symbol found at current cursor location"); - get_fix_its = [this]() { - if (!parsed) { - Info::get().print("Buffer is parsing"); - return std::vector<FixIt>(); - } - if (fix_its.empty()) - Info::get().print("No fix-its found in current buffer"); - return fix_its; - }; - - get_documentation_template = [this]() { - if (!parsed) { - Info::get().print("Buffer is parsing"); - return std::tuple<Source::Offset, std::string, size_t>(Source::Offset(), "", 0); - } - auto identifier = get_identifier(); - if (identifier) { - auto cursor = identifier.cursor.get_canonical(); - if (!clang_Range_isNull(clang_Cursor_getCommentRange(cursor.cx_cursor))) { - Info::get().print("Symbol is already documented"); - return std::tuple<Source::Offset, std::string, size_t>(Source::Offset(), "", 0); - } - auto clang_offsets = cursor.get_source_range().get_offsets(); - auto source_offset = Offset(clang_offsets.first.line - 1, 0, cursor.get_source_location().get_path()); - std::string tabs; - for (size_t c = 0; c < clang_offsets.first.index - 1; ++c) - tabs += ' '; - auto first_line = tabs + "/**\n"; - auto second_line = tabs + " * \n"; - auto iter_offset = first_line.size() + second_line.size() - 1; - - std::string param_lines; - for (int c = 0; c < clang_Cursor_getNumArguments(cursor.cx_cursor); ++c) - param_lines += tabs + " * @param " + - clangmm::Cursor(clang_Cursor_getArgument(cursor.cx_cursor, c)).get_spelling() + '\n'; - - std::string return_line; - auto return_spelling = cursor.get_type().get_result().get_spelling(); - if (!return_spelling.empty() && return_spelling != "void") - return_line += tabs + " * @return\n"; - - auto documentation = first_line + second_line; - if (!param_lines.empty() || !return_line.empty()) - documentation += tabs + " *\n"; - - documentation += param_lines + return_line + tabs + " */\n"; - - return std::tuple<Source::Offset, std::string, size_t>(source_offset, documentation, iter_offset); - } else { - Info::get().print("No symbol found at current cursor location"); - return std::tuple<Source::Offset, std::string, size_t>(Source::Offset(), "", 0); - } - }; + return data; + }; + + goto_next_diagnostic = [this]() { + if (!parsed) { + Info::get().print("Buffer is parsing"); + return; + } + place_cursor_at_next_diagnostic(); + }; + + get_fix_its = [this]() { + if (!parsed) { + Info::get().print("Buffer is parsing"); + return std::vector<FixIt>(); + } + if (fix_its.empty()) + Info::get().print("No fix-its found in current buffer"); + return fix_its; + }; + + get_documentation_template = [this]() { + if (!parsed) { + Info::get().print("Buffer is parsing"); + return std::tuple<Source::Offset, std::string, size_t>(Source::Offset(), "", 0); + } + auto identifier = get_identifier(); + if (identifier) { + auto cursor = identifier.cursor.get_canonical(); + if (!clang_Range_isNull(clang_Cursor_getCommentRange(cursor.cx_cursor))) { + Info::get().print("Symbol is already documented"); + return std::tuple<Source::Offset, std::string, size_t>(Source::Offset(), "", 0); + } + auto clang_offsets = cursor.get_source_range().get_offsets(); + auto source_offset = Offset(clang_offsets.first.line - 1, 0, cursor.get_source_location().get_path()); + std::string tabs; + for (size_t c = 0; c < clang_offsets.first.index - 1; ++c) + tabs += ' '; + auto first_line = tabs + "/**\n"; + auto second_line = tabs + " * \n"; + auto iter_offset = first_line.size() + second_line.size() - 1; + + std::string param_lines; + for (int c = 0; c < clang_Cursor_getNumArguments(cursor.cx_cursor); ++c) + param_lines += tabs + " * @param " + + clangmm::Cursor(clang_Cursor_getArgument(cursor.cx_cursor, c)).get_spelling() + '\n'; + + std::string return_line; + auto return_spelling = cursor.get_type().get_result().get_spelling(); + if (!return_spelling.empty() && return_spelling != "void") + return_line += tabs + " * @return\n"; + + auto documentation = first_line + second_line; + if (!param_lines.empty() || !return_line.empty()) + documentation += tabs + " *\n"; + + documentation += param_lines + return_line + tabs + " */\n"; + + return std::tuple<Source::Offset, std::string, size_t>(source_offset, documentation, iter_offset); + } else { + Info::get().print("No symbol found at current cursor location"); + return std::tuple<Source::Offset, std::string, size_t>(Source::Offset(), "", 0); + } + }; } Source::ClangViewRefactor::Identifier Source::ClangViewRefactor::get_identifier() { - if (!parsed) - return Identifier(); - auto iter = get_buffer()->get_insert()->get_iter(); - auto line = static_cast<unsigned>(iter.get_line()); - auto index = static_cast<unsigned>(iter.get_line_index()); - for (size_t c = clang_tokens->size() - 1; c != static_cast<size_t>(-1); --c) { - auto &token = (*clang_tokens)[c]; - if (token.is_identifier()) { - auto &token_offsets = clang_tokens_offsets[c]; - if (line == token_offsets.first.line - 1 && index >= token_offsets.first.index - 1 && - index <= token_offsets.second.index - 1) { - auto referenced = token.get_cursor().get_referenced(); - if (referenced) - return Identifier(token.get_spelling(), referenced); - } - } - } + if (!parsed) return Identifier(); + auto iter = get_buffer()->get_insert()->get_iter(); + auto line = static_cast<unsigned>(iter.get_line()); + auto index = static_cast<unsigned>(iter.get_line_index()); + for (size_t c = clang_tokens->size() - 1; c != static_cast<size_t>(-1); --c) { + auto &token = (*clang_tokens)[c]; + if (token.is_identifier()) { + auto &token_offsets = clang_tokens_offsets[c]; + if (line == token_offsets.first.line - 1 && index >= token_offsets.first.index - 1 && + index <= token_offsets.second.index - 1) { + auto referenced = token.get_cursor().get_referenced(); + if (referenced) + return Identifier(token.get_spelling(), referenced); + } + } + } + return Identifier(); } void Source::ClangViewRefactor::wait_parsing() { - std::unique_ptr<Dialog::Message> message; - std::vector<Source::ClangView *> clang_views; - for (auto &view: views) { - if (auto clang_view = dynamic_cast<Source::ClangView *>(view)) { - if (!clang_view->parsed && !clang_view->selected_completion_string) { - clang_views.emplace_back(clang_view); - if (!message) - message = std::make_unique<Dialog::Message>("Please wait while all buffers finish parsing"); - } - } + std::unique_ptr<Dialog::Message> message; + std::vector<Source::ClangView *> clang_views; + for (auto &view: views) { + if (auto clang_view = dynamic_cast<Source::ClangView *>(view)) { + if (!clang_view->parsed && !clang_view->selected_completion_string) { + clang_views.emplace_back(clang_view); + if (!message) + message = std::make_unique<Dialog::Message>("Please wait while all buffers finish parsing"); + } } - if (message) { - for (;;) { - while (Gtk::Main::events_pending()) - Gtk::Main::iteration(false); - bool all_parsed = true; - for (auto &clang_view: clang_views) { - if (!clang_view->parsed) { - all_parsed = false; - break; - } - } - if (all_parsed) - break; - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + if (message) { + for (;;) { + while (Gtk::Main::events_pending()) + Gtk::Main::iteration(false); + bool all_parsed = true; + for (auto &clang_view: clang_views) { + if (!clang_view->parsed) { + all_parsed = false; + break; } - message->hide(); + } + if (all_parsed) + break; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } + message->hide(); + } } void Source::ClangViewRefactor::tag_similar_identifiers(const Identifier &identifier) { - if (parsed) { - if (identifier && last_tagged_identifier != identifier) { - for (auto &mark: similar_identifiers_marks) { - get_buffer()->remove_tag(similar_identifiers_tag, mark.first->get_iter(), mark.second->get_iter()); - get_buffer()->delete_mark(mark.first); - get_buffer()->delete_mark(mark.second); - } - similar_identifiers_marks.clear(); - auto offsets = clang_tokens->get_similar_token_offsets(identifier.kind, identifier.spelling, - identifier.cursor.get_all_usr_extended()); - for (auto &offset: offsets) { - auto start_iter = get_buffer()->get_iter_at_line_index(offset.first.line - 1, offset.first.index - 1); - auto end_iter = get_buffer()->get_iter_at_line_index(offset.second.line - 1, offset.second.index - 1); - get_buffer()->apply_tag(similar_identifiers_tag, start_iter, end_iter); - similar_identifiers_marks.emplace_back(get_buffer()->create_mark(start_iter), - get_buffer()->create_mark(end_iter)); - } - last_tagged_identifier = identifier; - } + if (parsed) { + if (identifier && last_tagged_identifier != identifier) { + for (auto &mark: similar_identifiers_marks) { + get_buffer()->remove_tag(similar_identifiers_tag, mark.first->get_iter(), mark.second->get_iter()); + get_buffer()->delete_mark(mark.first); + get_buffer()->delete_mark(mark.second); + } + similar_identifiers_marks.clear(); + auto offsets = clang_tokens->get_similar_token_offsets(identifier.kind, identifier.spelling, + identifier.cursor.get_all_usr_extended()); + for (auto &offset: offsets) { + auto start_iter = get_buffer()->get_iter_at_line_index(offset.first.line - 1, offset.first.index - 1); + auto end_iter = get_buffer()->get_iter_at_line_index(offset.second.line - 1, offset.second.index - 1); + get_buffer()->apply_tag(similar_identifiers_tag, start_iter, end_iter); + similar_identifiers_marks.emplace_back(get_buffer()->create_mark(start_iter), + get_buffer()->create_mark(end_iter)); + } + last_tagged_identifier = identifier; } - if (!identifier && last_tagged_identifier) { - for (auto &mark: similar_identifiers_marks) { - get_buffer()->remove_tag(similar_identifiers_tag, mark.first->get_iter(), mark.second->get_iter()); - get_buffer()->delete_mark(mark.first); - get_buffer()->delete_mark(mark.second); - } - similar_identifiers_marks.clear(); - last_tagged_identifier = Identifier(); + } + if (!identifier && last_tagged_identifier) { + for (auto &mark: similar_identifiers_marks) { + get_buffer()->remove_tag(similar_identifiers_tag, mark.first->get_iter(), mark.second->get_iter()); + get_buffer()->delete_mark(mark.first); + get_buffer()->delete_mark(mark.second); } + similar_identifiers_marks.clear(); + last_tagged_identifier = Identifier(); + } } Source::ClangView::ClangView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language) : - BaseView(file_path, language), ClangViewParse(file_path, language), ClangViewAutocomplete(file_path, language), - ClangViewRefactor(file_path, language) { - if (language) { - get_source_buffer()->set_highlight_syntax(true); - get_source_buffer()->set_language(language); - } - - do_delete_object.connect([this]() { - if (delete_thread.joinable()) - delete_thread.join(); - delete this; - }); + BaseView(file_path, language), ClangViewParse(file_path, language), ClangViewAutocomplete(file_path, language), + ClangViewRefactor(file_path, language) { + if (language) { + get_source_buffer()->set_highlight_syntax(true); + get_source_buffer()->set_language(language); + } + + do_delete_object.connect([this]() { + if (delete_thread.joinable()) + delete_thread.join(); + delete this; + }); } void Source::ClangView::full_reparse() { - auto print_error = [this] { - Terminal::get().async_print( - "Error: failed to reparse " + file_path.string() + ". Please reopen the file manually.\n", true); - }; - full_reparse_needed = false; - if (full_reparse_running) { + auto print_error = [this] { + Terminal::get().async_print( + "Error: failed to reparse " + file_path.string() + ". Please reopen the file manually.\n", true); + }; + full_reparse_needed = false; + if (full_reparse_running) { + print_error(); + return; + } else { + auto expected = ParseState::PROCESSING; + if (!parse_state.compare_exchange_strong(expected, ParseState::RESTARTING)) { + expected = ParseState::RESTARTING; + if (!parse_state.compare_exchange_strong(expected, ParseState::RESTARTING)) { print_error(); return; - } else { - auto expected = ParseState::PROCESSING; - if (!parse_state.compare_exchange_strong(expected, ParseState::RESTARTING)) { - expected = ParseState::RESTARTING; - if (!parse_state.compare_exchange_strong(expected, ParseState::RESTARTING)) { - print_error(); - return; - } - } - autocomplete.state = Autocomplete::State::IDLE; - soft_reparse_needed = false; - full_reparse_running = true; - if (full_reparse_thread.joinable()) - full_reparse_thread.join(); - full_reparse_thread = std::thread([this]() { - if (parse_thread.joinable()) - parse_thread.join(); - if (autocomplete.thread.joinable()) - autocomplete.thread.join(); - dispatcher.post([this] { - parse_initialize(); - full_reparse_running = false; - }); - }); + } } + autocomplete.state = Autocomplete::State::IDLE; + soft_reparse_needed = false; + full_reparse_running = true; + if (full_reparse_thread.joinable()) + full_reparse_thread.join(); + full_reparse_thread = std::thread([this]() { + if (parse_thread.joinable()) + parse_thread.join(); + if (autocomplete.thread.joinable()) + autocomplete.thread.join(); + dispatcher.post([this] { + parse_initialize(); + full_reparse_running = false; + }); + }); + } } void Source::ClangView::async_delete() { - delayed_show_arguments_connection.disconnect(); - delayed_tag_similar_identifiers_connection.disconnect(); - - views.erase(this); - std::set<boost::filesystem::path> project_paths_in_use; - for (auto &view: views) { - if (dynamic_cast<ClangView *>(view)) { - auto build = Project::Build::create(view->file_path); - if (!build->project_path.empty()) - project_paths_in_use.emplace(build->project_path); - } - } - Usages::Clang::erase_unused_caches(project_paths_in_use); - Usages::Clang::cache_in_progress(); - - if (!get_buffer()->get_modified()) { - if (full_reparse_needed) - full_reparse(); - else if (soft_reparse_needed) - soft_reparse(); + delayed_show_arguments_connection.disconnect(); + delayed_tag_similar_identifiers_connection.disconnect(); + + views.erase(this); + std::set<boost::filesystem::path> project_paths_in_use; + for (auto &view: views) { + if (dynamic_cast<ClangView *>(view)) { + auto build = Project::Build::create(view->file_path); + if (!build->project_path.empty()) + project_paths_in_use.emplace(build->project_path); } + } + Usages::Clang::erase_unused_caches(project_paths_in_use); + Usages::Clang::cache_in_progress(); + + if (!get_buffer()->get_modified()) { + if (full_reparse_needed) + full_reparse(); + else if (soft_reparse_needed) + soft_reparse(); + } + + auto before_parse_time = std::time(nullptr); + delete_thread = std::thread([this, before_parse_time, project_paths_in_use = std::move(project_paths_in_use)] { + while (!parsed) + std::this_thread::sleep_for(std::chrono::milliseconds(10)); - auto before_parse_time = std::time(nullptr); - delete_thread = std::thread([this, before_parse_time, project_paths_in_use = std::move(project_paths_in_use)] { - while (!parsed) - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - - delayed_reparse_connection.disconnect(); - parse_state = ParseState::STOP; - dispatcher.disconnect(); - - if (get_buffer()->get_modified()) { - std::ifstream stream(file_path.string(), std::ios::binary); - if (stream) { - std::string buffer; - buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>()); - if (language && (language->get_id() == "chdr" || language->get_id() == "cpphdr")) - clangmm::remove_include_guard(buffer); - clang_tu->reparse(buffer); - clang_tokens = clang_tu->get_tokens(); - } else - clang_tokens = nullptr; - } + delayed_reparse_connection.disconnect(); + parse_state = ParseState::STOP; + dispatcher.disconnect(); + + if (get_buffer()->get_modified()) { + std::ifstream stream(file_path.string(), std::ios::binary); + if (stream) { + std::string buffer; + buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>()); + if (language && (language->get_id() == "chdr" || language->get_id() == "cpphdr")) + clangmm::remove_include_guard(buffer); + clang_tu->reparse(buffer); + clang_tokens = clang_tu->get_tokens(); + } else + clang_tokens = nullptr; + } - if (clang_tokens) { - auto build = Project::Build::create(file_path); - Usages::Clang::cache(build->project_path, build->get_default_path(), file_path, before_parse_time, - project_paths_in_use, clang_tu.get(), clang_tokens.get()); - } + if (clang_tokens) { + auto build = Project::Build::create(file_path); + Usages::Clang::cache(build->project_path, build->get_default_path(), file_path, before_parse_time, + project_paths_in_use, clang_tu.get(), clang_tokens.get()); + } - if (full_reparse_thread.joinable()) - full_reparse_thread.join(); - if (parse_thread.joinable()) - parse_thread.join(); - if (autocomplete.thread.joinable()) - autocomplete.thread.join(); - do_delete_object(); - }); + if (full_reparse_thread.joinable()) + full_reparse_thread.join(); + if (parse_thread.joinable()) + parse_thread.join(); + if (autocomplete.thread.joinable()) + autocomplete.thread.join(); + do_delete_object(); + }); } diff --git a/src/source_clang.h b/src/source_clang.h index b4184525..5ae25f87 100644 --- a/src/source_clang.h +++ b/src/source_clang.h @@ -11,134 +11,134 @@ #include "autocomplete.h" namespace Source { - class ClangViewParse : public View { - protected: - enum class ParseState { - PROCESSING, RESTARTING, STOP - }; - enum class ParseProcessState { - IDLE, STARTING, PREPROCESSING, PROCESSING, POSTPROCESSING - }; + class ClangViewParse : public View { + protected: + enum class ParseState { + PROCESSING, RESTARTING, STOP + }; + enum class ParseProcessState { + IDLE, STARTING, PREPROCESSING, PROCESSING, POSTPROCESSING + }; - public: - ClangViewParse(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); + public: + ClangViewParse(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); - bool save() override; + bool save() override; - void configure() override; + void configure() override; - void soft_reparse(bool delayed = false) override; + void soft_reparse(bool delayed = false) override; - protected: - Dispatcher dispatcher; + protected: + Dispatcher dispatcher; - void parse_initialize(); + void parse_initialize(); - std::unique_ptr<clangmm::TranslationUnit> clang_tu; - std::unique_ptr<clangmm::Tokens> clang_tokens; - std::vector<std::pair<clangmm::Offset, clangmm::Offset>> clang_tokens_offsets; - sigc::connection delayed_reparse_connection; + std::unique_ptr<clangmm::TranslationUnit> clang_tu; + std::unique_ptr<clangmm::Tokens> clang_tokens; + std::vector<std::pair<clangmm::Offset, clangmm::Offset>> clang_tokens_offsets; + sigc::connection delayed_reparse_connection; - void show_type_tooltips(const Gdk::Rectangle &rectangle) override; + void show_type_tooltips(const Gdk::Rectangle &rectangle) override; - std::vector<FixIt> fix_its; + std::vector<FixIt> fix_its; - std::thread parse_thread; - std::mutex parse_mutex; - std::atomic<ParseState> parse_state; - std::atomic<ParseProcessState> parse_process_state; + std::thread parse_thread; + std::mutex parse_mutex; + std::atomic<ParseState> parse_state; + std::atomic<ParseProcessState> parse_process_state; - CXCompletionString selected_completion_string = nullptr; - private: - Glib::ustring parse_thread_buffer; + CXCompletionString selected_completion_string = nullptr; + private: + Glib::ustring parse_thread_buffer; - static const std::unordered_map<int, std::string> &clang_types(); + static const std::unordered_map<int, std::string> &clang_types(); - void update_syntax(); + void update_syntax(); - std::set<std::string> last_syntax_tags; + std::set<std::string> last_syntax_tags; - void update_diagnostics(); + void update_diagnostics(); - std::vector<clangmm::Diagnostic> clang_diagnostics; + std::vector<clangmm::Diagnostic> clang_diagnostics; - static clangmm::Index clang_index; - }; + static clangmm::Index clang_index; + }; - class ClangViewAutocomplete : public virtual ClangViewParse { - public: - ClangViewAutocomplete(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); + class ClangViewAutocomplete : public virtual ClangViewParse { + public: + ClangViewAutocomplete(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); - protected: - Autocomplete autocomplete; - std::unique_ptr<clangmm::CodeCompleteResults> code_complete_results; - std::vector<CXCompletionString> completion_strings; - sigc::connection delayed_show_arguments_connection; - private: - bool is_possible_parameter(); + protected: + Autocomplete autocomplete; + std::unique_ptr<clangmm::CodeCompleteResults> code_complete_results; + std::vector<CXCompletionString> completion_strings; + sigc::connection delayed_show_arguments_connection; + private: + bool is_possible_parameter(); - bool show_arguments; + bool show_arguments; - const std::unordered_map<std::string, std::string> &autocomplete_manipulators_map(); - }; + const std::unordered_map<std::string, std::string> &autocomplete_manipulators_map(); + }; - class ClangViewRefactor : public virtual ClangViewParse { - class Identifier { - public: - Identifier(const std::string &spelling, const clangmm::Cursor &cursor) - : kind(cursor.get_kind()), spelling(spelling), usr_extended(cursor.get_usr_extended()), - cursor(cursor) {} + class ClangViewRefactor : public virtual ClangViewParse { + class Identifier { + public: + Identifier(const std::string &spelling, const clangmm::Cursor &cursor) + : kind(cursor.get_kind()), spelling(spelling), usr_extended(cursor.get_usr_extended()), + cursor(cursor) {} - Identifier() : kind(static_cast<clangmm::Cursor::Kind>(0)) {} + Identifier() : kind(static_cast<clangmm::Cursor::Kind>(0)) {} - operator bool() const { return static_cast<int>(kind) != 0; } + operator bool() const { return static_cast<int>(kind) != 0; } - bool operator==(const Identifier &rhs) const { - return spelling == rhs.spelling && usr_extended == rhs.usr_extended; - } + bool operator==(const Identifier &rhs) const { + return spelling == rhs.spelling && usr_extended == rhs.usr_extended; + } - bool operator!=(const Identifier &rhs) const { return !(*this == rhs); } + bool operator!=(const Identifier &rhs) const { return !(*this == rhs); } - bool operator<(const Identifier &rhs) const { - return spelling < rhs.spelling || (spelling == rhs.spelling && usr_extended < rhs.usr_extended); - } + bool operator<(const Identifier &rhs) const { + return spelling < rhs.spelling || (spelling == rhs.spelling && usr_extended < rhs.usr_extended); + } - clangmm::Cursor::Kind kind; - std::string spelling; - std::string usr_extended; - clangmm::Cursor cursor; - }; + clangmm::Cursor::Kind kind; + std::string spelling; + std::string usr_extended; + clangmm::Cursor cursor; + }; - public: - ClangViewRefactor(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); + public: + ClangViewRefactor(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); - protected: - sigc::connection delayed_tag_similar_identifiers_connection; - private: - Identifier get_identifier(); + protected: + sigc::connection delayed_tag_similar_identifiers_connection; + private: + Identifier get_identifier(); - void wait_parsing(); + void wait_parsing(); - std::list<std::pair<Glib::RefPtr<Gtk::TextMark>, Glib::RefPtr<Gtk::TextMark> > > similar_identifiers_marks; + std::list<std::pair<Glib::RefPtr<Gtk::TextMark>, Glib::RefPtr<Gtk::TextMark> > > similar_identifiers_marks; - void tag_similar_identifiers(const Identifier &identifier); + void tag_similar_identifiers(const Identifier &identifier); - Glib::RefPtr<Gtk::TextTag> similar_identifiers_tag; - Identifier last_tagged_identifier; - }; + Glib::RefPtr<Gtk::TextTag> similar_identifiers_tag; + Identifier last_tagged_identifier; + }; - class ClangView : public ClangViewAutocomplete, public ClangViewRefactor { - public: - ClangView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); + class ClangView : public ClangViewAutocomplete, public ClangViewRefactor { + public: + ClangView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); - void full_reparse() override; + void full_reparse() override; - void async_delete(); + void async_delete(); - private: - Glib::Dispatcher do_delete_object; - std::thread delete_thread; - std::thread full_reparse_thread; - bool full_reparse_running = false; - }; + private: + Glib::Dispatcher do_delete_object; + std::thread delete_thread; + std::thread full_reparse_thread; + bool full_reparse_running = false; + }; } diff --git a/src/source_diff.cc b/src/source_diff.cc index e0bec547..535ef89d 100644 --- a/src/source_diff.cc +++ b/src/source_diff.cc @@ -6,366 +6,366 @@ #include <boost/version.hpp> Source::DiffView::Renderer::Renderer() : Gsv::GutterRenderer() { - set_padding(4, 0); + set_padding(4, 0); } void Source::DiffView::Renderer::draw_vfunc(const Cairo::RefPtr<Cairo::Context> &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::TextIter &start, Gtk::TextIter &end, Gsv::GutterRendererState p6) { - if (start.has_tag(tag_added) || end.has_tag(tag_added)) { - cr->set_source_rgba(0.0, 1.0, 0.0, 0.5); - cr->rectangle(cell_area.get_x(), cell_area.get_y(), 4, cell_area.get_height()); - cr->fill(); - } else if (start.has_tag(tag_modified) || end.has_tag(tag_modified)) { - cr->set_source_rgba(0.9, 0.9, 0.0, 0.75); - cr->rectangle(cell_area.get_x(), cell_area.get_y(), 4, cell_area.get_height()); - cr->fill(); - } - if (start.has_tag(tag_removed_below) || end.has_tag(tag_removed_below)) { - cr->set_source_rgba(0.75, 0.0, 0.0, 0.5); - cr->rectangle(cell_area.get_x() - 4, cell_area.get_y() + cell_area.get_height() - 2, 8, 2); - cr->fill(); - } - if (start.has_tag(tag_removed_above) || end.has_tag(tag_removed_above)) { - cr->set_source_rgba(0.75, 0.0, 0.0, 0.5); - cr->rectangle(cell_area.get_x() - 4, cell_area.get_y(), 8, 2); - cr->fill(); - } + if (start.has_tag(tag_added) || end.has_tag(tag_added)) { + cr->set_source_rgba(0.0, 1.0, 0.0, 0.5); + cr->rectangle(cell_area.get_x(), cell_area.get_y(), 4, cell_area.get_height()); + cr->fill(); + } else if (start.has_tag(tag_modified) || end.has_tag(tag_modified)) { + cr->set_source_rgba(0.9, 0.9, 0.0, 0.75); + cr->rectangle(cell_area.get_x(), cell_area.get_y(), 4, cell_area.get_height()); + cr->fill(); + } + if (start.has_tag(tag_removed_below) || end.has_tag(tag_removed_below)) { + cr->set_source_rgba(0.75, 0.0, 0.0, 0.5); + cr->rectangle(cell_area.get_x() - 4, cell_area.get_y() + cell_area.get_height() - 2, 8, 2); + cr->fill(); + } + if (start.has_tag(tag_removed_above) || end.has_tag(tag_removed_above)) { + cr->set_source_rgba(0.75, 0.0, 0.0, 0.5); + cr->rectangle(cell_area.get_x() - 4, cell_area.get_y(), 8, 2); + cr->fill(); + } } Source::DiffView::DiffView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language) : BaseView( - file_path, language), renderer(new Renderer()) { - boost::system::error_code ec; - canonical_file_path = boost::filesystem::canonical(file_path, ec); - if (ec) - canonical_file_path = file_path; + file_path, language), renderer(new Renderer()) { + boost::system::error_code ec; + canonical_file_path = boost::filesystem::canonical(file_path, ec); + if (ec) + canonical_file_path = file_path; - renderer->tag_added = get_buffer()->create_tag("git_added"); - renderer->tag_modified = get_buffer()->create_tag("git_modified"); - renderer->tag_removed = get_buffer()->create_tag("git_removed"); - renderer->tag_removed_below = get_buffer()->create_tag(); - renderer->tag_removed_above = get_buffer()->create_tag(); + renderer->tag_added = get_buffer()->create_tag("git_added"); + renderer->tag_modified = get_buffer()->create_tag("git_modified"); + renderer->tag_removed = get_buffer()->create_tag("git_removed"); + renderer->tag_removed_below = get_buffer()->create_tag(); + renderer->tag_removed_above = get_buffer()->create_tag(); - configure(); + configure(); } Source::DiffView::~DiffView() { - dispatcher.disconnect(); - if (repository) { - get_gutter(Gtk::TextWindowType::TEXT_WINDOW_LEFT)->remove(renderer.get()); - buffer_insert_connection.disconnect(); - buffer_erase_connection.disconnect(); - monitor_changed_connection.disconnect(); - delayed_buffer_changed_connection.disconnect(); - delayed_monitor_changed_connection.disconnect(); + dispatcher.disconnect(); + if (repository) { + get_gutter(Gtk::TextWindowType::TEXT_WINDOW_LEFT)->remove(renderer.get()); + buffer_insert_connection.disconnect(); + buffer_erase_connection.disconnect(); + monitor_changed_connection.disconnect(); + delayed_buffer_changed_connection.disconnect(); + delayed_monitor_changed_connection.disconnect(); - parse_stop = true; - if (parse_thread.joinable()) - parse_thread.join(); - } + parse_stop = true; + if (parse_thread.joinable()) + parse_thread.join(); + } } void Source::DiffView::configure() { - if (Config::get().source.show_git_diff) { - if (repository) - return; - } else if (repository) { - get_gutter(Gtk::TextWindowType::TEXT_WINDOW_LEFT)->remove(renderer.get()); - buffer_insert_connection.disconnect(); - buffer_erase_connection.disconnect(); - monitor_changed_connection.disconnect(); - delayed_buffer_changed_connection.disconnect(); - delayed_monitor_changed_connection.disconnect(); + if (Config::get().source.show_git_diff) { + if (repository) + return; + } else if (repository) { + get_gutter(Gtk::TextWindowType::TEXT_WINDOW_LEFT)->remove(renderer.get()); + buffer_insert_connection.disconnect(); + buffer_erase_connection.disconnect(); + monitor_changed_connection.disconnect(); + delayed_buffer_changed_connection.disconnect(); + delayed_monitor_changed_connection.disconnect(); - parse_stop = true; - if (parse_thread.joinable()) - parse_thread.join(); - repository = nullptr; - diff = nullptr; + parse_stop = true; + if (parse_thread.joinable()) + parse_thread.join(); + repository = nullptr; + diff = nullptr; - return; - } else - return; + return; + } else + return; - try { - repository = Git::get_repository(this->file_path.parent_path()); - } - catch (const std::exception &) { - return; - } + try { + repository = Git::get_repository(this->file_path.parent_path()); + } + catch (const std::exception &) { + return; + } - get_gutter(Gtk::TextWindowType::TEXT_WINDOW_LEFT)->insert(renderer.get(), -40); - parse_state = ParseState::STARTING; - parse_stop = false; - monitor_changed = false; + get_gutter(Gtk::TextWindowType::TEXT_WINDOW_LEFT)->insert(renderer.get(), -40); + parse_state = ParseState::STARTING; + parse_stop = false; + monitor_changed = false; - buffer_insert_connection = get_buffer()->signal_insert().connect( - [this](const Gtk::TextBuffer::iterator &iter, const Glib::ustring &text, int) { - //Do not perform git diff if no newline is added and line is already marked as added - if (!iter.starts_line() && iter.has_tag(renderer->tag_added)) { - bool newline = false; - for (auto &c: text.raw()) { - if (c == '\n') { - newline = true; - break; - } - } - if (!newline) - return; - } - //Remove tag_removed_above/below if newline is inserted - else if (!text.empty() && text[0] == '\n' && iter.has_tag(renderer->tag_removed)) { - auto start_iter = get_buffer()->get_iter_at_line(iter.get_line()); - auto end_iter = get_iter_at_line_end(iter.get_line()); - end_iter.forward_char(); - get_buffer()->remove_tag(renderer->tag_removed_above, start_iter, end_iter); - get_buffer()->remove_tag(renderer->tag_removed_below, start_iter, end_iter); - } - parse_state = ParseState::IDLE; - delayed_buffer_changed_connection.disconnect(); - delayed_buffer_changed_connection = Glib::signal_timeout().connect([this]() { - parse_state = ParseState::STARTING; - return false; - }, 250); - }, false); - - buffer_erase_connection = get_buffer()->signal_erase().connect( - [this](const Gtk::TextBuffer::iterator &start_iter, const Gtk::TextBuffer::iterator &end_iter) { - //Do not perform git diff if start_iter and end_iter is at the same line in addition to the line is tagged added - if (start_iter.get_line() == end_iter.get_line() && start_iter.has_tag(renderer->tag_added)) - return; + buffer_insert_connection = get_buffer()->signal_insert().connect( + [this](const Gtk::TextBuffer::iterator &iter, const Glib::ustring &text, int) { + //Do not perform git diff if no newline is added and line is already marked as added + if (!iter.starts_line() && iter.has_tag(renderer->tag_added)) { + bool newline = false; + for (auto &c: text.raw()) { + if (c == '\n') { + newline = true; + break; + } + } + if (!newline) + return; + } + //Remove tag_removed_above/below if newline is inserted + else if (!text.empty() && text[0] == '\n' && iter.has_tag(renderer->tag_removed)) { + auto start_iter = get_buffer()->get_iter_at_line(iter.get_line()); + auto end_iter = get_iter_at_line_end(iter.get_line()); + end_iter.forward_char(); + get_buffer()->remove_tag(renderer->tag_removed_above, start_iter, end_iter); + get_buffer()->remove_tag(renderer->tag_removed_below, start_iter, end_iter); + } + parse_state = ParseState::IDLE; + delayed_buffer_changed_connection.disconnect(); + delayed_buffer_changed_connection = Glib::signal_timeout().connect([this]() { + parse_state = ParseState::STARTING; + return false; + }, 250); + }, false); - parse_state = ParseState::IDLE; - delayed_buffer_changed_connection.disconnect(); - delayed_buffer_changed_connection = Glib::signal_timeout().connect([this]() { - parse_state = ParseState::STARTING; - return false; - }, 250); - }, false); + buffer_erase_connection = get_buffer()->signal_erase().connect( + [this](const Gtk::TextBuffer::iterator &start_iter, const Gtk::TextBuffer::iterator &end_iter) { + //Do not perform git diff if start_iter and end_iter is at the same line in addition to the line is tagged added + if (start_iter.get_line() == end_iter.get_line() && start_iter.has_tag(renderer->tag_added)) + return; - monitor_changed_connection = repository->monitor->signal_changed().connect( - [this](const Glib::RefPtr<Gio::File> &file, - const Glib::RefPtr<Gio::File> &, - Gio::FileMonitorEvent monitor_event) { - if (monitor_event != Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { - delayed_monitor_changed_connection.disconnect(); - delayed_monitor_changed_connection = Glib::signal_timeout().connect([this]() { - monitor_changed = true; - parse_state = ParseState::STARTING; - std::unique_lock<std::mutex> lock(parse_mutex); - diff = nullptr; - return false; - }, 500); - } - }); + parse_state = ParseState::IDLE; + delayed_buffer_changed_connection.disconnect(); + delayed_buffer_changed_connection = Glib::signal_timeout().connect([this]() { + parse_state = ParseState::STARTING; + return false; + }, 250); + }, false); - parse_thread = std::thread([this]() { - std::string status_branch; - try { - diff = get_diff(); - status_branch = repository->get_branch(); + monitor_changed_connection = repository->monitor->signal_changed().connect( + [this](const Glib::RefPtr<Gio::File> &file, + const Glib::RefPtr<Gio::File> &, + Gio::FileMonitorEvent monitor_event) { + if (monitor_event != Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { + delayed_monitor_changed_connection.disconnect(); + delayed_monitor_changed_connection = Glib::signal_timeout().connect([this]() { + monitor_changed = true; + parse_state = ParseState::STARTING; + std::unique_lock<std::mutex> lock(parse_mutex); + diff = nullptr; + return false; + }, 500); } - catch (const std::exception &) { - status_branch = ""; - } - dispatcher.post([this, status_branch = std::move(status_branch)] { - this->status_branch = status_branch; - if (update_status_branch) - update_status_branch(this); - }); + }); - try { - while (true) { - while (!parse_stop && parse_state != ParseState::STARTING && parse_state != ParseState::PROCESSING) - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - if (parse_stop) - break; - std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock); - auto expected = ParseState::STARTING; - if (parse_state.compare_exchange_strong(expected, ParseState::PREPROCESSING)) { - dispatcher.post([this] { - auto expected = ParseState::PREPROCESSING; - std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock); - if (parse_lock.try_lock()) { - if (parse_state.compare_exchange_strong(expected, ParseState::PROCESSING)) - parse_buffer = get_buffer()->get_text(); - parse_lock.unlock(); - } else - parse_state.compare_exchange_strong(expected, ParseState::STARTING); - }); - } else if (parse_state == ParseState::PROCESSING && parse_lock.try_lock()) { - bool expected_monitor_changed = true; - if (monitor_changed.compare_exchange_strong(expected_monitor_changed, false)) { - try { - diff = get_diff(); - dispatcher.post([this, status_branch = repository->get_branch()] { - this->status_branch = status_branch; - if (update_status_branch) - update_status_branch(this); - }); - } - catch (const std::exception &) { - dispatcher.post([this] { - get_buffer()->remove_tag(renderer->tag_added, get_buffer()->begin(), - get_buffer()->end()); - get_buffer()->remove_tag(renderer->tag_modified, get_buffer()->begin(), - get_buffer()->end()); - get_buffer()->remove_tag(renderer->tag_removed, get_buffer()->begin(), - get_buffer()->end()); - get_buffer()->remove_tag(renderer->tag_removed_below, get_buffer()->begin(), - get_buffer()->end()); - get_buffer()->remove_tag(renderer->tag_removed_above, get_buffer()->begin(), - get_buffer()->end()); - renderer->queue_draw(); - this->status_branch = ""; - if (update_status_branch) - update_status_branch(this); - }); - } - } - if (diff) - lines = diff->get_lines(parse_buffer.raw()); - else { - lines.added.clear(); - lines.modified.clear(); - lines.removed.clear(); - } - auto expected = ParseState::PROCESSING; - if (parse_state.compare_exchange_strong(expected, ParseState::POSTPROCESSING)) { - parse_lock.unlock(); - dispatcher.post([this] { - std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock); - if (parse_lock.try_lock()) { - auto expected = ParseState::POSTPROCESSING; - if (parse_state.compare_exchange_strong(expected, ParseState::IDLE)) - update_lines(); - } - }); - } - } + parse_thread = std::thread([this]() { + std::string status_branch; + try { + diff = get_diff(); + status_branch = repository->get_branch(); + } + catch (const std::exception &) { + status_branch = ""; + } + dispatcher.post([this, status_branch = std::move(status_branch)] { + this->status_branch = status_branch; + if (update_status_branch) + update_status_branch(this); + }); + + try { + while (true) { + while (!parse_stop && parse_state != ParseState::STARTING && parse_state != ParseState::PROCESSING) + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + if (parse_stop) + break; + std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock); + auto expected = ParseState::STARTING; + if (parse_state.compare_exchange_strong(expected, ParseState::PREPROCESSING)) { + dispatcher.post([this] { + auto expected = ParseState::PREPROCESSING; + std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock); + if (parse_lock.try_lock()) { + if (parse_state.compare_exchange_strong(expected, ParseState::PROCESSING)) + parse_buffer = get_buffer()->get_text(); + parse_lock.unlock(); + } else + parse_state.compare_exchange_strong(expected, ParseState::STARTING); + }); + } else if (parse_state == ParseState::PROCESSING && parse_lock.try_lock()) { + bool expected_monitor_changed = true; + if (monitor_changed.compare_exchange_strong(expected_monitor_changed, false)) { + try { + diff = get_diff(); + dispatcher.post([this, status_branch = repository->get_branch()] { + this->status_branch = status_branch; + if (update_status_branch) + update_status_branch(this); + }); } - } - catch (const std::exception &e) { - dispatcher.post([this, e_what = e.what()] { - get_buffer()->remove_tag(renderer->tag_added, get_buffer()->begin(), get_buffer()->end()); - get_buffer()->remove_tag(renderer->tag_modified, get_buffer()->begin(), get_buffer()->end()); - get_buffer()->remove_tag(renderer->tag_removed, get_buffer()->begin(), get_buffer()->end()); - get_buffer()->remove_tag(renderer->tag_removed_below, get_buffer()->begin(), get_buffer()->end()); - get_buffer()->remove_tag(renderer->tag_removed_above, get_buffer()->begin(), get_buffer()->end()); + catch (const std::exception &) { + dispatcher.post([this] { + get_buffer()->remove_tag(renderer->tag_added, get_buffer()->begin(), + get_buffer()->end()); + get_buffer()->remove_tag(renderer->tag_modified, get_buffer()->begin(), + get_buffer()->end()); + get_buffer()->remove_tag(renderer->tag_removed, get_buffer()->begin(), + get_buffer()->end()); + get_buffer()->remove_tag(renderer->tag_removed_below, get_buffer()->begin(), + get_buffer()->end()); + get_buffer()->remove_tag(renderer->tag_removed_above, get_buffer()->begin(), + get_buffer()->end()); renderer->queue_draw(); - Terminal::get().print(std::string("Error (git): ") + e_what + '\n', true); + this->status_branch = ""; + if (update_status_branch) + update_status_branch(this); + }); + } + } + if (diff) + lines = diff->get_lines(parse_buffer.raw()); + else { + lines.added.clear(); + lines.modified.clear(); + lines.removed.clear(); + } + auto expected = ParseState::PROCESSING; + if (parse_state.compare_exchange_strong(expected, ParseState::POSTPROCESSING)) { + parse_lock.unlock(); + dispatcher.post([this] { + std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock); + if (parse_lock.try_lock()) { + auto expected = ParseState::POSTPROCESSING; + if (parse_state.compare_exchange_strong(expected, ParseState::IDLE)) + update_lines(); + } }); + } } - }); + } + } + catch (const std::exception &e) { + dispatcher.post([this, e_what = e.what()] { + get_buffer()->remove_tag(renderer->tag_added, get_buffer()->begin(), get_buffer()->end()); + get_buffer()->remove_tag(renderer->tag_modified, get_buffer()->begin(), get_buffer()->end()); + get_buffer()->remove_tag(renderer->tag_removed, get_buffer()->begin(), get_buffer()->end()); + get_buffer()->remove_tag(renderer->tag_removed_below, get_buffer()->begin(), get_buffer()->end()); + get_buffer()->remove_tag(renderer->tag_removed_above, get_buffer()->begin(), get_buffer()->end()); + renderer->queue_draw(); + Terminal::get().print(std::string("Error (git): ") + e_what + '\n', true); + }); + } + }); } void Source::DiffView::rename(const boost::filesystem::path &path) { - Source::BaseView::rename(path); + Source::BaseView::rename(path); - std::lock_guard<std::mutex> lock(canonical_file_path_mutex); - boost::system::error_code ec; - canonical_file_path = boost::filesystem::canonical(path, ec); - if (ec) - canonical_file_path = path; + std::lock_guard<std::mutex> lock(canonical_file_path_mutex); + boost::system::error_code ec; + canonical_file_path = boost::filesystem::canonical(path, ec); + if (ec) + canonical_file_path = path; } void Source::DiffView::git_goto_next_diff() { - auto iter = get_buffer()->get_insert()->get_iter(); - auto insert_iter = iter; - bool wrapped = false; - iter.forward_char(); - for (;;) { - auto toggled_tags = iter.get_toggled_tags(); - for (auto &toggled_tag: toggled_tags) { - if (toggled_tag->property_name() == "git_added" || - toggled_tag->property_name() == "git_modified" || - toggled_tag->property_name() == "git_removed") { - get_buffer()->place_cursor(iter); - scroll_to(get_buffer()->get_insert(), 0.0, 1.0, 0.5); - return; - } - } - if (wrapped && (iter == insert_iter || iter == get_buffer()->end())) - break; - if (!wrapped && iter == get_buffer()->end()) { - iter = get_buffer()->begin(); - wrapped = true; - } else - iter.forward_char(); + auto iter = get_buffer()->get_insert()->get_iter(); + auto insert_iter = iter; + bool wrapped = false; + iter.forward_char(); + for (;;) { + auto toggled_tags = iter.get_toggled_tags(); + for (auto &toggled_tag: toggled_tags) { + if (toggled_tag->property_name() == "git_added" || + toggled_tag->property_name() == "git_modified" || + toggled_tag->property_name() == "git_removed") { + get_buffer()->place_cursor(iter); + scroll_to(get_buffer()->get_insert(), 0.0, 1.0, 0.5); + return; + } } - Info::get().print("No changes found in current buffer"); + if (wrapped && (iter == insert_iter || iter == get_buffer()->end())) + break; + if (!wrapped && iter == get_buffer()->end()) { + iter = get_buffer()->begin(); + wrapped = true; + } else + iter.forward_char(); + } + Info::get().print("No changes found in current buffer"); } std::string Source::DiffView::git_get_diff_details() { - std::string details; - if (diff) { - auto line_nr = get_buffer()->get_insert()->get_iter().get_line(); - auto iter = get_buffer()->get_iter_at_line(line_nr); - if (iter.has_tag(renderer->tag_removed_above)) - --line_nr; - std::unique_lock<std::mutex> lock(parse_mutex); - parse_buffer = get_buffer()->get_text(); - details = diff->get_details(parse_buffer.raw(), line_nr); - } - if (details.empty()) - Info::get().print("No changes found at current line"); - return details; + std::string details; + if (diff) { + auto line_nr = get_buffer()->get_insert()->get_iter().get_line(); + auto iter = get_buffer()->get_iter_at_line(line_nr); + if (iter.has_tag(renderer->tag_removed_above)) + --line_nr; + std::unique_lock<std::mutex> lock(parse_mutex); + parse_buffer = get_buffer()->get_text(); + details = diff->get_details(parse_buffer.raw(), line_nr); + } + if (details.empty()) + Info::get().print("No changes found at current line"); + return details; } ///Return repository diff instance. Throws exception on error std::unique_ptr<Git::Repository::Diff> Source::DiffView::get_diff() { - auto work_path = filesystem::get_normal_path(repository->get_work_path()); - boost::filesystem::path relative_path; - { - std::unique_lock<std::mutex> lock(canonical_file_path_mutex); - relative_path = filesystem::get_relative_path(canonical_file_path, work_path); - if (relative_path.empty()) - throw std::runtime_error("not a relative path"); - } - return std::make_unique<Git::Repository::Diff>(repository->get_diff(relative_path)); + auto work_path = filesystem::get_normal_path(repository->get_work_path()); + boost::filesystem::path relative_path; + { + std::unique_lock<std::mutex> lock(canonical_file_path_mutex); + relative_path = filesystem::get_relative_path(canonical_file_path, work_path); + if (relative_path.empty()) + throw std::runtime_error("not a relative path"); + } + return std::make_unique<Git::Repository::Diff>(repository->get_diff(relative_path)); } void Source::DiffView::update_lines() { - get_buffer()->remove_tag(renderer->tag_added, get_buffer()->begin(), get_buffer()->end()); - get_buffer()->remove_tag(renderer->tag_modified, get_buffer()->begin(), get_buffer()->end()); - get_buffer()->remove_tag(renderer->tag_removed, get_buffer()->begin(), get_buffer()->end()); - get_buffer()->remove_tag(renderer->tag_removed_below, get_buffer()->begin(), get_buffer()->end()); - get_buffer()->remove_tag(renderer->tag_removed_above, get_buffer()->begin(), get_buffer()->end()); + get_buffer()->remove_tag(renderer->tag_added, get_buffer()->begin(), get_buffer()->end()); + get_buffer()->remove_tag(renderer->tag_modified, get_buffer()->begin(), get_buffer()->end()); + get_buffer()->remove_tag(renderer->tag_removed, get_buffer()->begin(), get_buffer()->end()); + get_buffer()->remove_tag(renderer->tag_removed_below, get_buffer()->begin(), get_buffer()->end()); + get_buffer()->remove_tag(renderer->tag_removed_above, get_buffer()->begin(), get_buffer()->end()); - for (auto &added: lines.added) { - auto start_iter = get_buffer()->get_iter_at_line(added.first); - auto end_iter = get_iter_at_line_end(added.second - 1); - end_iter.forward_char(); - get_buffer()->apply_tag(renderer->tag_added, start_iter, end_iter); - } - for (auto &modified: lines.modified) { - auto start_iter = get_buffer()->get_iter_at_line(modified.first); - auto end_iter = get_iter_at_line_end(modified.second - 1); - end_iter.forward_char(); - get_buffer()->apply_tag(renderer->tag_modified, start_iter, end_iter); + for (auto &added: lines.added) { + auto start_iter = get_buffer()->get_iter_at_line(added.first); + auto end_iter = get_iter_at_line_end(added.second - 1); + end_iter.forward_char(); + get_buffer()->apply_tag(renderer->tag_added, start_iter, end_iter); + } + for (auto &modified: lines.modified) { + auto start_iter = get_buffer()->get_iter_at_line(modified.first); + auto end_iter = get_iter_at_line_end(modified.second - 1); + end_iter.forward_char(); + get_buffer()->apply_tag(renderer->tag_modified, start_iter, end_iter); + } + for (auto &line_nr: lines.removed) { + Gtk::TextIter removed_start, removed_end; + if (line_nr >= 0) { + auto start_iter = get_buffer()->get_iter_at_line(line_nr); + removed_start = start_iter; + auto end_iter = get_iter_at_line_end(line_nr); + end_iter.forward_char(); + removed_end = end_iter; + get_buffer()->apply_tag(renderer->tag_removed_below, start_iter, end_iter); } - for (auto &line_nr: lines.removed) { - Gtk::TextIter removed_start, removed_end; - if (line_nr >= 0) { - auto start_iter = get_buffer()->get_iter_at_line(line_nr); - removed_start = start_iter; - auto end_iter = get_iter_at_line_end(line_nr); - end_iter.forward_char(); - removed_end = end_iter; - get_buffer()->apply_tag(renderer->tag_removed_below, start_iter, end_iter); - } - if (line_nr + 1 < get_buffer()->get_line_count()) { - auto start_iter = get_buffer()->get_iter_at_line(line_nr + 1); - if (line_nr < 0) - removed_start = start_iter; - auto end_iter = get_iter_at_line_end(line_nr + 1); - end_iter.forward_char(); - removed_end = end_iter; - get_buffer()->apply_tag(renderer->tag_removed_above, start_iter, end_iter); - } - get_buffer()->apply_tag(renderer->tag_removed, removed_start, removed_end); + if (line_nr + 1 < get_buffer()->get_line_count()) { + auto start_iter = get_buffer()->get_iter_at_line(line_nr + 1); + if (line_nr < 0) + removed_start = start_iter; + auto end_iter = get_iter_at_line_end(line_nr + 1); + end_iter.forward_char(); + removed_end = end_iter; + get_buffer()->apply_tag(renderer->tag_removed_above, start_iter, end_iter); } + get_buffer()->apply_tag(renderer->tag_removed, removed_start, removed_end); + } - renderer->queue_draw(); + renderer->queue_draw(); } diff --git a/src/source_diff.h b/src/source_diff.h index 5f48f931..db20c2d7 100644 --- a/src/source_diff.h +++ b/src/source_diff.h @@ -11,67 +11,67 @@ #include "git.h" namespace Source { - class DiffView : virtual public Source::BaseView { - enum class ParseState { - IDLE, STARTING, PREPROCESSING, PROCESSING, POSTPROCESSING - }; - - class Renderer : public Gsv::GutterRenderer { - public: - Renderer(); - - Glib::RefPtr<Gtk::TextTag> tag_added; - Glib::RefPtr<Gtk::TextTag> tag_modified; - Glib::RefPtr<Gtk::TextTag> tag_removed; - Glib::RefPtr<Gtk::TextTag> tag_removed_below; - Glib::RefPtr<Gtk::TextTag> tag_removed_above; - - protected: - void draw_vfunc(const Cairo::RefPtr<Cairo::Context> &cr, const Gdk::Rectangle &background_area, - const Gdk::Rectangle &cell_area, Gtk::TextIter &start, Gtk::TextIter &end, - Gsv::GutterRendererState p6) override; - }; + class DiffView : virtual public Source::BaseView { + enum class ParseState { + IDLE, STARTING, PREPROCESSING, PROCESSING, POSTPROCESSING + }; + class Renderer : public Gsv::GutterRenderer { public: - DiffView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); + Renderer(); + + Glib::RefPtr<Gtk::TextTag> tag_added; + Glib::RefPtr<Gtk::TextTag> tag_modified; + Glib::RefPtr<Gtk::TextTag> tag_removed; + Glib::RefPtr<Gtk::TextTag> tag_removed_below; + Glib::RefPtr<Gtk::TextTag> tag_removed_above; + + protected: + void draw_vfunc(const Cairo::RefPtr<Cairo::Context> &cr, const Gdk::Rectangle &background_area, + const Gdk::Rectangle &cell_area, Gtk::TextIter &start, Gtk::TextIter &end, + Gsv::GutterRendererState p6) override; + }; - ~DiffView(); + public: + DiffView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); - void configure() override; + ~DiffView(); - void rename(const boost::filesystem::path &path) override; + void configure() override; - void git_goto_next_diff(); + void rename(const boost::filesystem::path &path) override; - std::string git_get_diff_details(); + void git_goto_next_diff(); - /// Use canonical path to follow symbolic links - boost::filesystem::path canonical_file_path; - private: - std::mutex canonical_file_path_mutex; + std::string git_get_diff_details(); - std::unique_ptr<Renderer> renderer; - Dispatcher dispatcher; + /// Use canonical path to follow symbolic links + boost::filesystem::path canonical_file_path; + private: + std::mutex canonical_file_path_mutex; - std::shared_ptr<Git::Repository> repository; - std::unique_ptr<Git::Repository::Diff> diff; + std::unique_ptr<Renderer> renderer; + Dispatcher dispatcher; - std::unique_ptr<Git::Repository::Diff> get_diff(); + std::shared_ptr<Git::Repository> repository; + std::unique_ptr<Git::Repository::Diff> diff; - std::thread parse_thread; - std::atomic<ParseState> parse_state; - std::mutex parse_mutex; - std::atomic<bool> parse_stop; - Glib::ustring parse_buffer; - sigc::connection buffer_insert_connection; - sigc::connection buffer_erase_connection; - sigc::connection monitor_changed_connection; - sigc::connection delayed_buffer_changed_connection; - sigc::connection delayed_monitor_changed_connection; - std::atomic<bool> monitor_changed; + std::unique_ptr<Git::Repository::Diff> get_diff(); - Git::Repository::Diff::Lines lines; + std::thread parse_thread; + std::atomic<ParseState> parse_state; + std::mutex parse_mutex; + std::atomic<bool> parse_stop; + Glib::ustring parse_buffer; + sigc::connection buffer_insert_connection; + sigc::connection buffer_erase_connection; + sigc::connection monitor_changed_connection; + sigc::connection delayed_buffer_changed_connection; + sigc::connection delayed_monitor_changed_connection; + std::atomic<bool> monitor_changed; - void update_lines(); - }; + Git::Repository::Diff::Lines lines; + + void update_lines(); + }; } diff --git a/src/source_language_protocol.cc b/src/source_language_protocol.cc index 68c3b37c..4a140601 100644 --- a/src/source_language_protocol.cc +++ b/src/source_language_protocol.cc @@ -18,1535 +18,1535 @@ const bool output_messages_and_errors = false; LanguageProtocol::Client::Client(std::string root_uri_, std::string language_id_) : root_uri(std::move(root_uri_)), language_id( - std::move(language_id_)) { - process = std::make_unique<TinyProcessLib::Process>(language_id + "-language-server", root_uri, - [this](const char *bytes, size_t n) { - server_message_stream.write(bytes, n); - parse_server_message(); - }, [](const char *bytes, size_t n) { - std::cerr.write(bytes, n); - }, true); + std::move(language_id_)) { + process = std::make_unique<TinyProcessLib::Process>(language_id + "-language-server", root_uri, + [this](const char *bytes, size_t n) { + server_message_stream.write(bytes, n); + parse_server_message(); + }, [](const char *bytes, size_t n) { + std::cerr.write(bytes, n); + }, true); } std::shared_ptr<LanguageProtocol::Client> LanguageProtocol::Client::get(const boost::filesystem::path &file_path, const std::string &language_id) { - std::string root_uri; - auto build = Project::Build::create(file_path); - if (!build->project_path.empty()) - root_uri = build->project_path.string(); - else - root_uri = file_path.parent_path().string(); - - auto cache_id = root_uri + '|' + language_id; - - static std::unordered_map<std::string, std::weak_ptr<Client>> cache; - static std::mutex mutex; - std::lock_guard<std::mutex> lock(mutex); - auto it = cache.find(cache_id); - if (it == cache.end()) - it = cache.emplace(cache_id, std::weak_ptr<Client>()).first; - auto instance = it->second.lock(); - if (!instance) - it->second = instance = std::shared_ptr<Client>(new Client(root_uri, language_id), [](Client *client_ptr) { - std::thread delete_thread([client_ptr] { - delete client_ptr; - }); - delete_thread.detach(); - }); - return instance; + std::string root_uri; + auto build = Project::Build::create(file_path); + if (!build->project_path.empty()) + root_uri = build->project_path.string(); + else + root_uri = file_path.parent_path().string(); + + auto cache_id = root_uri + '|' + language_id; + + static std::unordered_map<std::string, std::weak_ptr<Client>> cache; + static std::mutex mutex; + std::lock_guard<std::mutex> lock(mutex); + auto it = cache.find(cache_id); + if (it == cache.end()) + it = cache.emplace(cache_id, std::weak_ptr<Client>()).first; + auto instance = it->second.lock(); + if (!instance) + it->second = instance = std::shared_ptr<Client>(new Client(root_uri, language_id), [](Client *client_ptr) { + std::thread delete_thread([client_ptr] { + delete client_ptr; + }); + delete_thread.detach(); + }); + return instance; } LanguageProtocol::Client::~Client() { - std::promise<void> result_processed; - write_request(nullptr, "shutdown", "", - [this, &result_processed](const boost::property_tree::ptree &result, bool error) { - if (!error) - this->write_notification("exit", ""); - result_processed.set_value(); - }); - result_processed.get_future().get(); - - std::unique_lock<std::mutex> lock(timeout_threads_mutex); - for (auto &thread: timeout_threads) - thread.join(); - - int exit_status = -1; - for (size_t c = 0; c < 20; ++c) { - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - if (process->try_get_exit_status(exit_status)) - break; - } - if (output_messages_and_errors) - std::cout << "Language server exit status: " << exit_status << std::endl; - if (exit_status == -1) - process->kill(); + std::promise<void> result_processed; + write_request(nullptr, "shutdown", "", + [this, &result_processed](const boost::property_tree::ptree &result, bool error) { + if (!error) + this->write_notification("exit", ""); + result_processed.set_value(); + }); + result_processed.get_future().get(); + + std::unique_lock<std::mutex> lock(timeout_threads_mutex); + for (auto &thread: timeout_threads) + thread.join(); + + int exit_status = -1; + for (size_t c = 0; c < 20; ++c) { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + if (process->try_get_exit_status(exit_status)) + break; + } + if (output_messages_and_errors) + std::cout << "Language server exit status: " << exit_status << std::endl; + if (exit_status == -1) + process->kill(); } LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::LanguageProtocolView *view) { - if (view) { - std::unique_lock<std::mutex> lock(views_mutex); - views.emplace(view); - } + if (view) { + std::unique_lock<std::mutex> lock(views_mutex); + views.emplace(view); + } - std::lock_guard<std::mutex> lock(initialize_mutex); - - if (initialized) - return capabilities; - - std::promise<void> result_processed; - write_request(nullptr, "initialize", - "\"processId\":" + std::to_string(process->get_id()) + ",\"rootUri\":\"file://" + root_uri + - "\",\"capabilities\":{\"workspace\":{\"didChangeConfiguration\":{\"dynamicRegistration\":true},\"didChangeWatchedFiles\":{\"dynamicRegistration\":true},\"symbol\":{\"dynamicRegistration\":true},\"executeCommand\":{\"dynamicRegistration\":true}},\"textDocument\":{\"synchronization\":{\"dynamicRegistration\":true,\"willSave\":true,\"willSaveWaitUntil\":true,\"didSave\":true},\"completion\":{\"dynamicRegistration\":true,\"completionItem\":{\"snippetSupport\":true}},\"hover\":{\"dynamicRegistration\":true},\"signatureHelp\":{\"dynamicRegistration\":true},\"definition\":{\"dynamicRegistration\":true},\"references\":{\"dynamicRegistration\":true},\"documentHighlight\":{\"dynamicRegistration\":true},\"documentSymbol\":{\"dynamicRegistration\":true},\"codeAction\":{\"dynamicRegistration\":true},\"codeLens\":{\"dynamicRegistration\":true},\"formatting\":{\"dynamicRegistration\":true},\"rangeFormatting\":{\"dynamicRegistration\":true},\"onTypeFormatting\":{\"dynamicRegistration\":true},\"rename\":{\"dynamicRegistration\":true},\"documentLink\":{\"dynamicRegistration\":true}}},\"initializationOptions\":{\"omitInitBuild\":true},\"trace\":\"off\"", - [this, &result_processed](const boost::property_tree::ptree &result, bool error) { - if (!error) { - auto capabilities_pt = result.find("capabilities"); - if (capabilities_pt != result.not_found()) { - capabilities.text_document_sync = static_cast<LanguageProtocol::Capabilities::TextDocumentSync>(capabilities_pt->second.get<unsigned>( - "textDocumentSync", 0)); - capabilities.hover = capabilities_pt->second.get<bool>("hoverProvider", false); - capabilities.completion = capabilities_pt->second.find("completionProvider") != - capabilities_pt->second.not_found() ? true : false; - capabilities.definition = capabilities_pt->second.get<bool>("definitionProvider", false); - capabilities.references = capabilities_pt->second.get<bool>("referencesProvider", false); - capabilities.document_highlight = capabilities_pt->second.get<bool>( - "documentHighlightProvider", false); - capabilities.workspace_symbol = capabilities_pt->second.get<bool>( - "workspaceSymbolProvider", false); - capabilities.document_formatting = capabilities_pt->second.get<bool>( - "documentFormattingProvider", false); - capabilities.document_range_formatting = capabilities_pt->second.get<bool>( - "documentRangeFormattingProvider", false); - capabilities.rename = capabilities_pt->second.get<bool>("renameProvider", false); - } - - write_notification("initialized", ""); - if (language_id == "rust") - write_notification("workspace/didChangeConfiguration", - "\"settings\":{\"rust\":{\"sysroot\":null,\"target\":null,\"rustflags\":null,\"clear_env_rust_log\":true,\"build_lib\":null,\"build_bin\":null,\"cfg_test\":false,\"unstable_features\":false,\"wait_to_build\":500,\"show_warnings\":true,\"goto_def_racer_fallback\":false,\"use_crate_blacklist\":true,\"build_on_save\":false,\"workspace_mode\":true,\"analyze_package\":null,\"features\":[],\"all_features\":false,\"no_default_features\":false}}"); - } - result_processed.set_value(); - }); - result_processed.get_future().get(); + std::lock_guard<std::mutex> lock(initialize_mutex); - initialized = true; + if (initialized) return capabilities; + + std::promise<void> result_processed; + write_request(nullptr, "initialize", + "\"processId\":" + std::to_string(process->get_id()) + ",\"rootUri\":\"file://" + root_uri + + "\",\"capabilities\":{\"workspace\":{\"didChangeConfiguration\":{\"dynamicRegistration\":true},\"didChangeWatchedFiles\":{\"dynamicRegistration\":true},\"symbol\":{\"dynamicRegistration\":true},\"executeCommand\":{\"dynamicRegistration\":true}},\"textDocument\":{\"synchronization\":{\"dynamicRegistration\":true,\"willSave\":true,\"willSaveWaitUntil\":true,\"didSave\":true},\"completion\":{\"dynamicRegistration\":true,\"completionItem\":{\"snippetSupport\":true}},\"hover\":{\"dynamicRegistration\":true},\"signatureHelp\":{\"dynamicRegistration\":true},\"definition\":{\"dynamicRegistration\":true},\"references\":{\"dynamicRegistration\":true},\"documentHighlight\":{\"dynamicRegistration\":true},\"documentSymbol\":{\"dynamicRegistration\":true},\"codeAction\":{\"dynamicRegistration\":true},\"codeLens\":{\"dynamicRegistration\":true},\"formatting\":{\"dynamicRegistration\":true},\"rangeFormatting\":{\"dynamicRegistration\":true},\"onTypeFormatting\":{\"dynamicRegistration\":true},\"rename\":{\"dynamicRegistration\":true},\"documentLink\":{\"dynamicRegistration\":true}}},\"initializationOptions\":{\"omitInitBuild\":true},\"trace\":\"off\"", + [this, &result_processed](const boost::property_tree::ptree &result, bool error) { + if (!error) { + auto capabilities_pt = result.find("capabilities"); + if (capabilities_pt != result.not_found()) { + capabilities.text_document_sync = static_cast<LanguageProtocol::Capabilities::TextDocumentSync>(capabilities_pt->second.get<unsigned>( + "textDocumentSync", 0)); + capabilities.hover = capabilities_pt->second.get<bool>("hoverProvider", false); + capabilities.completion = capabilities_pt->second.find("completionProvider") != + capabilities_pt->second.not_found() ? true : false; + capabilities.definition = capabilities_pt->second.get<bool>("definitionProvider", false); + capabilities.references = capabilities_pt->second.get<bool>("referencesProvider", false); + capabilities.document_highlight = capabilities_pt->second.get<bool>( + "documentHighlightProvider", false); + capabilities.workspace_symbol = capabilities_pt->second.get<bool>( + "workspaceSymbolProvider", false); + capabilities.document_formatting = capabilities_pt->second.get<bool>( + "documentFormattingProvider", false); + capabilities.document_range_formatting = capabilities_pt->second.get<bool>( + "documentRangeFormattingProvider", false); + capabilities.rename = capabilities_pt->second.get<bool>("renameProvider", false); + } + + write_notification("initialized", ""); + if (language_id == "rust") + write_notification("workspace/didChangeConfiguration", + "\"settings\":{\"rust\":{\"sysroot\":null,\"target\":null,\"rustflags\":null,\"clear_env_rust_log\":true,\"build_lib\":null,\"build_bin\":null,\"cfg_test\":false,\"unstable_features\":false,\"wait_to_build\":500,\"show_warnings\":true,\"goto_def_racer_fallback\":false,\"use_crate_blacklist\":true,\"build_on_save\":false,\"workspace_mode\":true,\"analyze_package\":null,\"features\":[],\"all_features\":false,\"no_default_features\":false}}"); + } + result_processed.set_value(); + }); + result_processed.get_future().get(); + + initialized = true; + return capabilities; } void LanguageProtocol::Client::close(Source::LanguageProtocolView *view) { - { - std::unique_lock<std::mutex> lock(views_mutex); - auto it = views.find(view); - if (it != views.end()) - views.erase(it); - } - std::unique_lock<std::mutex> lock(read_write_mutex); - for (auto it = handlers.begin(); it != handlers.end();) { - if (it->second.first == view) - it = handlers.erase(it); - else - it++; - } + { + std::unique_lock<std::mutex> lock(views_mutex); + auto it = views.find(view); + if (it != views.end()) + views.erase(it); + } + std::unique_lock<std::mutex> lock(read_write_mutex); + for (auto it = handlers.begin(); it != handlers.end();) { + if (it->second.first == view) + it = handlers.erase(it); + else + it++; + } } void LanguageProtocol::Client::parse_server_message() { - if (!header_read) { - std::string line; - while (!header_read && std::getline(server_message_stream, line)) { - if (!line.empty()) { - if (line.back() == '\r') - line.pop_back(); - if (line.compare(0, 16, "Content-Length: ") == 0) { - try { - server_message_size = static_cast<size_t>(std::stoul(line.substr(16))); - } - catch (...) {} - } - } - if (line.empty()) { - server_message_content_pos = server_message_stream.tellg(); - server_message_size += server_message_content_pos; - header_read = true; - } + if (!header_read) { + std::string line; + while (!header_read && std::getline(server_message_stream, line)) { + if (!line.empty()) { + if (line.back() == '\r') + line.pop_back(); + if (line.compare(0, 16, "Content-Length: ") == 0) { + try { + server_message_size = static_cast<size_t>(std::stoul(line.substr(16))); + } + catch (...) {} } + } + if (line.empty()) { + server_message_content_pos = server_message_stream.tellg(); + server_message_size += server_message_content_pos; + header_read = true; + } } - - if (header_read) { - server_message_stream.seekg(0, std::ios::end); - size_t read_size = server_message_stream.tellg(); - std::stringstream tmp; - if (read_size >= server_message_size) { - if (read_size > server_message_size) { - server_message_stream.seekg(server_message_size, std::ios::beg); - server_message_stream.seekp(server_message_size, std::ios::beg); - for (size_t c = server_message_size; c < read_size; ++c) { - tmp.put(server_message_stream.get()); - server_message_stream.put(' '); - } + } + + if (header_read) { + server_message_stream.seekg(0, std::ios::end); + size_t read_size = server_message_stream.tellg(); + std::stringstream tmp; + if (read_size >= server_message_size) { + if (read_size > server_message_size) { + server_message_stream.seekg(server_message_size, std::ios::beg); + server_message_stream.seekp(server_message_size, std::ios::beg); + for (size_t c = server_message_size; c < read_size; ++c) { + tmp.put(server_message_stream.get()); + server_message_stream.put(' '); + } + } + + server_message_stream.seekg(server_message_content_pos, std::ios::beg); + boost::property_tree::ptree pt; + boost::property_tree::read_json(server_message_stream, pt); + + if (output_messages_and_errors) { + std::cout << "language server: "; + boost::property_tree::write_json(std::cout, pt); + } + + auto message_id = pt.get<size_t>("id", 0); + auto result_it = pt.find("result"); + auto error_it = pt.find("error"); + { + std::unique_lock<std::mutex> lock(read_write_mutex); + if (result_it != pt.not_found()) { + if (message_id) { + auto id_it = handlers.find(message_id); + if (id_it != handlers.end()) { + auto function = std::move(id_it->second.second); + handlers.erase(id_it->first); + lock.unlock(); + function(result_it->second, false); + lock.lock(); } - - server_message_stream.seekg(server_message_content_pos, std::ios::beg); - boost::property_tree::ptree pt; - boost::property_tree::read_json(server_message_stream, pt); - - if (output_messages_and_errors) { - std::cout << "language server: "; - boost::property_tree::write_json(std::cout, pt); + } + } else if (error_it != pt.not_found()) { + if (!output_messages_and_errors) + boost::property_tree::write_json(std::cerr, pt); + if (message_id) { + auto id_it = handlers.find(message_id); + if (id_it != handlers.end()) { + auto function = std::move(id_it->second.second); + handlers.erase(id_it->first); + lock.unlock(); + function(result_it->second, true); + lock.lock(); } - - auto message_id = pt.get<size_t>("id", 0); - auto result_it = pt.find("result"); - auto error_it = pt.find("error"); - { - std::unique_lock<std::mutex> lock(read_write_mutex); - if (result_it != pt.not_found()) { - if (message_id) { - auto id_it = handlers.find(message_id); - if (id_it != handlers.end()) { - auto function = std::move(id_it->second.second); - handlers.erase(id_it->first); - lock.unlock(); - function(result_it->second, false); - lock.lock(); - } - } - } else if (error_it != pt.not_found()) { - if (!output_messages_and_errors) - boost::property_tree::write_json(std::cerr, pt); - if (message_id) { - auto id_it = handlers.find(message_id); - if (id_it != handlers.end()) { - auto function = std::move(id_it->second.second); - handlers.erase(id_it->first); - lock.unlock(); - function(result_it->second, true); - lock.lock(); - } - } - } else { - auto method_it = pt.find("method"); - if (method_it != pt.not_found()) { - auto params_it = pt.find("params"); - if (params_it != pt.not_found()) { - lock.unlock(); - handle_server_request(method_it->second.get_value<std::string>(""), params_it->second); - lock.lock(); - } - } - } - } - - server_message_stream = std::stringstream(); - header_read = false; - server_message_size = static_cast<size_t>(-1); - - tmp.seekg(0, std::ios::end); - if (tmp.tellg() > 0) { - tmp.seekg(0, std::ios::beg); - server_message_stream << tmp.rdbuf(); - parse_server_message(); + } + } else { + auto method_it = pt.find("method"); + if (method_it != pt.not_found()) { + auto params_it = pt.find("params"); + if (params_it != pt.not_found()) { + lock.unlock(); + handle_server_request(method_it->second.get_value<std::string>(""), params_it->second); + lock.lock(); } + } } + } + + server_message_stream = std::stringstream(); + header_read = false; + server_message_size = static_cast<size_t>(-1); + + tmp.seekg(0, std::ios::end); + if (tmp.tellg() > 0) { + tmp.seekg(0, std::ios::beg); + server_message_stream << tmp.rdbuf(); + parse_server_message(); + } } + } } void LanguageProtocol::Client::write_request(Source::LanguageProtocolView *view, const std::string &method, const std::string ¶ms, std::function<void(const boost::property_tree::ptree &, bool error)> &&function) { - std::unique_lock<std::mutex> lock(read_write_mutex); - if (function) { - handlers.emplace(message_id, std::make_pair(view, std::move(function))); - - auto message_id = this->message_id; - std::unique_lock<std::mutex> lock(timeout_threads_mutex); - timeout_threads.emplace_back([this, message_id] { - for (size_t c = 0; c < 20; ++c) { - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - std::unique_lock<std::mutex> lock(read_write_mutex); - auto id_it = handlers.find(message_id); - if (id_it == handlers.end()) - return; - } - std::unique_lock<std::mutex> lock(read_write_mutex); - auto id_it = handlers.find(message_id); - if (id_it != handlers.end()) { - auto function = std::move(id_it->second.second); - handlers.erase(id_it->first); - lock.unlock(); - function(boost::property_tree::ptree(), false); - lock.lock(); - } - }); - } - std::string content("{\"jsonrpc\":\"2.0\",\"id\":" + std::to_string(message_id++) + ",\"method\":\"" + method + - "\",\"params\":{" + params + "}}"); - auto message = "Content-Length: " + std::to_string(content.size()) + "\r\n\r\n" + content; - if (output_messages_and_errors) - std::cout << "Language client: " << content << std::endl; - if (!process->write(message)) { - Terminal::get().async_print( - "Error writing to language protocol server. Please close and reopen all project source files.\n", true); - auto id_it = handlers.find(message_id - 1); - if (id_it != handlers.end()) { - auto function = std::move(id_it->second.second); - handlers.erase(id_it->first); - lock.unlock(); - function(boost::property_tree::ptree(), false); - lock.lock(); - } + std::unique_lock<std::mutex> lock(read_write_mutex); + if (function) { + handlers.emplace(message_id, std::make_pair(view, std::move(function))); + + auto message_id = this->message_id; + std::unique_lock<std::mutex> lock(timeout_threads_mutex); + timeout_threads.emplace_back([this, message_id] { + for (size_t c = 0; c < 20; ++c) { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + std::unique_lock<std::mutex> lock(read_write_mutex); + auto id_it = handlers.find(message_id); + if (id_it == handlers.end()) + return; + } + std::unique_lock<std::mutex> lock(read_write_mutex); + auto id_it = handlers.find(message_id); + if (id_it != handlers.end()) { + auto function = std::move(id_it->second.second); + handlers.erase(id_it->first); + lock.unlock(); + function(boost::property_tree::ptree(), false); + lock.lock(); + } + }); + } + std::string content("{\"jsonrpc\":\"2.0\",\"id\":" + std::to_string(message_id++) + ",\"method\":\"" + method + + "\",\"params\":{" + params + "}}"); + auto message = "Content-Length: " + std::to_string(content.size()) + "\r\n\r\n" + content; + if (output_messages_and_errors) + std::cout << "Language client: " << content << std::endl; + if (!process->write(message)) { + Terminal::get().async_print( + "Error writing to language protocol server. Please close and reopen all project source files.\n", true); + auto id_it = handlers.find(message_id - 1); + if (id_it != handlers.end()) { + auto function = std::move(id_it->second.second); + handlers.erase(id_it->first); + lock.unlock(); + function(boost::property_tree::ptree(), false); + lock.lock(); } + } } void LanguageProtocol::Client::write_notification(const std::string &method, const std::string ¶ms) { - std::unique_lock<std::mutex> lock(read_write_mutex); - std::string content("{\"jsonrpc\":\"2.0\",\"method\":\"" + method + "\",\"params\":{" + params + "}}"); - auto message = "Content-Length: " + std::to_string(content.size()) + "\r\n\r\n" + content; - if (output_messages_and_errors) - std::cout << "Language client: " << content << std::endl; - process->write(message); + std::unique_lock<std::mutex> lock(read_write_mutex); + std::string content("{\"jsonrpc\":\"2.0\",\"method\":\"" + method + "\",\"params\":{" + params + "}}"); + auto message = "Content-Length: " + std::to_string(content.size()) + "\r\n\r\n" + content; + if (output_messages_and_errors) + std::cout << "Language client: " << content << std::endl; + process->write(message); } void LanguageProtocol::Client::handle_server_request(const std::string &method, const boost::property_tree::ptree ¶ms) { - if (method == "textDocument/publishDiagnostics") { - std::vector<Diagnostic> diagnostics; - auto uri = params.get<std::string>("uri", ""); - if (!uri.empty()) { - auto diagnostics_pt = params.get_child("diagnostics", boost::property_tree::ptree()); - for (auto it = diagnostics_pt.begin(); it != diagnostics_pt.end(); ++it) { - auto range_it = it->second.find("range"); - if (range_it != it->second.not_found()) { - auto start_it = range_it->second.find("start"); - auto end_it = range_it->second.find("end"); - if (start_it != range_it->second.not_found() && start_it != range_it->second.not_found()) { - diagnostics.emplace_back(Diagnostic{it->second.get<std::string>("message", ""), - std::make_pair<Source::Offset, Source::Offset>( - Source::Offset( - start_it->second.get<unsigned>("line", 0), - start_it->second.get<unsigned>("character", - 0)), - Source::Offset( - end_it->second.get<unsigned>("line", 0), - end_it->second.get<unsigned>("character", - 0))), - it->second.get<unsigned>("severity", 0), uri}); - } - } - } - std::unique_lock<std::mutex> lock(views_mutex); - for (auto view: views) { - if (view->uri == uri) { - view->update_diagnostics(std::move(diagnostics)); - break; - } - } + if (method == "textDocument/publishDiagnostics") { + std::vector<Diagnostic> diagnostics; + auto uri = params.get<std::string>("uri", ""); + if (!uri.empty()) { + auto diagnostics_pt = params.get_child("diagnostics", boost::property_tree::ptree()); + for (auto it = diagnostics_pt.begin(); it != diagnostics_pt.end(); ++it) { + auto range_it = it->second.find("range"); + if (range_it != it->second.not_found()) { + auto start_it = range_it->second.find("start"); + auto end_it = range_it->second.find("end"); + if (start_it != range_it->second.not_found() && start_it != range_it->second.not_found()) { + diagnostics.emplace_back(Diagnostic{it->second.get<std::string>("message", ""), + std::make_pair<Source::Offset, Source::Offset>( + Source::Offset( + start_it->second.get<unsigned>("line", 0), + start_it->second.get<unsigned>("character", + 0)), + Source::Offset( + end_it->second.get<unsigned>("line", 0), + end_it->second.get<unsigned>("character", + 0))), + it->second.get<unsigned>("severity", 0), uri}); + } } + } + std::unique_lock<std::mutex> lock(views_mutex); + for (auto view: views) { + if (view->uri == uri) { + view->update_diagnostics(std::move(diagnostics)); + break; + } + } } + } } Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language, std::string language_id_) - : Source::BaseView(file_path, language), Source::View(file_path, language), uri("file://" + file_path.string()), - language_id(std::move(language_id_)), client(LanguageProtocol::Client::get(file_path, language_id)), - autocomplete(this, interactive_completion, last_keyval, false) { - configure(); - get_source_buffer()->set_language(language); - get_source_buffer()->set_highlight_syntax(true); - - similar_symbol_tag = get_buffer()->create_tag(); - similar_symbol_tag->property_weight() = Pango::WEIGHT_ULTRAHEAVY; - - status_state = "initializing..."; - if (update_status_state) - update_status_state(this); - - if (language_id == "javascript") { - boost::filesystem::path project_path; - auto build = Project::Build::create(file_path); - if (auto npm_build = dynamic_cast<Project::NpmBuild *>(build.get())) { - boost::system::error_code ec; - if (!npm_build->project_path.empty() && - boost::filesystem::exists(npm_build->project_path / ".flowconfig", ec)) { - auto executable = npm_build->project_path / "node_modules" / ".bin" / - "flow"; // It is recommended to use Flow binary installed in project, despite the security risk of doing so... - if (boost::filesystem::exists(executable, ec)) - flow_coverage_executable = executable; - else - flow_coverage_executable = filesystem::find_executable("flow"); - } - } + : Source::BaseView(file_path, language), Source::View(file_path, language), uri("file://" + file_path.string()), + language_id(std::move(language_id_)), client(LanguageProtocol::Client::get(file_path, language_id)), + autocomplete(this, interactive_completion, last_keyval, false) { + configure(); + get_source_buffer()->set_language(language); + get_source_buffer()->set_highlight_syntax(true); + + similar_symbol_tag = get_buffer()->create_tag(); + similar_symbol_tag->property_weight() = Pango::WEIGHT_ULTRAHEAVY; + + status_state = "initializing..."; + if (update_status_state) + update_status_state(this); + + if (language_id == "javascript") { + boost::filesystem::path project_path; + auto build = Project::Build::create(file_path); + if (auto npm_build = dynamic_cast<Project::NpmBuild *>(build.get())) { + boost::system::error_code ec; + if (!npm_build->project_path.empty() && + boost::filesystem::exists(npm_build->project_path / ".flowconfig", ec)) { + auto executable = npm_build->project_path / "node_modules" / ".bin" / + "flow"; // It is recommended to use Flow binary installed in project, despite the security risk of doing so... + if (boost::filesystem::exists(executable, ec)) + flow_coverage_executable = executable; + else + flow_coverage_executable = filesystem::find_executable("flow"); + } } + } - initialize_thread = std::thread([this] { - auto capabilities = client->initialize(this); + initialize_thread = std::thread([this] { + auto capabilities = client->initialize(this); - if (!flow_coverage_executable.empty()) - add_flow_coverage_tooltips(true); - - dispatcher.post([this, capabilities] { - this->capabilities = capabilities; + if (!flow_coverage_executable.empty()) + add_flow_coverage_tooltips(true); - std::string text = get_buffer()->get_text(); - escape_text(text); - client->write_notification("textDocument/didOpen", - "\"textDocument\":{\"uri\":\"" + uri + "\",\"languageId\":\"" + language_id + - "\",\"version\":" + std::to_string(document_version++) + ",\"text\":\"" + text + - "\"}"); + dispatcher.post([this, capabilities] { + this->capabilities = capabilities; - setup_autocomplete(); - setup_navigation_and_refactoring(); - Menu::get().toggle_menu_items(); + std::string text = get_buffer()->get_text(); + escape_text(text); + client->write_notification("textDocument/didOpen", + "\"textDocument\":{\"uri\":\"" + uri + "\",\"languageId\":\"" + language_id + + "\",\"version\":" + std::to_string(document_version++) + ",\"text\":\"" + text + + "\"}"); - if (status_state == "initializing...") { - status_state = ""; - if (update_status_state) - update_status_state(this); - } - }); - }); + setup_autocomplete(); + setup_navigation_and_refactoring(); + Menu::get().toggle_menu_items(); - get_buffer()->signal_changed().connect([this] { - get_buffer()->remove_tag(similar_symbol_tag, get_buffer()->begin(), get_buffer()->end()); + if (status_state == "initializing...") { + status_state = ""; + if (update_status_state) + update_status_state(this); + } }); - - get_buffer()->signal_mark_set().connect( - [this](const Gtk::TextBuffer::iterator &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { - if (mark->get_name() == "insert") { - delayed_tag_similar_symbols_connection.disconnect(); - delayed_tag_similar_symbols_connection = Glib::signal_timeout().connect([this]() { - tag_similar_symbols(); - return false; - }, 200); - } - }); - - get_buffer()->signal_insert().connect( - [this](const Gtk::TextBuffer::iterator &start, const Glib::ustring &text_, int bytes) { - std::string content_changes; - if (capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::NONE) - return; - if (capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::INCREMENTAL) { - std::string text = text_; - escape_text(text); - content_changes = - "{\"range\":{\"start\":{\"line\": " + std::to_string(start.get_line()) + ",\"character\":" + - std::to_string(start.get_line_offset()) + "},\"end\":{\"line\":" + - std::to_string(start.get_line()) + ",\"character\":" + - std::to_string(start.get_line_offset()) + "}},\"text\":\"" + text + "\"}"; - } else { - std::string text = get_buffer()->get_text(); - escape_text(text); - content_changes = "{\"text\":\"" + text + "\"}"; - } - client->write_notification("textDocument/didChange", - "\"textDocument\":{\"uri\":\"" + uri + "\",\"version\":" + - std::to_string(document_version++) + "},\"contentChanges\":[" + - content_changes + "]"); - }, false); - - get_buffer()->signal_erase().connect( - [this](const Gtk::TextBuffer::iterator &start, const Gtk::TextBuffer::iterator &end) { - std::string content_changes; - if (capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::NONE) - return; - if (capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::INCREMENTAL) - content_changes = - "{\"range\":{\"start\":{\"line\": " + std::to_string(start.get_line()) + ",\"character\":" + - std::to_string(start.get_line_offset()) + "},\"end\":{\"line\":" + - std::to_string(end.get_line()) + ",\"character\":" + std::to_string(end.get_line_offset()) + - "}},\"text\":\"\"}"; - else { - std::string text = get_buffer()->get_text(); - escape_text(text); - content_changes = "{\"text\":\"" + text + "\"}"; - } - client->write_notification("textDocument/didChange", - "\"textDocument\":{\"uri\":\"" + uri + "\",\"version\":" + - std::to_string(document_version++) + "},\"contentChanges\":[" + - content_changes + "]"); - }, false); + }); + + get_buffer()->signal_changed().connect([this] { + get_buffer()->remove_tag(similar_symbol_tag, get_buffer()->begin(), get_buffer()->end()); + }); + + get_buffer()->signal_mark_set().connect( + [this](const Gtk::TextBuffer::iterator &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { + if (mark->get_name() == "insert") { + delayed_tag_similar_symbols_connection.disconnect(); + delayed_tag_similar_symbols_connection = Glib::signal_timeout().connect([this]() { + tag_similar_symbols(); + return false; + }, 200); + } + }); + + get_buffer()->signal_insert().connect( + [this](const Gtk::TextBuffer::iterator &start, const Glib::ustring &text_, int bytes) { + std::string content_changes; + if (capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::NONE) + return; + if (capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::INCREMENTAL) { + std::string text = text_; + escape_text(text); + content_changes = + "{\"range\":{\"start\":{\"line\": " + std::to_string(start.get_line()) + ",\"character\":" + + std::to_string(start.get_line_offset()) + "},\"end\":{\"line\":" + + std::to_string(start.get_line()) + ",\"character\":" + + std::to_string(start.get_line_offset()) + "}},\"text\":\"" + text + "\"}"; + } else { + std::string text = get_buffer()->get_text(); + escape_text(text); + content_changes = "{\"text\":\"" + text + "\"}"; + } + client->write_notification("textDocument/didChange", + "\"textDocument\":{\"uri\":\"" + uri + "\",\"version\":" + + std::to_string(document_version++) + "},\"contentChanges\":[" + + content_changes + "]"); + }, false); + + get_buffer()->signal_erase().connect( + [this](const Gtk::TextBuffer::iterator &start, const Gtk::TextBuffer::iterator &end) { + std::string content_changes; + if (capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::NONE) + return; + if (capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::INCREMENTAL) + content_changes = + "{\"range\":{\"start\":{\"line\": " + std::to_string(start.get_line()) + ",\"character\":" + + std::to_string(start.get_line_offset()) + "},\"end\":{\"line\":" + + std::to_string(end.get_line()) + ",\"character\":" + std::to_string(end.get_line_offset()) + + "}},\"text\":\"\"}"; + else { + std::string text = get_buffer()->get_text(); + escape_text(text); + content_changes = "{\"text\":\"" + text + "\"}"; + } + client->write_notification("textDocument/didChange", + "\"textDocument\":{\"uri\":\"" + uri + "\",\"version\":" + + std::to_string(document_version++) + "},\"contentChanges\":[" + + content_changes + "]"); + }, false); } Source::LanguageProtocolView::~LanguageProtocolView() { - delayed_tag_similar_symbols_connection.disconnect(); + delayed_tag_similar_symbols_connection.disconnect(); - if (initialize_thread.joinable()) - initialize_thread.join(); + if (initialize_thread.joinable()) + initialize_thread.join(); - autocomplete.state = Autocomplete::State::IDLE; - if (autocomplete.thread.joinable()) - autocomplete.thread.join(); + autocomplete.state = Autocomplete::State::IDLE; + if (autocomplete.thread.joinable()) + autocomplete.thread.join(); - client->write_notification("textDocument/didClose", "\"textDocument\":{\"uri\":\"" + uri + "\"}"); - client->close(this); + client->write_notification("textDocument/didClose", "\"textDocument\":{\"uri\":\"" + uri + "\"}"); + client->close(this); - client = nullptr; + client = nullptr; } bool Source::LanguageProtocolView::save() { - if (!Source::View::save()) - return false; + if (!Source::View::save()) + return false; - if (!flow_coverage_executable.empty()) - add_flow_coverage_tooltips(false); + if (!flow_coverage_executable.empty()) + add_flow_coverage_tooltips(false); - return true; + return true; } void Source::LanguageProtocolView::setup_navigation_and_refactoring() { - if (capabilities.document_formatting) { - format_style = [this](bool continue_without_style_file) { - if (!continue_without_style_file) { - bool has_style_file = false; - auto style_file_search_path = this->file_path.parent_path(); - auto style_file = '.' + language_id + "-format"; - - while (true) { - if (boost::filesystem::exists(style_file_search_path / style_file)) { - has_style_file = true; - break; - } - if (style_file_search_path == style_file_search_path.root_directory()) - break; - style_file_search_path = style_file_search_path.parent_path(); - } - - if (!has_style_file && !continue_without_style_file) - return; - } - - class Replace { - public: - Offset start, end; - std::string text; - }; - std::vector<Replace> replaces; - std::promise<void> result_processed; - - std::string method; - std::string params; - std::string options("\"tabSize\":" + std::to_string(tab_size) + ",\"insertSpaces\":" + - (tab_char == ' ' ? "true" : "false")); - if (get_buffer()->get_has_selection() && capabilities.document_range_formatting) { - method = "textDocument/rangeFormatting"; - Gtk::TextIter start, end; - get_buffer()->get_selection_bounds(start, end); - params = "\"textDocument\":{\"uri\":\"" + uri + "\"},\"range\":{\"start\":{\"line\":" + - std::to_string(start.get_line()) + ",\"character\":" + - std::to_string(start.get_line_offset()) + "},\"end\":{\"line\":" + - std::to_string(end.get_line()) + ",\"character\":" + std::to_string(end.get_line_offset()) + - "}},\"options\":{" + options + "}"; - } else { - method = "textDocument/formatting"; - params = "\"textDocument\":{\"uri\":\"" + uri + "\"},\"options\":{" + options + "}"; - } + if (capabilities.document_formatting) { + format_style = [this](bool continue_without_style_file) { + if (!continue_without_style_file) { + bool has_style_file = false; + auto style_file_search_path = this->file_path.parent_path(); + auto style_file = '.' + language_id + "-format"; + + while (true) { + if (boost::filesystem::exists(style_file_search_path / style_file)) { + has_style_file = true; + break; + } + if (style_file_search_path == style_file_search_path.root_directory()) + break; + style_file_search_path = style_file_search_path.parent_path(); + } - client->write_request(this, method, params, - [&replaces, &result_processed](const boost::property_tree::ptree &result, - bool error) { - if (!error) { - for (auto it = result.begin(); it != result.end(); ++it) { - auto range_it = it->second.find("range"); - auto text_it = it->second.find("newText"); - if (range_it != it->second.not_found() && - text_it != it->second.not_found()) { - auto start_it = range_it->second.find("start"); - auto end_it = range_it->second.find("end"); - if (start_it != range_it->second.not_found() && - end_it != range_it->second.not_found()) { - try { - replaces.emplace_back( - Replace{Offset(start_it->second.get<unsigned>("line"), - start_it->second.get<unsigned>( - "character")), - Offset(end_it->second.get<unsigned>("line"), - end_it->second.get<unsigned>( - "character")), - text_it->second.get_value<std::string>()}); - } - catch (...) {} - } - } - } + if (!has_style_file && !continue_without_style_file) + return; + } + + class Replace { + public: + Offset start, end; + std::string text; + }; + std::vector<Replace> replaces; + std::promise<void> result_processed; + + std::string method; + std::string params; + std::string options("\"tabSize\":" + std::to_string(tab_size) + ",\"insertSpaces\":" + + (tab_char == ' ' ? "true" : "false")); + if (get_buffer()->get_has_selection() && capabilities.document_range_formatting) { + method = "textDocument/rangeFormatting"; + Gtk::TextIter start, end; + get_buffer()->get_selection_bounds(start, end); + params = "\"textDocument\":{\"uri\":\"" + uri + "\"},\"range\":{\"start\":{\"line\":" + + std::to_string(start.get_line()) + ",\"character\":" + + std::to_string(start.get_line_offset()) + "},\"end\":{\"line\":" + + std::to_string(end.get_line()) + ",\"character\":" + std::to_string(end.get_line_offset()) + + "}},\"options\":{" + options + "}"; + } else { + method = "textDocument/formatting"; + params = "\"textDocument\":{\"uri\":\"" + uri + "\"},\"options\":{" + options + "}"; + } + + client->write_request(this, method, params, + [&replaces, &result_processed](const boost::property_tree::ptree &result, + bool error) { + if (!error) { + for (auto it = result.begin(); it != result.end(); ++it) { + auto range_it = it->second.find("range"); + auto text_it = it->second.find("newText"); + if (range_it != it->second.not_found() && + text_it != it->second.not_found()) { + auto start_it = range_it->second.find("start"); + auto end_it = range_it->second.find("end"); + if (start_it != range_it->second.not_found() && + end_it != range_it->second.not_found()) { + try { + replaces.emplace_back( + Replace{Offset(start_it->second.get<unsigned>("line"), + start_it->second.get<unsigned>( + "character")), + Offset(end_it->second.get<unsigned>("line"), + end_it->second.get<unsigned>( + "character")), + text_it->second.get_value<std::string>()}); + } + catch (...) {} + } + } + } + } + result_processed.set_value(); + }); + result_processed.get_future().get(); + + auto end_iter = get_buffer()->end(); + if (replaces.size() == 1 && + replaces[0].start.line == 0 && replaces[0].start.index == 0 && + (replaces[0].end.line > static_cast<unsigned>(end_iter.get_line()) || + (replaces[0].end.line == static_cast<unsigned>(end_iter.get_line()) && + replaces[0].end.index >= static_cast<unsigned>(end_iter.get_line_offset())))) { + unescape_text(replaces[0].text); + replace_text(replaces[0].text); + } else { + get_buffer()->begin_user_action(); + for (auto it = replaces.rbegin(); it != replaces.rend(); ++it) { + auto start = get_iter_at_line_pos(it->start.line, it->start.index); + auto end = get_iter_at_line_pos(it->end.line, it->end.index); + get_buffer()->erase(start, end); + start = get_iter_at_line_pos(it->start.line, it->start.index); + unescape_text(it->text); + get_buffer()->insert(start, it->text); + } + get_buffer()->end_user_action(); + } + hide_tooltips(); + }; + } + + if (capabilities.definition) { + get_declaration_location = [this]() { + auto offset = get_declaration(get_buffer()->get_insert()->get_iter()); + if (!offset) + Info::get().print("No declaration found"); + return offset; + }; + get_declaration_or_implementation_locations = [this]() { + std::vector<Offset> offsets; + auto offset = get_declaration_location(); + if (offset) + offsets.emplace_back(std::move(offset)); + return offsets; + }; + } + + if (capabilities.references) { + get_usages = [this] { + auto iter = get_buffer()->get_insert()->get_iter(); + std::vector<std::pair<Offset, std::string>> usages; + std::vector<Offset> end_offsets; + std::promise<void> result_processed; + client->write_request(this, "textDocument/references", + "\"textDocument\":{\"uri\":\"" + uri + "\"}, \"position\": {\"line\": " + + std::to_string(iter.get_line()) + ", \"character\": " + + std::to_string(iter.get_line_offset()) + + "}, \"context\": {\"includeDeclaration\": true}", + [&usages, &end_offsets, &result_processed](const boost::property_tree::ptree &result, + bool error) { + if (!error) { + try { + for (auto it = result.begin(); it != result.end(); ++it) { + auto path = it->second.get<std::string>("uri", ""); + if (path.size() >= 7) { + path.erase(0, 7); + auto range_it = it->second.find("range"); + if (range_it != it->second.not_found()) { + auto start_it = range_it->second.find("start"); + auto end_it = range_it->second.find("end"); + if (start_it != range_it->second.not_found() && + end_it != range_it->second.not_found()) { + usages.emplace_back(std::make_pair( + Offset(start_it->second.get<unsigned>("line"), + start_it->second.get<unsigned>( + "character"), path), "")); + end_offsets.emplace_back( + end_it->second.get<unsigned>("line"), + end_it->second.get<unsigned>("character")); + } } - result_processed.set_value(); - }); - result_processed.get_future().get(); - - auto end_iter = get_buffer()->end(); - if (replaces.size() == 1 && - replaces[0].start.line == 0 && replaces[0].start.index == 0 && - (replaces[0].end.line > static_cast<unsigned>(end_iter.get_line()) || - (replaces[0].end.line == static_cast<unsigned>(end_iter.get_line()) && - replaces[0].end.index >= static_cast<unsigned>(end_iter.get_line_offset())))) { - unescape_text(replaces[0].text); - replace_text(replaces[0].text); + } + } + } + catch (...) { + usages.clear(); + end_offsets.clear(); + } + } + result_processed.set_value(); + }); + result_processed.get_future().get(); + + auto embolden_token = [](std::string &line_, unsigned token_start_pos, unsigned token_end_pos) { + Glib::ustring line = line_; + if (token_start_pos >= line.size() || token_end_pos >= line.size()) + return; + + //markup token as bold + size_t pos = 0; + while ((pos = line.find('&', pos)) != Glib::ustring::npos) { + size_t pos2 = line.find(';', pos + 2); + if (token_start_pos > pos) { + token_start_pos += pos2 - pos; + token_end_pos += pos2 - pos; + } else if (token_end_pos > pos) + token_end_pos += pos2 - pos; + else + break; + pos = pos2 + 1; + } + line.insert(token_end_pos, "</b>"); + line.insert(token_start_pos, "<b>"); + + size_t start_pos = 0; + while (start_pos < line.size() && (line[start_pos] == ' ' || line[start_pos] == '\t')) + ++start_pos; + if (start_pos > 0) + line.erase(0, start_pos); + + line_ = line.raw(); + }; + + std::map<boost::filesystem::path, std::vector<std::string>> file_lines; + size_t c = static_cast<size_t>(-1); + for (auto &usage: usages) { + ++c; + auto view_it = views.end(); + for (auto it = views.begin(); it != views.end(); ++it) { + if (usage.first.file_path == (*it)->file_path) { + view_it = it; + break; + } + } + if (view_it != views.end()) { + if (usage.first.line < static_cast<unsigned>((*view_it)->get_buffer()->get_line_count())) { + auto start = (*view_it)->get_buffer()->get_iter_at_line(usage.first.line); + auto end = start; + end.forward_to_line_end(); + usage.second = Glib::Markup::escape_text((*view_it)->get_buffer()->get_text(start, end)); + embolden_token(usage.second, usage.first.index, end_offsets[c].index); + } + } else { + auto it = file_lines.find(usage.first.file_path); + if (it == file_lines.end()) { + std::ifstream ifs(usage.first.file_path.string()); + if (ifs) { + std::vector<std::string> lines; + std::string line; + while (std::getline(ifs, line)) { + if (!line.empty() && line.back() == '\r') + line.pop_back(); + lines.emplace_back(line); + } + auto pair = file_lines.emplace(usage.first.file_path, lines); + it = pair.first; } else { - get_buffer()->begin_user_action(); - for (auto it = replaces.rbegin(); it != replaces.rend(); ++it) { - auto start = get_iter_at_line_pos(it->start.line, it->start.index); - auto end = get_iter_at_line_pos(it->end.line, it->end.index); - get_buffer()->erase(start, end); - start = get_iter_at_line_pos(it->start.line, it->start.index); - unescape_text(it->text); - get_buffer()->insert(start, it->text); - } - get_buffer()->end_user_action(); + auto pair = file_lines.emplace(usage.first.file_path, std::vector<std::string>()); + it = pair.first; } - hide_tooltips(); - }; - } - - if (capabilities.definition) { - get_declaration_location = [this]() { - auto offset = get_declaration(get_buffer()->get_insert()->get_iter()); - if (!offset) - Info::get().print("No declaration found"); - return offset; - }; - get_declaration_or_implementation_locations = [this]() { - std::vector<Offset> offsets; - auto offset = get_declaration_location(); - if (offset) - offsets.emplace_back(std::move(offset)); - return offsets; - }; - } + } - if (capabilities.references) { - get_usages = [this] { - auto iter = get_buffer()->get_insert()->get_iter(); - std::vector<std::pair<Offset, std::string>> usages; - std::vector<Offset> end_offsets; - std::promise<void> result_processed; - client->write_request(this, "textDocument/references", - "\"textDocument\":{\"uri\":\"" + uri + "\"}, \"position\": {\"line\": " + - std::to_string(iter.get_line()) + ", \"character\": " + - std::to_string(iter.get_line_offset()) + - "}, \"context\": {\"includeDeclaration\": true}", - [&usages, &end_offsets, &result_processed](const boost::property_tree::ptree &result, - bool error) { - if (!error) { - try { - for (auto it = result.begin(); it != result.end(); ++it) { - auto path = it->second.get<std::string>("uri", ""); - if (path.size() >= 7) { - path.erase(0, 7); - auto range_it = it->second.find("range"); - if (range_it != it->second.not_found()) { - auto start_it = range_it->second.find("start"); - auto end_it = range_it->second.find("end"); - if (start_it != range_it->second.not_found() && - end_it != range_it->second.not_found()) { - usages.emplace_back(std::make_pair( - Offset(start_it->second.get<unsigned>("line"), - start_it->second.get<unsigned>( - "character"), path), "")); - end_offsets.emplace_back( - end_it->second.get<unsigned>("line"), - end_it->second.get<unsigned>("character")); - } - } - } - } - } - catch (...) { - usages.clear(); - end_offsets.clear(); - } - } - result_processed.set_value(); - }); - result_processed.get_future().get(); - - auto embolden_token = [](std::string &line_, unsigned token_start_pos, unsigned token_end_pos) { - Glib::ustring line = line_; - if (token_start_pos >= line.size() || token_end_pos >= line.size()) - return; - - //markup token as bold - size_t pos = 0; - while ((pos = line.find('&', pos)) != Glib::ustring::npos) { - size_t pos2 = line.find(';', pos + 2); - if (token_start_pos > pos) { - token_start_pos += pos2 - pos; - token_end_pos += pos2 - pos; - } else if (token_end_pos > pos) - token_end_pos += pos2 - pos; - else - break; - pos = pos2 + 1; - } - line.insert(token_end_pos, "</b>"); - line.insert(token_start_pos, "<b>"); - - size_t start_pos = 0; - while (start_pos < line.size() && (line[start_pos] == ' ' || line[start_pos] == '\t')) - ++start_pos; - if (start_pos > 0) - line.erase(0, start_pos); - - line_ = line.raw(); - }; - - std::map<boost::filesystem::path, std::vector<std::string>> file_lines; - size_t c = static_cast<size_t>(-1); - for (auto &usage: usages) { - ++c; - auto view_it = views.end(); - for (auto it = views.begin(); it != views.end(); ++it) { - if (usage.first.file_path == (*it)->file_path) { - view_it = it; - break; - } - } - if (view_it != views.end()) { - if (usage.first.line < static_cast<unsigned>((*view_it)->get_buffer()->get_line_count())) { - auto start = (*view_it)->get_buffer()->get_iter_at_line(usage.first.line); - auto end = start; - end.forward_to_line_end(); - usage.second = Glib::Markup::escape_text((*view_it)->get_buffer()->get_text(start, end)); - embolden_token(usage.second, usage.first.index, end_offsets[c].index); - } - } else { - auto it = file_lines.find(usage.first.file_path); - if (it == file_lines.end()) { - std::ifstream ifs(usage.first.file_path.string()); - if (ifs) { - std::vector<std::string> lines; - std::string line; - while (std::getline(ifs, line)) { - if (!line.empty() && line.back() == '\r') - line.pop_back(); - lines.emplace_back(line); - } - auto pair = file_lines.emplace(usage.first.file_path, lines); - it = pair.first; - } else { - auto pair = file_lines.emplace(usage.first.file_path, std::vector<std::string>()); - it = pair.first; - } - } - - if (usage.first.line < it->second.size()) { - usage.second = it->second[usage.first.line]; - embolden_token(usage.second, usage.first.index, end_offsets[c].index); - } - } - } + if (usage.first.line < it->second.size()) { + usage.second = it->second[usage.first.line]; + embolden_token(usage.second, usage.first.index, end_offsets[c].index); + } + } + } - if (usages.empty()) - Info::get().print("No symbol found at current cursor location"); + if (usages.empty()) + Info::get().print("No symbol found at current cursor location"); - return usages; - }; + return usages; + }; + } + + get_token_spelling = [this]() -> std::string { + auto start = get_buffer()->get_insert()->get_iter(); + auto end = start; + auto previous = start; + while (previous.backward_char() && + ((*previous >= 'A' && *previous <= 'Z') || (*previous >= 'a' && *previous <= 'z') || + (*previous >= '0' && *previous <= '9') || *previous == '_') && start.backward_char()) {} + while (((*end >= 'A' && *end <= 'Z') || (*end >= 'a' && *end <= 'z') || (*end >= '0' && *end <= '9') || + *end == '_') && end.forward_char()) {} + + if (start == end) { + Info::get().print("No valid symbol found at current cursor location"); + return ""; } - get_token_spelling = [this]() -> std::string { - auto start = get_buffer()->get_insert()->get_iter(); - auto end = start; - auto previous = start; - while (previous.backward_char() && - ((*previous >= 'A' && *previous <= 'Z') || (*previous >= 'a' && *previous <= 'z') || - (*previous >= '0' && *previous <= '9') || *previous == '_') && start.backward_char()) {} - while (((*end >= 'A' && *end <= 'Z') || (*end >= 'a' && *end <= 'z') || (*end >= '0' && *end <= '9') || - *end == '_') && end.forward_char()) {} + return get_buffer()->get_text(start, end); + }; - if (start == end) { - Info::get().print("No valid symbol found at current cursor location"); - return ""; - } + if (capabilities.rename) { + rename_similar_tokens = [this](const std::string &text) { + class Usages { + public: + boost::filesystem::path path; + std::unique_ptr<std::string> new_text; + std::vector<std::pair<Offset, Offset>> offsets; + }; - return get_buffer()->get_text(start, end); - }; + auto previous_text = get_token_spelling(); + if (previous_text.empty()) + return; - if (capabilities.rename) { - rename_similar_tokens = [this](const std::string &text) { - class Usages { - public: - boost::filesystem::path path; - std::unique_ptr<std::string> new_text; - std::vector<std::pair<Offset, Offset>> offsets; - }; - - auto previous_text = get_token_spelling(); - if (previous_text.empty()) - return; - - auto iter = get_buffer()->get_insert()->get_iter(); - std::vector<Usages> usages; - std::promise<void> result_processed; - client->write_request(this, "textDocument/rename", - "\"textDocument\":{\"uri\":\"" + uri + "\"}, \"position\": {\"line\": " + - std::to_string(iter.get_line()) + ", \"character\": " + - std::to_string(iter.get_line_offset()) + "}, \"newName\": \"" + text + "\"", - [this, &usages, &result_processed](const boost::property_tree::ptree &result, - bool error) { - if (!error) { - boost::filesystem::path project_path; - auto build = Project::Build::create(file_path); - if (!build->project_path.empty()) - project_path = build->project_path; - else - project_path = file_path.parent_path(); - try { - auto changes_it = result.find("changes"); - if (changes_it != result.not_found()) { - for (auto file_it = changes_it->second.begin(); - file_it != changes_it->second.end(); ++file_it) { - auto path = file_it->first; - if (path.size() >= 7) { - path.erase(0, 7); - if (filesystem::file_in_path(path, project_path)) { - usages.emplace_back(Usages{path, nullptr, - std::vector<std::pair<Offset, Offset>>()}); - for (auto edit_it = file_it->second.begin(); - edit_it != file_it->second.end(); ++edit_it) { - auto range_it = edit_it->second.find("range"); - if (range_it != edit_it->second.not_found()) { - auto start_it = range_it->second.find("start"); - auto end_it = range_it->second.find("end"); - if (start_it != range_it->second.not_found() && - end_it != range_it->second.not_found()) - usages.back().offsets.emplace_back( - std::make_pair( - Offset(start_it->second.get<unsigned>( - "line"), - start_it->second.get<unsigned>( - "character")), - Offset(end_it->second.get<unsigned>( - "line"), - end_it->second.get<unsigned>( - "character")))); - } - } - } - } - } - } else { - auto changes_pt = result.get_child("documentChanges", - boost::property_tree::ptree()); - for (auto change_it = changes_pt.begin(); - change_it != changes_pt.end(); ++change_it) { - auto document_it = change_it->second.find("textDocument"); - if (document_it != change_it->second.not_found()) { - auto path = document_it->second.get<std::string>("uri", ""); - if (path.size() >= 7) { - path.erase(0, 7); - if (filesystem::file_in_path(path, project_path)) { - usages.emplace_back( - Usages{path, std::make_unique<std::string>(), - std::vector<std::pair<Offset, Offset>>()}); - auto edits_pt = change_it->second.get_child("edits", - boost::property_tree::ptree()); - for (auto edit_it = edits_pt.begin(); - edit_it != edits_pt.end(); ++edit_it) { - auto new_text_it = edit_it->second.find( - "newText"); - if (new_text_it != edit_it->second.not_found()) { - auto range_it = edit_it->second.find("range"); - if (range_it != edit_it->second.not_found()) { - auto start_it = range_it->second.find( - "start"); - auto end_it = range_it->second.find( - "end"); - if (start_it != - range_it->second.not_found() && - end_it != - range_it->second.not_found()) { - auto end_line = end_it->second.get<size_t>( - "line"); - if (end_line > - std::numeric_limits<int>::max()) - end_line = std::numeric_limits<int>::max(); - *usages.back().new_text = new_text_it->second.get_value<std::string>(); - usages.back().offsets.emplace_back( - std::make_pair( - Offset(start_it->second.get<unsigned>( - "line"), - start_it->second.get<unsigned>( - "character")), - Offset(static_cast<unsigned>(end_line), - end_it->second.get<unsigned>( - "character")))); - } - } - } - } - } - } - } + auto iter = get_buffer()->get_insert()->get_iter(); + std::vector<Usages> usages; + std::promise<void> result_processed; + client->write_request(this, "textDocument/rename", + "\"textDocument\":{\"uri\":\"" + uri + "\"}, \"position\": {\"line\": " + + std::to_string(iter.get_line()) + ", \"character\": " + + std::to_string(iter.get_line_offset()) + "}, \"newName\": \"" + text + "\"", + [this, &usages, &result_processed](const boost::property_tree::ptree &result, + bool error) { + if (!error) { + boost::filesystem::path project_path; + auto build = Project::Build::create(file_path); + if (!build->project_path.empty()) + project_path = build->project_path; + else + project_path = file_path.parent_path(); + try { + auto changes_it = result.find("changes"); + if (changes_it != result.not_found()) { + for (auto file_it = changes_it->second.begin(); + file_it != changes_it->second.end(); ++file_it) { + auto path = file_it->first; + if (path.size() >= 7) { + path.erase(0, 7); + if (filesystem::file_in_path(path, project_path)) { + usages.emplace_back(Usages{path, nullptr, + std::vector<std::pair<Offset, Offset>>()}); + for (auto edit_it = file_it->second.begin(); + edit_it != file_it->second.end(); ++edit_it) { + auto range_it = edit_it->second.find("range"); + if (range_it != edit_it->second.not_found()) { + auto start_it = range_it->second.find("start"); + auto end_it = range_it->second.find("end"); + if (start_it != range_it->second.not_found() && + end_it != range_it->second.not_found()) + usages.back().offsets.emplace_back( + std::make_pair( + Offset(start_it->second.get<unsigned>( + "line"), + start_it->second.get<unsigned>( + "character")), + Offset(end_it->second.get<unsigned>( + "line"), + end_it->second.get<unsigned>( + "character")))); + } + } + } + } + } + } else { + auto changes_pt = result.get_child("documentChanges", + boost::property_tree::ptree()); + for (auto change_it = changes_pt.begin(); + change_it != changes_pt.end(); ++change_it) { + auto document_it = change_it->second.find("textDocument"); + if (document_it != change_it->second.not_found()) { + auto path = document_it->second.get<std::string>("uri", ""); + if (path.size() >= 7) { + path.erase(0, 7); + if (filesystem::file_in_path(path, project_path)) { + usages.emplace_back( + Usages{path, std::make_unique<std::string>(), + std::vector<std::pair<Offset, Offset>>()}); + auto edits_pt = change_it->second.get_child("edits", + boost::property_tree::ptree()); + for (auto edit_it = edits_pt.begin(); + edit_it != edits_pt.end(); ++edit_it) { + auto new_text_it = edit_it->second.find( + "newText"); + if (new_text_it != edit_it->second.not_found()) { + auto range_it = edit_it->second.find("range"); + if (range_it != edit_it->second.not_found()) { + auto start_it = range_it->second.find( + "start"); + auto end_it = range_it->second.find( + "end"); + if (start_it != + range_it->second.not_found() && + end_it != + range_it->second.not_found()) { + auto end_line = end_it->second.get<size_t>( + "line"); + if (end_line > + std::numeric_limits<int>::max()) + end_line = std::numeric_limits<int>::max(); + *usages.back().new_text = new_text_it->second.get_value<std::string>(); + usages.back().offsets.emplace_back( + std::make_pair( + Offset(start_it->second.get<unsigned>( + "line"), + start_it->second.get<unsigned>( + "character")), + Offset(static_cast<unsigned>(end_line), + end_it->second.get<unsigned>( + "character")))); } + } } + } } - catch (...) { - usages.clear(); - } + } } - result_processed.set_value(); - }); - result_processed.get_future().get(); - - std::vector<Usages *> usages_renamed; - for (auto &usage: usages) { - auto view_it = views.end(); - for (auto it = views.begin(); it != views.end(); ++it) { - if ((*it)->file_path == usage.path) { - view_it = it; - break; - } - } - if (view_it != views.end()) { - (*view_it)->get_buffer()->begin_user_action(); - auto iter = get_buffer()->get_insert()->get_iter(); - auto line = iter.get_line(); - auto offset = iter.get_line_offset(); - for (auto offset_it = usage.offsets.rbegin(); offset_it != usage.offsets.rend(); ++offset_it) { - auto start_iter = (*view_it)->get_iter_at_line_pos(offset_it->first.line, - offset_it->first.index); - auto end_iter = (*view_it)->get_iter_at_line_pos(offset_it->second.line, - offset_it->second.index); - (*view_it)->get_buffer()->erase(start_iter, end_iter); - start_iter = (*view_it)->get_iter_at_line_pos(offset_it->first.line, offset_it->first.index); - if (usage.new_text) - (*view_it)->get_buffer()->insert(start_iter, *usage.new_text); - else - (*view_it)->get_buffer()->insert(start_iter, text); - } - if (usage.new_text && get_buffer()->get_insert()->get_iter().is_end()) { - place_cursor_at_line_offset(line, offset); - hide_tooltips(); - scroll_to_cursor_delayed(this, true, false); - } - (*view_it)->get_buffer()->end_user_action(); - (*view_it)->save(); - usages_renamed.emplace_back(&usage); - } else { - Glib::ustring buffer; - { - std::ifstream stream(usage.path.string(), std::ifstream::binary); - if (stream) - buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>()); - } - std::ofstream stream(usage.path.string(), std::ifstream::binary); - if (!buffer.empty() && stream) { - std::vector<size_t> lines_start_pos = {0}; - for (size_t c = 0; c < buffer.size(); ++c) { - if (buffer[c] == '\n') - lines_start_pos.emplace_back(c + 1); - } - for (auto offset_it = usage.offsets.rbegin(); offset_it != usage.offsets.rend(); ++offset_it) { - auto start_line = offset_it->first.line; - auto end_line = offset_it->second.line; - if (start_line < lines_start_pos.size()) { - auto start = lines_start_pos[start_line] + offset_it->first.index; - unsigned end; - if (end_line >= lines_start_pos.size()) - end = buffer.size(); - else - end = lines_start_pos[end_line] + offset_it->second.index; - if (start < buffer.size() && end <= buffer.size()) { - if (usage.new_text) - buffer.replace(start, end - start, *usage.new_text); - else - buffer.replace(start, end - start, text); + } + } } - } - } - stream.write(buffer.data(), buffer.bytes()); - usages_renamed.emplace_back(&usage); - } else - Terminal::get().print("Error: could not write to file " + usage.path.string() + '\n', true); - } + catch (...) { + usages.clear(); + } + } + result_processed.set_value(); + }); + result_processed.get_future().get(); + + std::vector<Usages *> usages_renamed; + for (auto &usage: usages) { + auto view_it = views.end(); + for (auto it = views.begin(); it != views.end(); ++it) { + if ((*it)->file_path == usage.path) { + view_it = it; + break; + } + } + if (view_it != views.end()) { + (*view_it)->get_buffer()->begin_user_action(); + auto iter = get_buffer()->get_insert()->get_iter(); + auto line = iter.get_line(); + auto offset = iter.get_line_offset(); + for (auto offset_it = usage.offsets.rbegin(); offset_it != usage.offsets.rend(); ++offset_it) { + auto start_iter = (*view_it)->get_iter_at_line_pos(offset_it->first.line, + offset_it->first.index); + auto end_iter = (*view_it)->get_iter_at_line_pos(offset_it->second.line, + offset_it->second.index); + (*view_it)->get_buffer()->erase(start_iter, end_iter); + start_iter = (*view_it)->get_iter_at_line_pos(offset_it->first.line, offset_it->first.index); + if (usage.new_text) + (*view_it)->get_buffer()->insert(start_iter, *usage.new_text); + else + (*view_it)->get_buffer()->insert(start_iter, text); + } + if (usage.new_text && get_buffer()->get_insert()->get_iter().is_end()) { + place_cursor_at_line_offset(line, offset); + hide_tooltips(); + scroll_to_cursor_delayed(this, true, false); + } + (*view_it)->get_buffer()->end_user_action(); + (*view_it)->save(); + usages_renamed.emplace_back(&usage); + } else { + Glib::ustring buffer; + { + std::ifstream stream(usage.path.string(), std::ifstream::binary); + if (stream) + buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>()); + } + std::ofstream stream(usage.path.string(), std::ifstream::binary); + if (!buffer.empty() && stream) { + std::vector<size_t> lines_start_pos = {0}; + for (size_t c = 0; c < buffer.size(); ++c) { + if (buffer[c] == '\n') + lines_start_pos.emplace_back(c + 1); } - - if (!usages_renamed.empty()) { - Terminal::get().print("Renamed "); - Terminal::get().print(previous_text, true); - Terminal::get().print(" to "); - Terminal::get().print(text, true); - Terminal::get().print("\n"); + for (auto offset_it = usage.offsets.rbegin(); offset_it != usage.offsets.rend(); ++offset_it) { + auto start_line = offset_it->first.line; + auto end_line = offset_it->second.line; + if (start_line < lines_start_pos.size()) { + auto start = lines_start_pos[start_line] + offset_it->first.index; + unsigned end; + if (end_line >= lines_start_pos.size()) + end = buffer.size(); + else + end = lines_start_pos[end_line] + offset_it->second.index; + if (start < buffer.size() && end <= buffer.size()) { + if (usage.new_text) + buffer.replace(start, end - start, *usage.new_text); + else + buffer.replace(start, end - start, text); + } + } } - }; - } - - goto_next_diagnostic = [this]() { - place_cursor_at_next_diagnostic(); + stream.write(buffer.data(), buffer.bytes()); + usages_renamed.emplace_back(&usage); + } else + Terminal::get().print("Error: could not write to file " + usage.path.string() + '\n', true); + } + } + + if (!usages_renamed.empty()) { + Terminal::get().print("Renamed "); + Terminal::get().print(previous_text, true); + Terminal::get().print(" to "); + Terminal::get().print(text, true); + Terminal::get().print("\n"); + } }; + } + + goto_next_diagnostic = [this]() { + place_cursor_at_next_diagnostic(); + }; } void Source::LanguageProtocolView::escape_text(std::string &text) { - for (size_t c = 0; c < text.size(); ++c) { - if (text[c] == '\n') { - text.replace(c, 1, "\\n"); - ++c; - } else if (text[c] == '\r') { - text.replace(c, 1, "\\r"); - ++c; - } else if (text[c] == '\"') { - text.replace(c, 1, "\\\""); - ++c; - } + for (size_t c = 0; c < text.size(); ++c) { + if (text[c] == '\n') { + text.replace(c, 1, "\\n"); + ++c; + } else if (text[c] == '\r') { + text.replace(c, 1, "\\r"); + ++c; + } else if (text[c] == '\"') { + text.replace(c, 1, "\\\""); + ++c; } + } } void Source::LanguageProtocolView::unescape_text(std::string &text) { - for (size_t c = 0; c < text.size(); ++c) { - if (text[c] == '\\' && c + 1 < text.size()) { - if (text[c + 1] == 'n') - text.replace(c, 2, "\n"); - else if (text[c + 1] == 'r') - text.replace(c, 2, "\r"); - else - text.erase(c, 1); - } + for (size_t c = 0; c < text.size(); ++c) { + if (text[c] == '\\' && c + 1 < text.size()) { + if (text[c + 1] == 'n') + text.replace(c, 2, "\n"); + else if (text[c + 1] == 'r') + text.replace(c, 2, "\r"); + else + text.erase(c, 1); } + } } void Source::LanguageProtocolView::update_diagnostics(std::vector<LanguageProtocol::Diagnostic> &&diagnostics) { - dispatcher.post([this, diagnostics = std::move(diagnostics)] { - clear_diagnostic_tooltips(); - num_warnings = 0; - num_errors = 0; - num_fix_its = 0; - for (auto &diagnostic: diagnostics) { - if (diagnostic.uri == uri) { - auto start = get_iter_at_line_pos(diagnostic.offsets.first.line, diagnostic.offsets.first.index); - auto end = get_iter_at_line_pos(diagnostic.offsets.second.line, diagnostic.offsets.second.index); - - if (start == end) { - if (!end.is_end()) - end.forward_char(); - else - start.forward_char(); - } + dispatcher.post([this, diagnostics = std::move(diagnostics)] { + clear_diagnostic_tooltips(); + num_warnings = 0; + num_errors = 0; + num_fix_its = 0; + for (auto &diagnostic: diagnostics) { + if (diagnostic.uri == uri) { + auto start = get_iter_at_line_pos(diagnostic.offsets.first.line, diagnostic.offsets.first.index); + auto end = get_iter_at_line_pos(diagnostic.offsets.second.line, diagnostic.offsets.second.index); - bool error = false; - std::string severity_tag_name; - if (diagnostic.severity >= 2) { - severity_tag_name = "def:warning"; - num_warnings++; - } else { - severity_tag_name = "def:error"; - num_errors++; - error = true; - } + if (start == end) { + if (!end.is_end()) + end.forward_char(); + else + start.forward_char(); + } - add_diagnostic_tooltip(start, end, diagnostic.spelling, error); - } + bool error = false; + std::string severity_tag_name; + if (diagnostic.severity >= 2) { + severity_tag_name = "def:warning"; + num_warnings++; + } else { + severity_tag_name = "def:error"; + num_errors++; + error = true; } - for (auto &mark: flow_coverage_marks) - add_diagnostic_tooltip(mark.first->get_iter(), mark.second->get_iter(), flow_coverage_message, false); + add_diagnostic_tooltip(start, end, diagnostic.spelling, error); + } + } - status_diagnostics = std::make_tuple(num_warnings + num_flow_coverage_warnings, num_errors, num_fix_its); - if (update_status_diagnostics) - update_status_diagnostics(this); - }); + for (auto &mark: flow_coverage_marks) + add_diagnostic_tooltip(mark.first->get_iter(), mark.second->get_iter(), flow_coverage_message, false); + + status_diagnostics = std::make_tuple(num_warnings + num_flow_coverage_warnings, num_errors, num_fix_its); + if (update_status_diagnostics) + update_status_diagnostics(this); + }); } Gtk::TextIter Source::LanguageProtocolView::get_iter_at_line_pos(int line, int pos) { - return get_iter_at_line_offset(line, pos); + return get_iter_at_line_offset(line, pos); } void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rectangle) { - if (!capabilities.hover) - return; - - Gtk::TextIter iter; - int location_x, location_y; - window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, rectangle.get_x(), rectangle.get_y(), location_x, - location_y); - location_x += (rectangle.get_width() - 1) / 2; - get_iter_at_location(iter, location_x, location_y); - Gdk::Rectangle iter_rectangle; - get_iter_location(iter, iter_rectangle); - if (iter.ends_line() && location_x > iter_rectangle.get_x()) - return; - - auto offset = iter.get_offset(); + if (!capabilities.hover) + return; + + Gtk::TextIter iter; + int location_x, location_y; + window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, rectangle.get_x(), rectangle.get_y(), location_x, + location_y); + location_x += (rectangle.get_width() - 1) / 2; + get_iter_at_location(iter, location_x, location_y); + Gdk::Rectangle iter_rectangle; + get_iter_location(iter, iter_rectangle); + if (iter.ends_line() && location_x > iter_rectangle.get_x()) + return; + + auto offset = iter.get_offset(); + + static int request_count = 0; + request_count++; + auto current_request = request_count; + client->write_request(this, "textDocument/hover", "\"textDocument\": {\"uri\":\"file://" + file_path.string() + + "\"}, \"position\": {\"line\": " + + std::to_string(iter.get_line()) + ", \"character\": " + + std::to_string(iter.get_line_offset()) + "}", + [this, offset, current_request](const boost::property_tree::ptree &result, bool error) { + if (!error) { + // hover result structure vary significantly from the different language servers + std::string content; + auto contents_pt = result.get_child("contents", boost::property_tree::ptree()); + for (auto it = contents_pt.begin(); it != contents_pt.end(); ++it) { + auto value = it->second.get<std::string>("value", ""); + if (!value.empty()) + content.insert(0, value + (content.empty() ? "" : "\n\n")); + else { + value = it->second.get_value<std::string>(""); + if (!value.empty()) + content += (content.empty() ? "" : "\n\n") + value; + } + } + if (content.empty()) { + auto contents_it = result.find("contents"); + if (contents_it != result.not_found()) { + content = contents_it->second.get<std::string>("value", ""); + if (content.empty()) + content = contents_it->second.get_value<std::string>(""); + } + } + if (!content.empty()) { + dispatcher.post([this, offset, content = std::move(content), current_request] { + if (current_request != request_count) + return; + if (offset >= get_buffer()->get_char_count()) + return; + type_tooltips.clear(); + auto create_tooltip_buffer = [this, offset, content = std::move(content)]() { + auto tooltip_buffer = Gtk::TextBuffer::create( + get_buffer()->get_tag_table()); + tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), content); - static int request_count = 0; - request_count++; - auto current_request = request_count; - client->write_request(this, "textDocument/hover", "\"textDocument\": {\"uri\":\"file://" + file_path.string() + - "\"}, \"position\": {\"line\": " + - std::to_string(iter.get_line()) + ", \"character\": " + - std::to_string(iter.get_line_offset()) + "}", - [this, offset, current_request](const boost::property_tree::ptree &result, bool error) { - if (!error) { - // hover result structure vary significantly from the different language servers - std::string content; - auto contents_pt = result.get_child("contents", boost::property_tree::ptree()); - for (auto it = contents_pt.begin(); it != contents_pt.end(); ++it) { - auto value = it->second.get<std::string>("value", ""); - if (!value.empty()) - content.insert(0, value + (content.empty() ? "" : "\n\n")); - else { - value = it->second.get_value<std::string>(""); - if (!value.empty()) - content += (content.empty() ? "" : "\n\n") + value; +#ifdef JUCI_ENABLE_DEBUG + if(language_id=="rust" && capabilities.definition) { + if(Debug::LLDB::get().is_stopped()) { + Glib::ustring value_type="Value"; + + auto start=get_buffer()->get_iter_at_offset(offset); + auto end=start; + auto previous=start; + while(previous.backward_char() && ((*previous>='A' && *previous<='Z') || (*previous>='a' && *previous<='z') || (*previous>='0' && *previous<='9') || *previous=='_') && start.backward_char()) {} + while(((*end>='A' && *end<='Z') || (*end>='a' && *end<='z') || (*end>='0' && *end<='9') || *end=='_') && end.forward_char()) {} + + auto offset=get_declaration(start); + Glib::ustring debug_value=Debug::LLDB::get().get_value(get_buffer()->get_text(start, end), offset.file_path, offset.line+1, offset.index+1); + if(debug_value.empty()) { + value_type="Return value"; + debug_value=Debug::LLDB::get().get_return_value(file_path, start.get_line()+1, start.get_line_index()+1); } - } - if (content.empty()) { - auto contents_it = result.find("contents"); - if (contents_it != result.not_found()) { - content = contents_it->second.get<std::string>("value", ""); - if (content.empty()) - content = contents_it->second.get_value<std::string>(""); + if(!debug_value.empty()) { + size_t pos=debug_value.find(" = "); + if(pos!=Glib::ustring::npos) { + Glib::ustring::iterator iter; + while(!debug_value.validate(iter)) { + auto next_char_iter=iter; + next_char_iter++; + debug_value.replace(iter, next_char_iter, "?"); + } + tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), "\n\n"+value_type+": "+debug_value.substr(pos+3, debug_value.size()-(pos+3)-1)); + } } + } } - if (!content.empty()) { - dispatcher.post([this, offset, content = std::move(content), current_request] { - if (current_request != request_count) - return; - if (offset >= get_buffer()->get_char_count()) - return; - type_tooltips.clear(); - auto create_tooltip_buffer = [this, offset, content = std::move(content)]() { - auto tooltip_buffer = Gtk::TextBuffer::create( - get_buffer()->get_tag_table()); - tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), content); - -#ifdef JUCI_ENABLE_DEBUG - if(language_id=="rust" && capabilities.definition) { - if(Debug::LLDB::get().is_stopped()) { - Glib::ustring value_type="Value"; - - auto start=get_buffer()->get_iter_at_offset(offset); - auto end=start; - auto previous=start; - while(previous.backward_char() && ((*previous>='A' && *previous<='Z') || (*previous>='a' && *previous<='z') || (*previous>='0' && *previous<='9') || *previous=='_') && start.backward_char()) {} - while(((*end>='A' && *end<='Z') || (*end>='a' && *end<='z') || (*end>='0' && *end<='9') || *end=='_') && end.forward_char()) {} - - auto offset=get_declaration(start); - Glib::ustring debug_value=Debug::LLDB::get().get_value(get_buffer()->get_text(start, end), offset.file_path, offset.line+1, offset.index+1); - if(debug_value.empty()) { - value_type="Return value"; - debug_value=Debug::LLDB::get().get_return_value(file_path, start.get_line()+1, start.get_line_index()+1); - } - if(!debug_value.empty()) { - size_t pos=debug_value.find(" = "); - if(pos!=Glib::ustring::npos) { - Glib::ustring::iterator iter; - while(!debug_value.validate(iter)) { - auto next_char_iter=iter; - next_char_iter++; - debug_value.replace(iter, next_char_iter, "?"); - } - tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), "\n\n"+value_type+": "+debug_value.substr(pos+3, debug_value.size()-(pos+3)-1)); - } - } - } - } #endif - return tooltip_buffer; - }; - - auto start = get_buffer()->get_iter_at_offset(offset); - auto end = start; - auto previous = start; - while (previous.backward_char() && ((*previous >= 'A' && *previous <= 'Z') || - (*previous >= 'a' && *previous <= 'z') || - (*previous >= '0' && *previous <= '9') || - *previous == '_') && - start.backward_char()) {} - while (((*end >= 'A' && *end <= 'Z') || (*end >= 'a' && *end <= 'z') || - (*end >= '0' && *end <= '9') || *end == '_') && end.forward_char()) {} - type_tooltips.emplace_back(create_tooltip_buffer, this, - get_buffer()->create_mark(start), - get_buffer()->create_mark(end)); - type_tooltips.show(); - }); - } - } - }); + return tooltip_buffer; + }; + + auto start = get_buffer()->get_iter_at_offset(offset); + auto end = start; + auto previous = start; + while (previous.backward_char() && ((*previous >= 'A' && *previous <= 'Z') || + (*previous >= 'a' && *previous <= 'z') || + (*previous >= '0' && *previous <= '9') || + *previous == '_') && + start.backward_char()) {} + while (((*end >= 'A' && *end <= 'Z') || (*end >= 'a' && *end <= 'z') || + (*end >= '0' && *end <= '9') || *end == '_') && end.forward_char()) {} + type_tooltips.emplace_back(create_tooltip_buffer, this, + get_buffer()->create_mark(start), + get_buffer()->create_mark(end)); + type_tooltips.show(); + }); + } + } + }); } void Source::LanguageProtocolView::tag_similar_symbols() { - if (!capabilities.document_highlight && !capabilities.references) - return; - - auto iter = get_buffer()->get_insert()->get_iter(); - std::string method; - if (capabilities.document_highlight) - method = "textDocument/documentHighlight"; - else - method = "textDocument/references"; - - static int request_count = 0; - request_count++; - auto current_request = request_count; - client->write_request(this, method, "\"textDocument\":{\"uri\":\"" + uri + "\"}, \"position\": {\"line\": " + - std::to_string(iter.get_line()) + ", \"character\": " + - std::to_string(iter.get_line_offset()) + - "}, \"context\": {\"includeDeclaration\": true}", - [this, current_request](const boost::property_tree::ptree &result, bool error) { - if (!error) { - std::vector<std::pair<Offset, Offset>> offsets; - for (auto it = result.begin(); it != result.end(); ++it) { - if (capabilities.document_highlight || - it->second.get<std::string>("uri", "") == uri) { - auto range_it = it->second.find("range"); - if (range_it != it->second.not_found()) { - auto start_it = range_it->second.find("start"); - auto end_it = range_it->second.find("end"); - if (start_it != range_it->second.not_found() && - end_it != range_it->second.not_found()) { - try { - offsets.emplace_back(std::make_pair( - Offset(start_it->second.get<unsigned>("line"), - start_it->second.get<unsigned>("character")), - Offset(end_it->second.get<unsigned>("line"), - end_it->second.get<unsigned>("character")))); - } - catch (...) {} - } - } - } + if (!capabilities.document_highlight && !capabilities.references) + return; + + auto iter = get_buffer()->get_insert()->get_iter(); + std::string method; + if (capabilities.document_highlight) + method = "textDocument/documentHighlight"; + else + method = "textDocument/references"; + + static int request_count = 0; + request_count++; + auto current_request = request_count; + client->write_request(this, method, "\"textDocument\":{\"uri\":\"" + uri + "\"}, \"position\": {\"line\": " + + std::to_string(iter.get_line()) + ", \"character\": " + + std::to_string(iter.get_line_offset()) + + "}, \"context\": {\"includeDeclaration\": true}", + [this, current_request](const boost::property_tree::ptree &result, bool error) { + if (!error) { + std::vector<std::pair<Offset, Offset>> offsets; + for (auto it = result.begin(); it != result.end(); ++it) { + if (capabilities.document_highlight || + it->second.get<std::string>("uri", "") == uri) { + auto range_it = it->second.find("range"); + if (range_it != it->second.not_found()) { + auto start_it = range_it->second.find("start"); + auto end_it = range_it->second.find("end"); + if (start_it != range_it->second.not_found() && + end_it != range_it->second.not_found()) { + try { + offsets.emplace_back(std::make_pair( + Offset(start_it->second.get<unsigned>("line"), + start_it->second.get<unsigned>("character")), + Offset(end_it->second.get<unsigned>("line"), + end_it->second.get<unsigned>("character")))); + } + catch (...) {} } - dispatcher.post([this, offsets = std::move(offsets), current_request] { - if (current_request != request_count) - return; - get_buffer()->remove_tag(similar_symbol_tag, get_buffer()->begin(), - get_buffer()->end()); - for (auto &pair: offsets) { - auto start = get_iter_at_line_pos(pair.first.line, pair.first.index); - auto end = get_iter_at_line_pos(pair.second.line, pair.second.index); - get_buffer()->apply_tag(similar_symbol_tag, start, end); - } - }); + } } - }); + } + dispatcher.post([this, offsets = std::move(offsets), current_request] { + if (current_request != request_count) + return; + get_buffer()->remove_tag(similar_symbol_tag, get_buffer()->begin(), + get_buffer()->end()); + for (auto &pair: offsets) { + auto start = get_iter_at_line_pos(pair.first.line, pair.first.index); + auto end = get_iter_at_line_pos(pair.second.line, pair.second.index); + get_buffer()->apply_tag(similar_symbol_tag, start, end); + } + }); + } + }); } Source::Offset Source::LanguageProtocolView::get_declaration(const Gtk::TextIter &iter) { - auto offset = std::make_shared<Offset>(); - std::promise<void> result_processed; - client->write_request(this, "textDocument/definition", - "\"textDocument\":{\"uri\":\"" + uri + "\"}, \"position\": {\"line\": " + - std::to_string(iter.get_line()) + ", \"character\": " + - std::to_string(iter.get_line_offset()) + "}", - [offset, &result_processed](const boost::property_tree::ptree &result, bool error) { - if (!error) { - for (auto it = result.begin(); it != result.end(); ++it) { - auto uri = it->second.get<std::string>("uri", ""); - if (uri.compare(0, 7, "file://") == 0) - uri.erase(0, 7); - auto range = it->second.find("range"); - if (range != it->second.not_found()) { - auto start = range->second.find("start"); - if (start != range->second.not_found()) - *offset = Offset(start->second.get<unsigned>("line", 0), - start->second.get<unsigned>("character", 0), uri); - } - break; // TODO: can a language server return several definitions? - } + auto offset = std::make_shared<Offset>(); + std::promise<void> result_processed; + client->write_request(this, "textDocument/definition", + "\"textDocument\":{\"uri\":\"" + uri + "\"}, \"position\": {\"line\": " + + std::to_string(iter.get_line()) + ", \"character\": " + + std::to_string(iter.get_line_offset()) + "}", + [offset, &result_processed](const boost::property_tree::ptree &result, bool error) { + if (!error) { + for (auto it = result.begin(); it != result.end(); ++it) { + auto uri = it->second.get<std::string>("uri", ""); + if (uri.compare(0, 7, "file://") == 0) + uri.erase(0, 7); + auto range = it->second.find("range"); + if (range != it->second.not_found()) { + auto start = range->second.find("start"); + if (start != range->second.not_found()) + *offset = Offset(start->second.get<unsigned>("line", 0), + start->second.get<unsigned>("character", 0), uri); } - result_processed.set_value(); - }); - result_processed.get_future().get(); - return *offset; + break; // TODO: can a language server return several definitions? + } + } + result_processed.set_value(); + }); + result_processed.get_future().get(); + return *offset; } void Source::LanguageProtocolView::setup_autocomplete() { - if (!capabilities.completion) - return; + if (!capabilities.completion) + return; - non_interactive_completion = [this] { - if (CompletionDialog::get() && CompletionDialog::get()->is_visible()) - return; - autocomplete.run(); - }; + non_interactive_completion = [this] { + if (CompletionDialog::get() && CompletionDialog::get()->is_visible()) + return; + autocomplete.run(); + }; - autocomplete.reparse = [this] { - autocomplete_comment.clear(); - autocomplete_insert.clear(); - }; + autocomplete.reparse = [this] { + autocomplete_comment.clear(); + autocomplete_insert.clear(); + }; - autocomplete.is_continue_key = [](guint keyval) { - if ((keyval >= '0' && keyval <= '9') || (keyval >= 'a' && keyval <= 'z') || (keyval >= 'A' && keyval <= 'Z') || - keyval == '_') - return true; + autocomplete.is_continue_key = [](guint keyval) { + if ((keyval >= '0' && keyval <= '9') || (keyval >= 'a' && keyval <= 'z') || (keyval >= 'A' && keyval <= 'Z') || + keyval == '_') + return true; - return false; - }; - - autocomplete.is_restart_key = [this](guint keyval) { - auto iter = get_buffer()->get_insert()->get_iter(); - iter.backward_chars(2); - if (keyval == '.' || (keyval == ':' && *iter == ':')) - return true; - return false; - }; - - autocomplete.run_check = [this]() { - auto iter = get_buffer()->get_insert()->get_iter(); - iter.backward_char(); - if (!is_code_iter(iter)) - return false; - - std::string line = " " + get_line_before(); - const static std::regex dot_or_arrow("^.*[a-zA-Z0-9_\\)\\]\\>\"'](\\.)([a-zA-Z0-9_]*)$"); - const static std::regex colon_colon("^.*::([a-zA-Z0-9_]*)$"); - const static std::regex part_of_symbol("^.*[^a-zA-Z0-9_]+([a-zA-Z0-9_]{3,})$"); - std::smatch sm; - if (std::regex_match(line, sm, dot_or_arrow)) { - { - std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); - autocomplete.prefix = sm[2].str(); - } - if (autocomplete.prefix.size() == 0 || autocomplete.prefix[0] < '0' || autocomplete.prefix[0] > '9') - return true; - } else if (std::regex_match(line, sm, colon_colon)) { - { - std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); - autocomplete.prefix = sm[1].str(); - } - if (autocomplete.prefix.size() == 0 || autocomplete.prefix[0] < '0' || autocomplete.prefix[0] > '9') - return true; - } else if (std::regex_match(line, sm, part_of_symbol)) { - { - std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); - autocomplete.prefix = sm[1].str(); - } - if (autocomplete.prefix.size() == 0 || autocomplete.prefix[0] < '0' || autocomplete.prefix[0] > '9') - return true; - } else if (!interactive_completion) { - auto end_iter = get_buffer()->get_insert()->get_iter(); - auto iter = end_iter; - while (iter.backward_char() && autocomplete.is_continue_key(*iter)) {} - if (iter != end_iter) - iter.forward_char(); - std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); - autocomplete.prefix = get_buffer()->get_text(iter, end_iter); - return true; - } + return false; + }; - return false; - }; + autocomplete.is_restart_key = [this](guint keyval) { + auto iter = get_buffer()->get_insert()->get_iter(); + iter.backward_chars(2); + if (keyval == '.' || (keyval == ':' && *iter == ':')) + return true; + return false; + }; - autocomplete.before_add_rows = [this] { - status_state = "autocomplete..."; - if (update_status_state) - update_status_state(this); - }; + autocomplete.run_check = [this]() { + auto iter = get_buffer()->get_insert()->get_iter(); + iter.backward_char(); + if (!is_code_iter(iter)) + return false; + + std::string line = " " + get_line_before(); + const static std::regex dot_or_arrow("^.*[a-zA-Z0-9_\\)\\]\\>\"'](\\.)([a-zA-Z0-9_]*)$"); + const static std::regex colon_colon("^.*::([a-zA-Z0-9_]*)$"); + const static std::regex part_of_symbol("^.*[^a-zA-Z0-9_]+([a-zA-Z0-9_]{3,})$"); + std::smatch sm; + if (std::regex_match(line, sm, dot_or_arrow)) { + { + std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); + autocomplete.prefix = sm[2].str(); + } + if (autocomplete.prefix.size() == 0 || autocomplete.prefix[0] < '0' || autocomplete.prefix[0] > '9') + return true; + } else if (std::regex_match(line, sm, colon_colon)) { + { + std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); + autocomplete.prefix = sm[1].str(); + } + if (autocomplete.prefix.size() == 0 || autocomplete.prefix[0] < '0' || autocomplete.prefix[0] > '9') + return true; + } else if (std::regex_match(line, sm, part_of_symbol)) { + { + std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); + autocomplete.prefix = sm[1].str(); + } + if (autocomplete.prefix.size() == 0 || autocomplete.prefix[0] < '0' || autocomplete.prefix[0] > '9') + return true; + } else if (!interactive_completion) { + auto end_iter = get_buffer()->get_insert()->get_iter(); + auto iter = end_iter; + while (iter.backward_char() && autocomplete.is_continue_key(*iter)) {} + if (iter != end_iter) + iter.forward_char(); + std::unique_lock<std::mutex> lock(autocomplete.prefix_mutex); + autocomplete.prefix = get_buffer()->get_text(iter, end_iter); + return true; + } - autocomplete.after_add_rows = [this] { - status_state = ""; - if (update_status_state) - update_status_state(this); - }; + return false; + }; - autocomplete.on_add_rows_error = [this] { - autocomplete_comment.clear(); - autocomplete_insert.clear(); - }; + autocomplete.before_add_rows = [this] { + status_state = "autocomplete..."; + if (update_status_state) + update_status_state(this); + }; - autocomplete.add_rows = [this](std::string &buffer, int line_number, int column) { - if (autocomplete.state == Autocomplete::State::STARTING) { - autocomplete_comment.clear(); - autocomplete_insert.clear(); - std::promise<void> result_processed; - client->write_request(this, "textDocument/completion", - "\"textDocument\":{\"uri\":\"" + uri + "\"}, \"position\": {\"line\": " + - std::to_string(line_number - 1) + ", \"character\": " + std::to_string(column - 1) + - "}", - [this, &result_processed](const boost::property_tree::ptree &result, bool error) { - if (!error) { - auto begin = result.begin(); // rust language server is bugged - auto end = result.end(); - auto items_it = result.find("items"); // correct - if (items_it != result.not_found()) { - begin = items_it->second.begin(); - end = items_it->second.end(); - } - for (auto it = begin; it != end; ++it) { - auto label = it->second.get<std::string>("label", ""); - auto detail = it->second.get<std::string>("detail", ""); - auto documentation = it->second.get<std::string>("documentation", ""); - auto insert = it->second.get<std::string>("insertText", ""); - if (insert.empty()) { - insert = label; - auto kind = it->second.get<unsigned>("kind", 0); - if (kind >= 2 && kind <= 3) { - bool found_bracket = false; - for (auto &chr: insert) { - if (chr == '(' || chr == '{') { - found_bracket = true; - break; - } - } - if (!found_bracket) - insert += "(${1:})"; - } - } - if (!label.empty()) { - autocomplete.rows.emplace_back(std::move(label)); - autocomplete_comment.emplace_back(std::move(detail)); - if (!documentation.empty()) { - if (!autocomplete_comment.back().empty()) - autocomplete_comment.back() += "\n\n"; - autocomplete_comment.back() += documentation; - } - autocomplete_insert.emplace_back(std::move(insert)); - } - } + autocomplete.after_add_rows = [this] { + status_state = ""; + if (update_status_state) + update_status_state(this); + }; + + autocomplete.on_add_rows_error = [this] { + autocomplete_comment.clear(); + autocomplete_insert.clear(); + }; + + autocomplete.add_rows = [this](std::string &buffer, int line_number, int column) { + if (autocomplete.state == Autocomplete::State::STARTING) { + autocomplete_comment.clear(); + autocomplete_insert.clear(); + std::promise<void> result_processed; + client->write_request(this, "textDocument/completion", + "\"textDocument\":{\"uri\":\"" + uri + "\"}, \"position\": {\"line\": " + + std::to_string(line_number - 1) + ", \"character\": " + std::to_string(column - 1) + + "}", + [this, &result_processed](const boost::property_tree::ptree &result, bool error) { + if (!error) { + auto begin = result.begin(); // rust language server is bugged + auto end = result.end(); + auto items_it = result.find("items"); // correct + if (items_it != result.not_found()) { + begin = items_it->second.begin(); + end = items_it->second.end(); + } + for (auto it = begin; it != end; ++it) { + auto label = it->second.get<std::string>("label", ""); + auto detail = it->second.get<std::string>("detail", ""); + auto documentation = it->second.get<std::string>("documentation", ""); + auto insert = it->second.get<std::string>("insertText", ""); + if (insert.empty()) { + insert = label; + auto kind = it->second.get<unsigned>("kind", 0); + if (kind >= 2 && kind <= 3) { + bool found_bracket = false; + for (auto &chr: insert) { + if (chr == '(' || chr == '{') { + found_bracket = true; + break; + } } - result_processed.set_value(); - }); - result_processed.get_future().get(); - } - }; - - signal_key_press_event().connect([this](GdkEventKey *event) { - if ((event->keyval == GDK_KEY_Tab || event->keyval == GDK_KEY_ISO_Left_Tab) && - (event->state & GDK_SHIFT_MASK) == 0) { - if (!autocomplete_marks.empty()) { - auto it = autocomplete_marks.begin(); - auto start = it->first->get_iter(); - auto end = it->second->get_iter(); - if (start == end) - return false; - autocomplete_keep_marks = true; - get_buffer()->select_range(it->first->get_iter(), it->second->get_iter()); - autocomplete_keep_marks = false; - get_buffer()->delete_mark(it->first); - get_buffer()->delete_mark(it->second); - autocomplete_marks.erase(it); - return true; + if (!found_bracket) + insert += "(${1:})"; + } + } + if (!label.empty()) { + autocomplete.rows.emplace_back(std::move(label)); + autocomplete_comment.emplace_back(std::move(detail)); + if (!documentation.empty()) { + if (!autocomplete_comment.back().empty()) + autocomplete_comment.back() += "\n\n"; + autocomplete_comment.back() += documentation; + } + autocomplete_insert.emplace_back(std::move(insert)); + } + } + } + result_processed.set_value(); + }); + result_processed.get_future().get(); + } + }; + + signal_key_press_event().connect([this](GdkEventKey *event) { + if ((event->keyval == GDK_KEY_Tab || event->keyval == GDK_KEY_ISO_Left_Tab) && + (event->state & GDK_SHIFT_MASK) == 0) { + if (!autocomplete_marks.empty()) { + auto it = autocomplete_marks.begin(); + auto start = it->first->get_iter(); + auto end = it->second->get_iter(); + if (start == end) + return false; + autocomplete_keep_marks = true; + get_buffer()->select_range(it->first->get_iter(), it->second->get_iter()); + autocomplete_keep_marks = false; + get_buffer()->delete_mark(it->first); + get_buffer()->delete_mark(it->second); + autocomplete_marks.erase(it); + return true; + } + } + return false; + }, false); + + get_buffer()->signal_mark_set().connect( + [this](const Gtk::TextBuffer::iterator &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { + if (mark->get_name() == "insert") { + if (!autocomplete_keep_marks) { + for (auto &pair: autocomplete_marks) { + get_buffer()->delete_mark(pair.first); + get_buffer()->delete_mark(pair.second); } + autocomplete_marks.clear(); + } } - return false; - }, false); - - get_buffer()->signal_mark_set().connect( - [this](const Gtk::TextBuffer::iterator &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { - if (mark->get_name() == "insert") { - if (!autocomplete_keep_marks) { - for (auto &pair: autocomplete_marks) { - get_buffer()->delete_mark(pair.first); - get_buffer()->delete_mark(pair.second); - } - autocomplete_marks.clear(); - } - } - }); + }); - autocomplete.on_show = [this] { - for (auto &pair: autocomplete_marks) { - get_buffer()->delete_mark(pair.first); - get_buffer()->delete_mark(pair.second); + autocomplete.on_show = [this] { + for (auto &pair: autocomplete_marks) { + get_buffer()->delete_mark(pair.first); + get_buffer()->delete_mark(pair.second); + } + autocomplete_marks.clear(); + hide_tooltips(); + }; + + autocomplete.on_hide = [this] { + autocomplete_comment.clear(); + autocomplete_insert.clear(); + }; + + autocomplete.on_select = [this](unsigned int index, const std::string &text, bool hide_window) { + get_buffer()->erase(CompletionDialog::get()->start_mark->get_iter(), get_buffer()->get_insert()->get_iter()); + if (hide_window) { + Glib::ustring insert = autocomplete_insert[index]; + + // Do not insert function/template parameters if they already exist + auto iter = get_buffer()->get_insert()->get_iter(); + if (*iter == '(' || *iter == '<') { + auto bracket_pos = insert.find(*iter); + if (bracket_pos != std::string::npos) { + insert = insert.substr(0, bracket_pos); } - autocomplete_marks.clear(); - hide_tooltips(); - }; - - autocomplete.on_hide = [this] { - autocomplete_comment.clear(); - autocomplete_insert.clear(); - }; - - autocomplete.on_select = [this](unsigned int index, const std::string &text, bool hide_window) { - get_buffer()->erase(CompletionDialog::get()->start_mark->get_iter(), get_buffer()->get_insert()->get_iter()); - if (hide_window) { - Glib::ustring insert = autocomplete_insert[index]; - - // Do not insert function/template parameters if they already exist - auto iter = get_buffer()->get_insert()->get_iter(); - if (*iter == '(' || *iter == '<') { - auto bracket_pos = insert.find(*iter); - if (bracket_pos != std::string::npos) { - insert = insert.substr(0, bracket_pos); - } - } - - // Find and add position marks that one can move to using tab-key - size_t pos1 = 0; - std::vector<std::pair<size_t, size_t>> mark_offsets; - while ((pos1 = insert.find("${"), pos1) != Glib::ustring::npos) { - size_t pos2 = insert.find(":", pos1 + 2); - if (pos2 != Glib::ustring::npos) { - size_t pos3 = insert.find("}", pos2 + 1); - if (pos3 != Glib::ustring::npos) { - size_t length = pos3 - pos2 - 1; - insert.erase(pos3, 1); - insert.erase(pos1, pos2 - pos1 + 1); - mark_offsets.emplace_back(pos1, pos1 + length); - pos1 += length; - } else - break; - } else - break; - } - get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), insert); - for (auto &offset: mark_offsets) { - auto start = CompletionDialog::get()->start_mark->get_iter(); - auto end = start; - start.forward_chars(offset.first); - end.forward_chars(offset.second); - autocomplete_marks.emplace_back(get_buffer()->create_mark(start), get_buffer()->create_mark(end)); - } - if (!autocomplete_marks.empty()) { - auto it = autocomplete_marks.begin(); - autocomplete_keep_marks = true; - get_buffer()->select_range(it->first->get_iter(), it->second->get_iter()); - autocomplete_keep_marks = false; - get_buffer()->delete_mark(it->first); - get_buffer()->delete_mark(it->second); - autocomplete_marks.erase(it); - } + } + + // Find and add position marks that one can move to using tab-key + size_t pos1 = 0; + std::vector<std::pair<size_t, size_t>> mark_offsets; + while ((pos1 = insert.find("${"), pos1) != Glib::ustring::npos) { + size_t pos2 = insert.find(":", pos1 + 2); + if (pos2 != Glib::ustring::npos) { + size_t pos3 = insert.find("}", pos2 + 1); + if (pos3 != Glib::ustring::npos) { + size_t length = pos3 - pos2 - 1; + insert.erase(pos3, 1); + insert.erase(pos1, pos2 - pos1 + 1); + mark_offsets.emplace_back(pos1, pos1 + length); + pos1 += length; + } else + break; } else - get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), text); - }; - - autocomplete.get_tooltip = [this](unsigned int index) { - return autocomplete_comment[index]; - }; + break; + } + get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), insert); + for (auto &offset: mark_offsets) { + auto start = CompletionDialog::get()->start_mark->get_iter(); + auto end = start; + start.forward_chars(offset.first); + end.forward_chars(offset.second); + autocomplete_marks.emplace_back(get_buffer()->create_mark(start), get_buffer()->create_mark(end)); + } + if (!autocomplete_marks.empty()) { + auto it = autocomplete_marks.begin(); + autocomplete_keep_marks = true; + get_buffer()->select_range(it->first->get_iter(), it->second->get_iter()); + autocomplete_keep_marks = false; + get_buffer()->delete_mark(it->first); + get_buffer()->delete_mark(it->second); + autocomplete_marks.erase(it); + } + } else + get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), text); + }; + + autocomplete.get_tooltip = [this](unsigned int index) { + return autocomplete_comment[index]; + }; } void Source::LanguageProtocolView::add_flow_coverage_tooltips(bool called_in_thread) { - std::stringstream stdin_stream, stderr_stream; - auto stdout_stream = std::make_shared<std::stringstream>(); - auto exit_status = Terminal::get().process(stdin_stream, *stdout_stream, - flow_coverage_executable.string() + " coverage --json " + - file_path.string(), "", &stderr_stream); - auto f = [this, exit_status, stdout_stream] { - clear_diagnostic_tooltips(); - num_flow_coverage_warnings = 0; - for (auto &mark: flow_coverage_marks) { - get_buffer()->delete_mark(mark.first); - get_buffer()->delete_mark(mark.second); - } - flow_coverage_marks.clear(); - - if (exit_status == 0) { - boost::property_tree::ptree pt; - try { - boost::property_tree::read_json(*stdout_stream, pt); - auto uncovered_locs_pt = pt.get_child("expressions.uncovered_locs"); - for (auto it = uncovered_locs_pt.begin(); it != uncovered_locs_pt.end(); ++it) { - auto start_pt = it->second.get_child("start"); - auto start = get_iter_at_line_offset(start_pt.get<int>("line") - 1, - start_pt.get<int>("column") - 1); - auto end_pt = it->second.get_child("end"); - auto end = get_iter_at_line_offset(end_pt.get<int>("line") - 1, end_pt.get<int>("column")); - - add_diagnostic_tooltip(start, end, flow_coverage_message, false); - ++num_flow_coverage_warnings; - - flow_coverage_marks.emplace_back(get_buffer()->create_mark(start), get_buffer()->create_mark(end)); - } - } - catch (...) {} + std::stringstream stdin_stream, stderr_stream; + auto stdout_stream = std::make_shared<std::stringstream>(); + auto exit_status = Terminal::get().process(stdin_stream, *stdout_stream, + flow_coverage_executable.string() + " coverage --json " + + file_path.string(), "", &stderr_stream); + auto f = [this, exit_status, stdout_stream] { + clear_diagnostic_tooltips(); + num_flow_coverage_warnings = 0; + for (auto &mark: flow_coverage_marks) { + get_buffer()->delete_mark(mark.first); + get_buffer()->delete_mark(mark.second); + } + flow_coverage_marks.clear(); + + if (exit_status == 0) { + boost::property_tree::ptree pt; + try { + boost::property_tree::read_json(*stdout_stream, pt); + auto uncovered_locs_pt = pt.get_child("expressions.uncovered_locs"); + for (auto it = uncovered_locs_pt.begin(); it != uncovered_locs_pt.end(); ++it) { + auto start_pt = it->second.get_child("start"); + auto start = get_iter_at_line_offset(start_pt.get<int>("line") - 1, + start_pt.get<int>("column") - 1); + auto end_pt = it->second.get_child("end"); + auto end = get_iter_at_line_offset(end_pt.get<int>("line") - 1, end_pt.get<int>("column")); + + add_diagnostic_tooltip(start, end, flow_coverage_message, false); + ++num_flow_coverage_warnings; + + flow_coverage_marks.emplace_back(get_buffer()->create_mark(start), get_buffer()->create_mark(end)); } - status_diagnostics = std::make_tuple(num_warnings + num_flow_coverage_warnings, num_errors, num_fix_its); - if (update_status_diagnostics) - update_status_diagnostics(this); - }; - if (called_in_thread) - dispatcher.post(std::move(f)); - else - f(); + } + catch (...) {} + } + status_diagnostics = std::make_tuple(num_warnings + num_flow_coverage_warnings, num_errors, num_fix_its); + if (update_status_diagnostics) + update_status_diagnostics(this); + }; + if (called_in_thread) + dispatcher.post(std::move(f)); + else + f(); } diff --git a/src/source_language_protocol.h b/src/source_language_protocol.h index dccd3574..bb068108 100644 --- a/src/source_language_protocol.h +++ b/src/source_language_protocol.h @@ -11,144 +11,144 @@ #include <unordered_set> namespace Source { - class LanguageProtocolView; + class LanguageProtocolView; } namespace LanguageProtocol { - class Diagnostic { - public: - std::string spelling; - std::pair<Source::Offset, Source::Offset> offsets; - unsigned severity; - std::string uri; + class Diagnostic { + public: + std::string spelling; + std::pair<Source::Offset, Source::Offset> offsets; + unsigned severity; + std::string uri; + }; + + class Capabilities { + public: + enum class TextDocumentSync { + NONE = 0, + FULL, + INCREMENTAL }; + TextDocumentSync text_document_sync; + bool hover; + bool completion; + bool definition; + bool references; + bool document_highlight; + bool workspace_symbol; + bool document_formatting; + bool document_range_formatting; + bool rename; + }; - class Capabilities { - public: - enum class TextDocumentSync { - NONE = 0, - FULL, - INCREMENTAL - }; - TextDocumentSync text_document_sync; - bool hover; - bool completion; - bool definition; - bool references; - bool document_highlight; - bool workspace_symbol; - bool document_formatting; - bool document_range_formatting; - bool rename; - }; - - class Client { - Client(std::string root_uri, std::string language_id); + class Client { + Client(std::string root_uri, std::string language_id); - std::string root_uri; - std::string language_id; + std::string root_uri; + std::string language_id; - Capabilities capabilities; + Capabilities capabilities; - std::unordered_set<Source::LanguageProtocolView *> views; - std::mutex views_mutex; + std::unordered_set<Source::LanguageProtocolView *> views; + std::mutex views_mutex; - std::mutex initialize_mutex; + std::mutex initialize_mutex; - std::unique_ptr<TinyProcessLib::Process> process; - std::mutex read_write_mutex; + std::unique_ptr<TinyProcessLib::Process> process; + std::mutex read_write_mutex; - std::stringstream server_message_stream; - size_t server_message_size = static_cast<size_t>(-1); - size_t server_message_content_pos; - bool header_read = false; + std::stringstream server_message_stream; + size_t server_message_size = static_cast<size_t>(-1); + size_t server_message_content_pos; + bool header_read = false; - size_t message_id = 1; + size_t message_id = 1; - std::unordered_map<size_t, std::pair<Source::LanguageProtocolView *, std::function<void( - const boost::property_tree::ptree &, bool error)>>> handlers; - std::vector<std::thread> timeout_threads; - std::mutex timeout_threads_mutex; + std::unordered_map<size_t, std::pair<Source::LanguageProtocolView *, std::function<void( + const boost::property_tree::ptree &, bool error)>>> handlers; + std::vector<std::thread> timeout_threads; + std::mutex timeout_threads_mutex; - public: - static std::shared_ptr<Client> get(const boost::filesystem::path &file_path, const std::string &language_id); + public: + static std::shared_ptr<Client> get(const boost::filesystem::path &file_path, const std::string &language_id); - ~Client(); + ~Client(); - bool initialized = false; + bool initialized = false; - Capabilities initialize(Source::LanguageProtocolView *view); + Capabilities initialize(Source::LanguageProtocolView *view); - void close(Source::LanguageProtocolView *view); + void close(Source::LanguageProtocolView *view); - void parse_server_message(); + void parse_server_message(); - void write_request(Source::LanguageProtocolView *view, const std::string &method, const std::string ¶ms, - std::function<void(const boost::property_tree::ptree &, bool)> &&function = nullptr); + void write_request(Source::LanguageProtocolView *view, const std::string &method, const std::string ¶ms, + std::function<void(const boost::property_tree::ptree &, bool)> &&function = nullptr); - void write_notification(const std::string &method, const std::string ¶ms); + void write_notification(const std::string &method, const std::string ¶ms); - void handle_server_request(const std::string &method, const boost::property_tree::ptree ¶ms); - }; + void handle_server_request(const std::string &method, const boost::property_tree::ptree ¶ms); + }; } // namespace LanguageProtocol namespace Source { - class LanguageProtocolView : public View { - public: - LanguageProtocolView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language, - std::string language_id_); + class LanguageProtocolView : public View { + public: + LanguageProtocolView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language, + std::string language_id_); - ~LanguageProtocolView(); + ~LanguageProtocolView(); - std::string uri; + std::string uri; - bool save() override; + bool save() override; - void update_diagnostics(std::vector<LanguageProtocol::Diagnostic> &&diagnostics); + void update_diagnostics(std::vector<LanguageProtocol::Diagnostic> &&diagnostics); - Gtk::TextIter get_iter_at_line_pos(int line, int pos) override; + Gtk::TextIter get_iter_at_line_pos(int line, int pos) override; - protected: - void show_type_tooltips(const Gdk::Rectangle &rectangle) override; + protected: + void show_type_tooltips(const Gdk::Rectangle &rectangle) override; - private: - std::string language_id; - LanguageProtocol::Capabilities capabilities; + private: + std::string language_id; + LanguageProtocol::Capabilities capabilities; - std::shared_ptr<LanguageProtocol::Client> client; + std::shared_ptr<LanguageProtocol::Client> client; - size_t document_version = 1; + size_t document_version = 1; - std::thread initialize_thread; - Dispatcher dispatcher; + std::thread initialize_thread; + Dispatcher dispatcher; - void setup_navigation_and_refactoring(); + void setup_navigation_and_refactoring(); - void escape_text(std::string &text); + void escape_text(std::string &text); - void unescape_text(std::string &text); + void unescape_text(std::string &text); - Glib::RefPtr<Gtk::TextTag> similar_symbol_tag; - sigc::connection delayed_tag_similar_symbols_connection; + Glib::RefPtr<Gtk::TextTag> similar_symbol_tag; + sigc::connection delayed_tag_similar_symbols_connection; - void tag_similar_symbols(); + void tag_similar_symbols(); - Offset get_declaration(const Gtk::TextIter &iter); + Offset get_declaration(const Gtk::TextIter &iter); - Autocomplete autocomplete; + Autocomplete autocomplete; - void setup_autocomplete(); + void setup_autocomplete(); - std::vector<std::string> autocomplete_comment; - std::vector<std::string> autocomplete_insert; - std::list<std::pair<Glib::RefPtr<Gtk::TextBuffer::Mark>, Glib::RefPtr<Gtk::TextBuffer::Mark>>> autocomplete_marks; - bool autocomplete_keep_marks = false; + std::vector<std::string> autocomplete_comment; + std::vector<std::string> autocomplete_insert; + std::list<std::pair<Glib::RefPtr<Gtk::TextBuffer::Mark>, Glib::RefPtr<Gtk::TextBuffer::Mark>>> autocomplete_marks; + bool autocomplete_keep_marks = false; - boost::filesystem::path flow_coverage_executable; - std::vector<std::pair<Glib::RefPtr<Gtk::TextMark>, Glib::RefPtr<Gtk::TextMark>>> flow_coverage_marks; - const std::string flow_coverage_message = "Not covered by Flow"; - size_t num_warnings = 0, num_errors = 0, num_fix_its = 0, num_flow_coverage_warnings = 0; + boost::filesystem::path flow_coverage_executable; + std::vector<std::pair<Glib::RefPtr<Gtk::TextMark>, Glib::RefPtr<Gtk::TextMark>>> flow_coverage_marks; + const std::string flow_coverage_message = "Not covered by Flow"; + size_t num_warnings = 0, num_errors = 0, num_fix_its = 0, num_flow_coverage_warnings = 0; - void add_flow_coverage_tooltips(bool called_in_thread); - }; + void add_flow_coverage_tooltips(bool called_in_thread); + }; } // namespace Source diff --git a/src/source_spellcheck.cc b/src/source_spellcheck.cc index ff2bb227..3785ce4e 100644 --- a/src/source_spellcheck.cc +++ b/src/source_spellcheck.cc @@ -7,506 +7,506 @@ AspellConfig *Source::SpellCheckView::spellcheck_config = nullptr; Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language) - : BaseView(file_path, language) { - if (spellcheck_config == nullptr) - spellcheck_config = new_aspell_config(); - spellcheck_checker = nullptr; - spellcheck_error_tag = get_buffer()->create_tag("spellcheck_error"); - spellcheck_error_tag->property_underline() = Pango::Underline::UNDERLINE_ERROR; - - signal_key_press_event().connect([](GdkEventKey *event) { - if (SelectionDialog::get() && SelectionDialog::get()->is_visible()) { - if (SelectionDialog::get()->on_key_press(event)) - return true; - } + : BaseView(file_path, language) { + if (spellcheck_config == nullptr) + spellcheck_config = new_aspell_config(); + spellcheck_checker = nullptr; + spellcheck_error_tag = get_buffer()->create_tag("spellcheck_error"); + spellcheck_error_tag->property_underline() = Pango::Underline::UNDERLINE_ERROR; + + signal_key_press_event().connect([](GdkEventKey *event) { + if (SelectionDialog::get() && SelectionDialog::get()->is_visible()) { + if (SelectionDialog::get()->on_key_press(event)) + return true; + } - return false; - }, false); + return false; + }, false); - //The following signal is added in case SpellCheckView is not subclassed - signal_key_press_event().connect([this](GdkEventKey *event) { - last_keyval = event->keyval; - return false; - }); + //The following signal is added in case SpellCheckView is not subclassed + signal_key_press_event().connect([this](GdkEventKey *event) { + last_keyval = event->keyval; + return false; + }); - get_buffer()->signal_changed().connect([this]() { - if (spellcheck_checker == nullptr) - return; + get_buffer()->signal_changed().connect([this]() { + if (spellcheck_checker == nullptr) + return; - delayed_spellcheck_suggestions_connection.disconnect(); + delayed_spellcheck_suggestions_connection.disconnect(); - auto iter = get_buffer()->get_insert()->get_iter(); - if (!is_word_iter(iter) && !iter.starts_line()) - iter.backward_char(); + auto iter = get_buffer()->get_insert()->get_iter(); + if (!is_word_iter(iter) && !iter.starts_line()) + iter.backward_char(); + + if (disable_spellcheck) { + if (is_word_iter(iter)) { + auto word = get_word(iter); + get_buffer()->remove_tag(spellcheck_error_tag, word.first, word.second); + } + return; + } - if (disable_spellcheck) { - if (is_word_iter(iter)) { - auto word = get_word(iter); - get_buffer()->remove_tag(spellcheck_error_tag, word.first, word.second); - } - return; + if (!is_code_iter(iter)) { + if (last_keyval == GDK_KEY_Return || last_keyval == GDK_KEY_KP_Enter) { + auto previous_line_iter = iter; + while (previous_line_iter.backward_char() && !previous_line_iter.ends_line()) {} + if (previous_line_iter.backward_char()) { + get_buffer()->remove_tag(spellcheck_error_tag, previous_line_iter, iter); + if (!is_code_iter(previous_line_iter)) { + auto word = get_word(previous_line_iter); + spellcheck_word(word.first, word.second); + } + auto word = get_word(iter); + spellcheck_word(word.first, word.second); } + } else { + auto previous_iter = iter; + //When for instance using space to split two words + if (!iter.starts_line() && !iter.ends_line() && is_word_iter(iter) && + previous_iter.backward_char() && !previous_iter.starts_line() && !is_word_iter(previous_iter)) { + auto first = previous_iter; + auto second = iter; + if (first.backward_char()) { + get_buffer()->remove_tag(spellcheck_error_tag, first, second); + auto word = get_word(first); + spellcheck_word(word.first, word.second); + word = get_word(second); + spellcheck_word(word.first, word.second); + } + } else { + auto word = get_word(iter); + spellcheck_word(word.first, word.second); + } + } + } + delayed_spellcheck_error_clear.disconnect(); + delayed_spellcheck_error_clear = Glib::signal_timeout().connect([this]() { + auto iter = get_buffer()->begin(); + Gtk::TextIter begin_no_spellcheck_iter; + if (spellcheck_all) { + bool spell_check = !get_source_buffer()->iter_has_context_class(iter, "no-spell-check"); + if (!spell_check) + begin_no_spellcheck_iter = iter; + while (iter != get_buffer()->end()) { + if (!get_source_buffer()->iter_forward_to_context_class_toggle(iter, "no-spell-check")) + iter = get_buffer()->end(); + + spell_check = !spell_check; + if (!spell_check) + begin_no_spellcheck_iter = iter; + else + get_buffer()->remove_tag(spellcheck_error_tag, begin_no_spellcheck_iter, iter); + } + return false; + } + + bool spell_check = get_source_buffer()->iter_has_context_class(iter, "string") || + get_source_buffer()->iter_has_context_class(iter, "comment"); + if (!spell_check) + begin_no_spellcheck_iter = iter; + while (iter != get_buffer()->end()) { + auto iter1 = iter; + auto iter2 = iter; + if (!get_source_buffer()->iter_forward_to_context_class_toggle(iter1, "string")) + iter1 = get_buffer()->end(); + if (!get_source_buffer()->iter_forward_to_context_class_toggle(iter2, "comment")) + iter2 = get_buffer()->end(); + + if (iter2 < iter1) + iter = iter2; + else + iter = iter1; + spell_check = !spell_check; + if (!spell_check) + begin_no_spellcheck_iter = iter; + else + get_buffer()->remove_tag(spellcheck_error_tag, begin_no_spellcheck_iter, iter); + } + return false; + }, 1000); + }); + + // In case of for instance text paste or undo/redo + get_buffer()->signal_insert().connect( + [this](const Gtk::TextIter &start_iter, const Glib::ustring &inserted_string, int) { + if (spellcheck_checker == nullptr) + return; - if (!is_code_iter(iter)) { - if (last_keyval == GDK_KEY_Return || last_keyval == GDK_KEY_KP_Enter) { - auto previous_line_iter = iter; - while (previous_line_iter.backward_char() && !previous_line_iter.ends_line()) {} - if (previous_line_iter.backward_char()) { - get_buffer()->remove_tag(spellcheck_error_tag, previous_line_iter, iter); - if (!is_code_iter(previous_line_iter)) { - auto word = get_word(previous_line_iter); - spellcheck_word(word.first, word.second); - } - auto word = get_word(iter); - spellcheck_word(word.first, word.second); - } - } else { - auto previous_iter = iter; - //When for instance using space to split two words - if (!iter.starts_line() && !iter.ends_line() && is_word_iter(iter) && - previous_iter.backward_char() && !previous_iter.starts_line() && !is_word_iter(previous_iter)) { - auto first = previous_iter; - auto second = iter; - if (first.backward_char()) { - get_buffer()->remove_tag(spellcheck_error_tag, first, second); - auto word = get_word(first); - spellcheck_word(word.first, word.second); - word = get_word(second); - spellcheck_word(word.first, word.second); - } - } else { - auto word = get_word(iter); - spellcheck_word(word.first, word.second); - } - } + if (!disable_spellcheck) + return; + + auto iter = start_iter; + if (!is_word_iter(iter) && !iter.starts_line()) + iter.backward_char(); + if (is_word_iter(iter)) { + auto word = get_word(iter); + get_buffer()->remove_tag(spellcheck_error_tag, word.first, word.second); } - delayed_spellcheck_error_clear.disconnect(); - delayed_spellcheck_error_clear = Glib::signal_timeout().connect([this]() { - auto iter = get_buffer()->begin(); - Gtk::TextIter begin_no_spellcheck_iter; - if (spellcheck_all) { - bool spell_check = !get_source_buffer()->iter_has_context_class(iter, "no-spell-check"); - if (!spell_check) - begin_no_spellcheck_iter = iter; - while (iter != get_buffer()->end()) { - if (!get_source_buffer()->iter_forward_to_context_class_toggle(iter, "no-spell-check")) - iter = get_buffer()->end(); - - spell_check = !spell_check; - if (!spell_check) - begin_no_spellcheck_iter = iter; - else - get_buffer()->remove_tag(spellcheck_error_tag, begin_no_spellcheck_iter, iter); + }, false); + + get_buffer()->signal_mark_set().connect( + [this](const Gtk::TextBuffer::iterator &iter, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { + if (spellcheck_checker == nullptr) + return; + + if (mark->get_name() == "insert") { + if (SelectionDialog::get()) + SelectionDialog::get()->hide(); + delayed_spellcheck_suggestions_connection.disconnect(); + delayed_spellcheck_suggestions_connection = Glib::signal_timeout().connect([this]() { + if (get_buffer()->get_insert()->get_iter().has_tag(spellcheck_error_tag)) { + SelectionDialog::create(this, + get_buffer()->create_mark(get_buffer()->get_insert()->get_iter()), + false); + auto word = get_word(get_buffer()->get_insert()->get_iter()); + if (*word.first == '\'' && word.second.get_offset() - word.first.get_offset() >= 3) { + auto before_end = word.second; + if (before_end.backward_char() && *before_end == '\'') { + word.first.forward_char(); + word.second.backward_char(); } + } + auto suggestions = get_spellcheck_suggestions(word.first, word.second); + if (suggestions.size() == 0) return false; - } - - bool spell_check = get_source_buffer()->iter_has_context_class(iter, "string") || - get_source_buffer()->iter_has_context_class(iter, "comment"); - if (!spell_check) - begin_no_spellcheck_iter = iter; - while (iter != get_buffer()->end()) { - auto iter1 = iter; - auto iter2 = iter; - if (!get_source_buffer()->iter_forward_to_context_class_toggle(iter1, "string")) - iter1 = get_buffer()->end(); - if (!get_source_buffer()->iter_forward_to_context_class_toggle(iter2, "comment")) - iter2 = get_buffer()->end(); - - if (iter2 < iter1) - iter = iter2; - else - iter = iter1; - spell_check = !spell_check; - if (!spell_check) - begin_no_spellcheck_iter = iter; - else - get_buffer()->remove_tag(spellcheck_error_tag, begin_no_spellcheck_iter, iter); + for (auto &suggestion: suggestions) + SelectionDialog::get()->add_row(suggestion); + SelectionDialog::get()->on_select = [this, word](unsigned int index, + const std::string &text, + bool hide_window) { + get_buffer()->begin_user_action(); + get_buffer()->erase(word.first, word.second); + get_buffer()->insert(get_buffer()->get_insert()->get_iter(), text); + get_buffer()->end_user_action(); + }; + hide_tooltips(); + SelectionDialog::get()->show(); } return false; - }, 1000); - }); - - // In case of for instance text paste or undo/redo - get_buffer()->signal_insert().connect( - [this](const Gtk::TextIter &start_iter, const Glib::ustring &inserted_string, int) { - if (spellcheck_checker == nullptr) - return; - - if (!disable_spellcheck) - return; - - auto iter = start_iter; - if (!is_word_iter(iter) && !iter.starts_line()) - iter.backward_char(); - if (is_word_iter(iter)) { - auto word = get_word(iter); - get_buffer()->remove_tag(spellcheck_error_tag, word.first, word.second); - } - }, false); - - get_buffer()->signal_mark_set().connect( - [this](const Gtk::TextBuffer::iterator &iter, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { - if (spellcheck_checker == nullptr) - return; - - if (mark->get_name() == "insert") { - if (SelectionDialog::get()) - SelectionDialog::get()->hide(); - delayed_spellcheck_suggestions_connection.disconnect(); - delayed_spellcheck_suggestions_connection = Glib::signal_timeout().connect([this]() { - if (get_buffer()->get_insert()->get_iter().has_tag(spellcheck_error_tag)) { - SelectionDialog::create(this, - get_buffer()->create_mark(get_buffer()->get_insert()->get_iter()), - false); - auto word = get_word(get_buffer()->get_insert()->get_iter()); - if (*word.first == '\'' && word.second.get_offset() - word.first.get_offset() >= 3) { - auto before_end = word.second; - if (before_end.backward_char() && *before_end == '\'') { - word.first.forward_char(); - word.second.backward_char(); - } - } - auto suggestions = get_spellcheck_suggestions(word.first, word.second); - if (suggestions.size() == 0) - return false; - for (auto &suggestion: suggestions) - SelectionDialog::get()->add_row(suggestion); - SelectionDialog::get()->on_select = [this, word](unsigned int index, - const std::string &text, - bool hide_window) { - get_buffer()->begin_user_action(); - get_buffer()->erase(word.first, word.second); - get_buffer()->insert(get_buffer()->get_insert()->get_iter(), text); - get_buffer()->end_user_action(); - }; - hide_tooltips(); - SelectionDialog::get()->show(); - } - return false; - }, 500); - } - }); + }, 500); + } + }); - get_buffer()->signal_mark_set().connect( - [](const Gtk::TextBuffer::iterator &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { - if (mark->get_name() == "insert") { - if (SelectionDialog::get()) - SelectionDialog::get()->hide(); - } - }); + get_buffer()->signal_mark_set().connect( + [](const Gtk::TextBuffer::iterator &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { + if (mark->get_name() == "insert") { + if (SelectionDialog::get()) + SelectionDialog::get()->hide(); + } + }); - signal_focus_out_event().connect([this](GdkEventFocus *event) { - delayed_spellcheck_suggestions_connection.disconnect(); - return false; - }); + signal_focus_out_event().connect([this](GdkEventFocus *event) { + delayed_spellcheck_suggestions_connection.disconnect(); + return false; + }); - signal_leave_notify_event().connect([this](GdkEventCrossing *) { - delayed_spellcheck_suggestions_connection.disconnect(); - return false; - }); - - signal_tag_added_connection = get_buffer()->get_tag_table()->signal_tag_added().connect( - [this](const Glib::RefPtr<Gtk::TextTag> &tag) { - if (tag->property_name() == "gtksourceview:context-classes:comment") - comment_tag = tag; - else if (tag->property_name() == "gtksourceview:context-classes:string") - string_tag = tag; - else if (tag->property_name() == "gtksourceview:context-classes:no-spell-check") - no_spell_check_tag = tag; - }); - signal_tag_removed_connection = get_buffer()->get_tag_table()->signal_tag_removed().connect( - [this](const Glib::RefPtr<Gtk::TextTag> &tag) { - if (tag->property_name() == "gtksourceview:context-classes:comment") - comment_tag.reset(); - else if (tag->property_name() == "gtksourceview:context-classes:string") - string_tag.reset(); - else if (tag->property_name() == "gtksourceview:context-classes:no-spell-check") - no_spell_check_tag.reset(); - }); + signal_leave_notify_event().connect([this](GdkEventCrossing *) { + delayed_spellcheck_suggestions_connection.disconnect(); + return false; + }); + + signal_tag_added_connection = get_buffer()->get_tag_table()->signal_tag_added().connect( + [this](const Glib::RefPtr<Gtk::TextTag> &tag) { + if (tag->property_name() == "gtksourceview:context-classes:comment") + comment_tag = tag; + else if (tag->property_name() == "gtksourceview:context-classes:string") + string_tag = tag; + else if (tag->property_name() == "gtksourceview:context-classes:no-spell-check") + no_spell_check_tag = tag; + }); + signal_tag_removed_connection = get_buffer()->get_tag_table()->signal_tag_removed().connect( + [this](const Glib::RefPtr<Gtk::TextTag> &tag) { + if (tag->property_name() == "gtksourceview:context-classes:comment") + comment_tag.reset(); + else if (tag->property_name() == "gtksourceview:context-classes:string") + string_tag.reset(); + else if (tag->property_name() == "gtksourceview:context-classes:no-spell-check") + no_spell_check_tag.reset(); + }); } Source::SpellCheckView::~SpellCheckView() { - delayed_spellcheck_suggestions_connection.disconnect(); - delayed_spellcheck_error_clear.disconnect(); + delayed_spellcheck_suggestions_connection.disconnect(); + delayed_spellcheck_error_clear.disconnect(); - if (spellcheck_checker != nullptr) - delete_aspell_speller(spellcheck_checker); + if (spellcheck_checker != nullptr) + delete_aspell_speller(spellcheck_checker); - signal_tag_added_connection.disconnect(); - signal_tag_removed_connection.disconnect(); + signal_tag_added_connection.disconnect(); + signal_tag_removed_connection.disconnect(); } void Source::SpellCheckView::configure() { - if (Config::get().source.spellcheck_language.size() > 0) { - aspell_config_replace(spellcheck_config, "lang", Config::get().source.spellcheck_language.c_str()); - aspell_config_replace(spellcheck_config, "encoding", "utf-8"); - } - spellcheck_possible_err = new_aspell_speller(spellcheck_config); - if (spellcheck_checker != nullptr) - delete_aspell_speller(spellcheck_checker); - spellcheck_checker = nullptr; - if (aspell_error_number(spellcheck_possible_err) != 0) - std::cerr << "Spell check error: " << aspell_error_message(spellcheck_possible_err) << std::endl; - else - spellcheck_checker = to_aspell_speller(spellcheck_possible_err); - get_buffer()->remove_tag(spellcheck_error_tag, get_buffer()->begin(), get_buffer()->end()); + if (Config::get().source.spellcheck_language.size() > 0) { + aspell_config_replace(spellcheck_config, "lang", Config::get().source.spellcheck_language.c_str()); + aspell_config_replace(spellcheck_config, "encoding", "utf-8"); + } + spellcheck_possible_err = new_aspell_speller(spellcheck_config); + if (spellcheck_checker != nullptr) + delete_aspell_speller(spellcheck_checker); + spellcheck_checker = nullptr; + if (aspell_error_number(spellcheck_possible_err) != 0) + std::cerr << "Spell check error: " << aspell_error_message(spellcheck_possible_err) << std::endl; + else + spellcheck_checker = to_aspell_speller(spellcheck_possible_err); + get_buffer()->remove_tag(spellcheck_error_tag, get_buffer()->begin(), get_buffer()->end()); } void Source::SpellCheckView::hide_dialogs() { - delayed_spellcheck_suggestions_connection.disconnect(); - if (SelectionDialog::get()) - SelectionDialog::get()->hide(); + delayed_spellcheck_suggestions_connection.disconnect(); + if (SelectionDialog::get()) + SelectionDialog::get()->hide(); } void Source::SpellCheckView::spellcheck(const Gtk::TextIter &start, const Gtk::TextIter &end) { - if (spellcheck_checker == nullptr) - return; - auto iter = start; - while (iter && iter < end) { - if (is_word_iter(iter)) { - auto word = get_word(iter); - spellcheck_word(word.first, word.second); - iter = word.second; - } - iter.forward_char(); + if (spellcheck_checker == nullptr) + return; + auto iter = start; + while (iter && iter < end) { + if (is_word_iter(iter)) { + auto word = get_word(iter); + spellcheck_word(word.first, word.second); + iter = word.second; } + iter.forward_char(); + } } void Source::SpellCheckView::spellcheck() { - auto iter = get_buffer()->begin(); - Gtk::TextIter begin_spellcheck_iter; - if (spellcheck_all) { - bool spell_check = !get_source_buffer()->iter_has_context_class(iter, "no-spell-check"); - if (spell_check) - begin_spellcheck_iter = iter; - while (iter != get_buffer()->end()) { - if (!get_source_buffer()->iter_forward_to_context_class_toggle(iter, "no-spell-check")) - iter = get_buffer()->end(); - - spell_check = !spell_check; - if (spell_check) - begin_spellcheck_iter = iter; - else - spellcheck(begin_spellcheck_iter, iter); - } - } else { - bool spell_check = !is_code_iter(iter); - if (spell_check) - begin_spellcheck_iter = iter; - while (iter != get_buffer()->end()) { - auto iter1 = iter; - auto iter2 = iter; - if (!get_source_buffer()->iter_forward_to_context_class_toggle(iter1, "string")) - iter1 = get_buffer()->end(); - if (!get_source_buffer()->iter_forward_to_context_class_toggle(iter2, "comment")) - iter2 = get_buffer()->end(); - - if (iter2 < iter1) - iter = iter2; - else - iter = iter1; - spell_check = !spell_check; - if (spell_check) - begin_spellcheck_iter = iter; - else - spellcheck(begin_spellcheck_iter, iter); - } + auto iter = get_buffer()->begin(); + Gtk::TextIter begin_spellcheck_iter; + if (spellcheck_all) { + bool spell_check = !get_source_buffer()->iter_has_context_class(iter, "no-spell-check"); + if (spell_check) + begin_spellcheck_iter = iter; + while (iter != get_buffer()->end()) { + if (!get_source_buffer()->iter_forward_to_context_class_toggle(iter, "no-spell-check")) + iter = get_buffer()->end(); + + spell_check = !spell_check; + if (spell_check) + begin_spellcheck_iter = iter; + else + spellcheck(begin_spellcheck_iter, iter); } + } else { + bool spell_check = !is_code_iter(iter); + if (spell_check) + begin_spellcheck_iter = iter; + while (iter != get_buffer()->end()) { + auto iter1 = iter; + auto iter2 = iter; + if (!get_source_buffer()->iter_forward_to_context_class_toggle(iter1, "string")) + iter1 = get_buffer()->end(); + if (!get_source_buffer()->iter_forward_to_context_class_toggle(iter2, "comment")) + iter2 = get_buffer()->end(); + + if (iter2 < iter1) + iter = iter2; + else + iter = iter1; + spell_check = !spell_check; + if (spell_check) + begin_spellcheck_iter = iter; + else + spellcheck(begin_spellcheck_iter, iter); + } + } } void Source::SpellCheckView::remove_spellcheck_errors() { - get_buffer()->remove_tag(spellcheck_error_tag, get_buffer()->begin(), get_buffer()->end()); + get_buffer()->remove_tag(spellcheck_error_tag, get_buffer()->begin(), get_buffer()->end()); } void Source::SpellCheckView::goto_next_spellcheck_error() { - auto iter = get_buffer()->get_insert()->get_iter(); - auto insert_iter = iter; - bool wrapped = false; + auto iter = get_buffer()->get_insert()->get_iter(); + auto insert_iter = iter; + bool wrapped = false; + iter.forward_char(); + while (!wrapped || iter < insert_iter) { + auto toggled_tags = iter.get_toggled_tags(); + for (auto &toggled_tag: toggled_tags) { + if (toggled_tag == spellcheck_error_tag) { + get_buffer()->place_cursor(iter); + scroll_to(get_buffer()->get_insert(), 0.0, 1.0, 0.5); + return; + } + } iter.forward_char(); - while (!wrapped || iter < insert_iter) { - auto toggled_tags = iter.get_toggled_tags(); - for (auto &toggled_tag: toggled_tags) { - if (toggled_tag == spellcheck_error_tag) { - get_buffer()->place_cursor(iter); - scroll_to(get_buffer()->get_insert(), 0.0, 1.0, 0.5); - return; - } - } - iter.forward_char(); - if (!wrapped && iter == get_buffer()->end()) { - iter = get_buffer()->begin(); - wrapped = true; - } + if (!wrapped && iter == get_buffer()->end()) { + iter = get_buffer()->begin(); + wrapped = true; } - Info::get().print("No spelling errors found in current buffer"); + } + Info::get().print("No spelling errors found in current buffer"); } bool Source::SpellCheckView::is_code_iter(const Gtk::TextIter &iter) { - if (*iter == '\'') { - auto previous_iter = iter; - if (!iter.starts_line() && previous_iter.backward_char() && *previous_iter == '\'') - return false; - } - if (spellcheck_all) { - if (no_spell_check_tag && (iter.has_tag(no_spell_check_tag) || iter.begins_tag(no_spell_check_tag) || - iter.ends_tag(no_spell_check_tag))) + if (*iter == '\'') { + auto previous_iter = iter; + if (!iter.starts_line() && previous_iter.backward_char() && *previous_iter == '\'') + return false; + } + if (spellcheck_all) { + if (no_spell_check_tag && (iter.has_tag(no_spell_check_tag) || iter.begins_tag(no_spell_check_tag) || + iter.ends_tag(no_spell_check_tag))) + return true; + // workaround for gtksourceview bug + if (iter.ends_line()) { + auto previous_iter = iter; + if (previous_iter.backward_char()) { + if (*previous_iter == '\'' || *previous_iter == '"') { + auto next_iter = iter; + next_iter.forward_char(); + if (next_iter.begins_tag(no_spell_check_tag) || next_iter.is_end()) return true; - // workaround for gtksourceview bug - if (iter.ends_line()) { - auto previous_iter = iter; - if (previous_iter.backward_char()) { - if (*previous_iter == '\'' || *previous_iter == '"') { - auto next_iter = iter; - next_iter.forward_char(); - if (next_iter.begins_tag(no_spell_check_tag) || next_iter.is_end()) - return true; - } - } } - // for example, mark first " as code iter in this case: r"" - if (*iter == '\'' || *iter == '"') { - auto previous_iter = iter; - if (previous_iter.backward_char() && *previous_iter != '\'' && *previous_iter != '\"' && - previous_iter.ends_tag(no_spell_check_tag)) - return true; - } - return false; + } } - if (comment_tag) { - if (iter.has_tag(comment_tag) && !iter.begins_tag(comment_tag)) - return false; - //Exception at the end of /**/ - else if (iter.ends_tag(comment_tag)) { - auto previous_iter = iter; - if (previous_iter.backward_char() && *previous_iter == '/') { - auto previous_previous_iter = previous_iter; - if (previous_previous_iter.backward_char() && *previous_previous_iter == '*') { - auto it = previous_iter; - while (!it.begins_tag(comment_tag) && it.backward_to_tag_toggle(comment_tag)) {} - auto next_iter = it; - if (it.begins_tag(comment_tag) && next_iter.forward_char() && *it == '/' && *next_iter == '*' && - previous_iter != it) - return true; - } - } - return false; + // for example, mark first " as code iter in this case: r"" + if (*iter == '\'' || *iter == '"') { + auto previous_iter = iter; + if (previous_iter.backward_char() && *previous_iter != '\'' && *previous_iter != '\"' && + previous_iter.ends_tag(no_spell_check_tag)) + return true; + } + return false; + } + if (comment_tag) { + if (iter.has_tag(comment_tag) && !iter.begins_tag(comment_tag)) + return false; + //Exception at the end of /**/ + else if (iter.ends_tag(comment_tag)) { + auto previous_iter = iter; + if (previous_iter.backward_char() && *previous_iter == '/') { + auto previous_previous_iter = previous_iter; + if (previous_previous_iter.backward_char() && *previous_previous_iter == '*') { + auto it = previous_iter; + while (!it.begins_tag(comment_tag) && it.backward_to_tag_toggle(comment_tag)) {} + auto next_iter = it; + if (it.begins_tag(comment_tag) && next_iter.forward_char() && *it == '/' && *next_iter == '*' && + previous_iter != it) + return true; } + } + return false; } - if (string_tag) { - if (iter.has_tag(string_tag)) { - // When ending an open ''-string with ', the last '-iter is not correctly marked as end iter for string_tag - // For instance 'test, when inserting ' at end, would lead to spellcheck error of test' - if (*iter == '\'') { - long backslash_count = 0; - auto it = iter; - while (it.backward_char() && *it == '\\') - ++backslash_count; - if (backslash_count % 2 == 0) { - auto it = iter; - while (!it.begins_tag(string_tag) && it.backward_to_tag_toggle(string_tag)) {} - if (it.begins_tag(string_tag) && *it == '\'' && iter != it) - return true; - } - } - if (!iter.begins_tag(string_tag)) - return false; + } + if (string_tag) { + if (iter.has_tag(string_tag)) { + // When ending an open ''-string with ', the last '-iter is not correctly marked as end iter for string_tag + // For instance 'test, when inserting ' at end, would lead to spellcheck error of test' + if (*iter == '\'') { + long backslash_count = 0; + auto it = iter; + while (it.backward_char() && *it == '\\') + ++backslash_count; + if (backslash_count % 2 == 0) { + auto it = iter; + while (!it.begins_tag(string_tag) && it.backward_to_tag_toggle(string_tag)) {} + if (it.begins_tag(string_tag) && *it == '\'' && iter != it) + return true; } - // If iter is at the end of string_tag, with exception of after " and ' - else if (iter.ends_tag(string_tag)) { - auto previous_iter = iter; - if (!iter.starts_line() && previous_iter.backward_char()) { - if ((*previous_iter == '"' || *previous_iter == '\'')) { - long backslash_count = 0; - auto it = previous_iter; - while (it.backward_char() && *it == '\\') - ++backslash_count; - if (backslash_count % 2 == 0) { - auto it = previous_iter; - while (!it.begins_tag(string_tag) && it.backward_to_tag_toggle(string_tag)) {} - if (it.begins_tag(string_tag) && *previous_iter == *it && previous_iter != it) - return true; - } - } - return false; - } + } + if (!iter.begins_tag(string_tag)) + return false; + } + // If iter is at the end of string_tag, with exception of after " and ' + else if (iter.ends_tag(string_tag)) { + auto previous_iter = iter; + if (!iter.starts_line() && previous_iter.backward_char()) { + if ((*previous_iter == '"' || *previous_iter == '\'')) { + long backslash_count = 0; + auto it = previous_iter; + while (it.backward_char() && *it == '\\') + ++backslash_count; + if (backslash_count % 2 == 0) { + auto it = previous_iter; + while (!it.begins_tag(string_tag) && it.backward_to_tag_toggle(string_tag)) {} + if (it.begins_tag(string_tag) && *previous_iter == *it && previous_iter != it) + return true; + } } + return false; + } } - return true; + } + return true; } bool Source::SpellCheckView::is_word_iter(const Gtk::TextIter &iter) { - auto previous_iter = iter; - size_t backslash_count = 0; - while (previous_iter.backward_char() && *previous_iter == '\\') - ++backslash_count; - if (backslash_count % 2 == 1) - return false; - if (((*iter >= 'A' && *iter <= 'Z') || (*iter >= 'a' && *iter <= 'z') || *iter >= 128)) - return true; - if (*iter == '\'') { - if (is_code_iter(iter)) - return false; - auto next_iter = iter; - if (next_iter.forward_char() && is_code_iter(next_iter) && - !(comment_tag && iter.ends_tag(comment_tag))) // additional check for end of line comment - return false; - return true; - } + auto previous_iter = iter; + size_t backslash_count = 0; + while (previous_iter.backward_char() && *previous_iter == '\\') + ++backslash_count; + if (backslash_count % 2 == 1) return false; + if (((*iter >= 'A' && *iter <= 'Z') || (*iter >= 'a' && *iter <= 'z') || *iter >= 128)) + return true; + if (*iter == '\'') { + if (is_code_iter(iter)) + return false; + auto next_iter = iter; + if (next_iter.forward_char() && is_code_iter(next_iter) && + !(comment_tag && iter.ends_tag(comment_tag))) // additional check for end of line comment + return false; + return true; + } + return false; } std::pair<Gtk::TextIter, Gtk::TextIter> Source::SpellCheckView::get_word(Gtk::TextIter iter) { - auto start = iter; - auto end = iter; - - while (is_word_iter(iter)) { - start = iter; - if (!iter.backward_char()) - break; - } - while (is_word_iter(end)) { - if (!end.forward_char()) - break; - } - - return {start, end}; + auto start = iter; + auto end = iter; + + while (is_word_iter(iter)) { + start = iter; + if (!iter.backward_char()) + break; + } + while (is_word_iter(end)) { + if (!end.forward_char()) + break; + } + + return {start, end}; } void Source::SpellCheckView::spellcheck_word(Gtk::TextIter start, Gtk::TextIter end) { - if (*start == '\'' && end.get_offset() - start.get_offset() >= 3) { - auto before_end = end; - if (before_end.backward_char() && *before_end == '\'') { - get_buffer()->remove_tag(spellcheck_error_tag, start, end); - start.forward_char(); - end.backward_char(); - } + if (*start == '\'' && end.get_offset() - start.get_offset() >= 3) { + auto before_end = end; + if (before_end.backward_char() && *before_end == '\'') { + get_buffer()->remove_tag(spellcheck_error_tag, start, end); + start.forward_char(); + end.backward_char(); } + } - auto word = get_buffer()->get_text(start, end); - if (word.size() > 0) { - auto correct = aspell_speller_check(spellcheck_checker, word.data(), word.bytes()); - if (correct == 0) - get_buffer()->apply_tag(spellcheck_error_tag, start, end); - else - get_buffer()->remove_tag(spellcheck_error_tag, start, end); - } + auto word = get_buffer()->get_text(start, end); + if (word.size() > 0) { + auto correct = aspell_speller_check(spellcheck_checker, word.data(), word.bytes()); + if (correct == 0) + get_buffer()->apply_tag(spellcheck_error_tag, start, end); + else + get_buffer()->remove_tag(spellcheck_error_tag, start, end); + } } std::vector<std::string> Source::SpellCheckView::get_spellcheck_suggestions(const Gtk::TextIter &start, const Gtk::TextIter &end) { - auto word_with_error = get_buffer()->get_text(start, end); + auto word_with_error = get_buffer()->get_text(start, end); - const AspellWordList *suggestions = aspell_speller_suggest(spellcheck_checker, word_with_error.data(), - word_with_error.bytes()); - AspellStringEnumeration *elements = aspell_word_list_elements(suggestions); + const AspellWordList *suggestions = aspell_speller_suggest(spellcheck_checker, word_with_error.data(), + word_with_error.bytes()); + AspellStringEnumeration *elements = aspell_word_list_elements(suggestions); - std::vector<std::string> words; - const char *word; - while ((word = aspell_string_enumeration_next(elements)) != nullptr) { - words.emplace_back(word); - } - delete_aspell_string_enumeration(elements); + std::vector<std::string> words; + const char *word; + while ((word = aspell_string_enumeration_next(elements)) != nullptr) { + words.emplace_back(word); + } + delete_aspell_string_enumeration(elements); - return words; + return words; } diff --git a/src/source_spellcheck.h b/src/source_spellcheck.h index 1b6b476e..4ae97b63 100644 --- a/src/source_spellcheck.h +++ b/src/source_spellcheck.h @@ -4,52 +4,52 @@ #include <aspell.h> namespace Source { - class SpellCheckView : virtual public Source::BaseView { - public: - SpellCheckView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); + class SpellCheckView : virtual public Source::BaseView { + public: + SpellCheckView(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); - ~SpellCheckView(); + ~SpellCheckView(); - void configure() override; + void configure() override; - void hide_dialogs() override; + void hide_dialogs() override; - void spellcheck(); + void spellcheck(); - void remove_spellcheck_errors(); + void remove_spellcheck_errors(); - void goto_next_spellcheck_error(); + void goto_next_spellcheck_error(); - protected: - bool is_code_iter(const Gtk::TextIter &iter); + protected: + bool is_code_iter(const Gtk::TextIter &iter); - bool spellcheck_all = false; - guint last_keyval = 0; + bool spellcheck_all = false; + guint last_keyval = 0; - Glib::RefPtr<Gtk::TextTag> comment_tag; - Glib::RefPtr<Gtk::TextTag> string_tag; - Glib::RefPtr<Gtk::TextTag> no_spell_check_tag; - private: - Glib::RefPtr<Gtk::TextTag> spellcheck_error_tag; + Glib::RefPtr<Gtk::TextTag> comment_tag; + Glib::RefPtr<Gtk::TextTag> string_tag; + Glib::RefPtr<Gtk::TextTag> no_spell_check_tag; + private: + Glib::RefPtr<Gtk::TextTag> spellcheck_error_tag; - sigc::connection signal_tag_added_connection; - sigc::connection signal_tag_removed_connection; + sigc::connection signal_tag_added_connection; + sigc::connection signal_tag_removed_connection; - static AspellConfig *spellcheck_config; - AspellCanHaveError *spellcheck_possible_err; - AspellSpeller *spellcheck_checker; + static AspellConfig *spellcheck_config; + AspellCanHaveError *spellcheck_possible_err; + AspellSpeller *spellcheck_checker; - bool is_word_iter(const Gtk::TextIter &iter); + bool is_word_iter(const Gtk::TextIter &iter); - std::pair<Gtk::TextIter, Gtk::TextIter> get_word(Gtk::TextIter iter); + std::pair<Gtk::TextIter, Gtk::TextIter> get_word(Gtk::TextIter iter); - void spellcheck_word(Gtk::TextIter start, Gtk::TextIter end); + void spellcheck_word(Gtk::TextIter start, Gtk::TextIter end); - std::vector<std::string> get_spellcheck_suggestions(const Gtk::TextIter &start, const Gtk::TextIter &end); + std::vector<std::string> get_spellcheck_suggestions(const Gtk::TextIter &start, const Gtk::TextIter &end); - sigc::connection delayed_spellcheck_suggestions_connection; - sigc::connection delayed_spellcheck_error_clear; + sigc::connection delayed_spellcheck_suggestions_connection; + sigc::connection delayed_spellcheck_error_clear; - void spellcheck(const Gtk::TextIter &start, const Gtk::TextIter &end); - }; + void spellcheck(const Gtk::TextIter &start, const Gtk::TextIter &end); + }; } diff --git a/src/terminal.cc b/src/terminal.cc index ab285590..6ee3e95f 100644 --- a/src/terminal.cc +++ b/src/terminal.cc @@ -9,426 +9,426 @@ #include <thread> Terminal::Terminal() { - set_editable(false); + set_editable(false); - bold_tag = get_buffer()->create_tag(); - bold_tag->property_weight() = Pango::WEIGHT_ULTRAHEAVY; + bold_tag = get_buffer()->create_tag(); + bold_tag->property_weight() = Pango::WEIGHT_ULTRAHEAVY; - link_tag = get_buffer()->create_tag(); - link_tag->property_underline() = Pango::Underline::UNDERLINE_SINGLE; + link_tag = get_buffer()->create_tag(); + link_tag->property_underline() = Pango::Underline::UNDERLINE_SINGLE; - link_mouse_cursor = Gdk::Cursor::create(Gdk::CursorType::HAND1); - default_mouse_cursor = Gdk::Cursor::create(Gdk::CursorType::XTERM); + link_mouse_cursor = Gdk::Cursor::create(Gdk::CursorType::HAND1); + default_mouse_cursor = Gdk::Cursor::create(Gdk::CursorType::XTERM); } int Terminal::process(const std::string &command, const boost::filesystem::path &path, bool use_pipes) { - std::unique_ptr<TinyProcessLib::Process> process; - if (use_pipes) - process = std::make_unique<TinyProcessLib::Process>(command, path.string(), - [this](const char *bytes, size_t n) { - async_print(std::string(bytes, n)); - }, [this](const char *bytes, size_t n) { - async_print(std::string(bytes, n), true); - }); - else - process = std::make_unique<TinyProcessLib::Process>(command, path.string()); - - if (process->get_id() <= 0) { - async_print("Error: failed to run command: " + command + "\n", true); - return -1; - } - - return process->get_exit_status(); + std::unique_ptr<TinyProcessLib::Process> process; + if (use_pipes) + process = std::make_unique<TinyProcessLib::Process>(command, path.string(), + [this](const char *bytes, size_t n) { + async_print(std::string(bytes, n)); + }, [this](const char *bytes, size_t n) { + async_print(std::string(bytes, n), true); + }); + else + process = std::make_unique<TinyProcessLib::Process>(command, path.string()); + + if (process->get_id() <= 0) { + async_print("Error: failed to run command: " + command + "\n", true); + return -1; + } + + return process->get_exit_status(); } int Terminal::process(std::istream &stdin_stream, std::ostream &stdout_stream, const std::string &command, const boost::filesystem::path &path, std::ostream *stderr_stream) { - TinyProcessLib::Process process(command, path.string(), [&stdout_stream](const char *bytes, size_t n) { - Glib::ustring umessage(std::string(bytes, n)); - Glib::ustring::iterator iter; - while (!umessage.validate(iter)) { - auto next_char_iter = iter; - next_char_iter++; - umessage.replace(iter, next_char_iter, "?"); - } - stdout_stream.write(umessage.data(), n); - }, [this, stderr_stream](const char *bytes, size_t n) { - if (stderr_stream) - stderr_stream->write(bytes, n); - else - async_print(std::string(bytes, n), true); - }, true); - - if (process.get_id() <= 0) { - async_print("Error: failed to run command: " + command + "\n", true); - return -1; + TinyProcessLib::Process process(command, path.string(), [&stdout_stream](const char *bytes, size_t n) { + Glib::ustring umessage(std::string(bytes, n)); + Glib::ustring::iterator iter; + while (!umessage.validate(iter)) { + auto next_char_iter = iter; + next_char_iter++; + umessage.replace(iter, next_char_iter, "?"); } - - char buffer[131072]; - for (;;) { - stdin_stream.readsome(buffer, 131072); - auto read_n = stdin_stream.gcount(); - if (read_n == 0) - break; - if (!process.write(buffer, read_n)) { - break; - } + stdout_stream.write(umessage.data(), n); + }, [this, stderr_stream](const char *bytes, size_t n) { + if (stderr_stream) + stderr_stream->write(bytes, n); + else + async_print(std::string(bytes, n), true); + }, true); + + if (process.get_id() <= 0) { + async_print("Error: failed to run command: " + command + "\n", true); + return -1; + } + + char buffer[131072]; + for (;;) { + stdin_stream.readsome(buffer, 131072); + auto read_n = stdin_stream.gcount(); + if (read_n == 0) + break; + if (!process.write(buffer, read_n)) { + break; } - process.close_stdin(); + } + process.close_stdin(); - return process.get_exit_status(); + return process.get_exit_status(); } void Terminal::async_process(const std::string &command, const boost::filesystem::path &path, std::function<void(int exit_status)> callback, bool quiet) { - std::thread async_execute_thread([this, command, path, callback, quiet]() { - std::unique_lock<std::mutex> processes_lock(processes_mutex); - stdin_buffer.clear(); - auto process = std::make_shared<TinyProcessLib::Process>(command, path.string(), - [this, quiet](const char *bytes, size_t n) { - if (!quiet) - async_print(std::string(bytes, n)); - }, [this, quiet](const char *bytes, size_t n) { - if (!quiet) - async_print(std::string(bytes, n), true); - }, true); - auto pid = process->get_id(); - if (pid <= 0) { - processes_lock.unlock(); - async_print("Error: failed to run command: " + command + "\n", true); - if (callback) - callback(-1); - return; - } else { - processes.emplace_back(process); - processes_lock.unlock(); - } + std::thread async_execute_thread([this, command, path, callback, quiet]() { + std::unique_lock<std::mutex> processes_lock(processes_mutex); + stdin_buffer.clear(); + auto process = std::make_shared<TinyProcessLib::Process>(command, path.string(), + [this, quiet](const char *bytes, size_t n) { + if (!quiet) + async_print(std::string(bytes, n)); + }, [this, quiet](const char *bytes, size_t n) { + if (!quiet) + async_print(std::string(bytes, n), true); + }, true); + auto pid = process->get_id(); + if (pid <= 0) { + processes_lock.unlock(); + async_print("Error: failed to run command: " + command + "\n", true); + if (callback) + callback(-1); + return; + } else { + processes.emplace_back(process); + processes_lock.unlock(); + } - auto exit_status = process->get_exit_status(); + auto exit_status = process->get_exit_status(); - processes_lock.lock(); - for (auto it = processes.begin(); it != processes.end(); it++) { - if ((*it)->get_id() == pid) { - processes.erase(it); - break; - } - } - stdin_buffer.clear(); - processes_lock.unlock(); + processes_lock.lock(); + for (auto it = processes.begin(); it != processes.end(); it++) { + if ((*it)->get_id() == pid) { + processes.erase(it); + break; + } + } + stdin_buffer.clear(); + processes_lock.unlock(); - if (callback) - callback(exit_status); - }); - async_execute_thread.detach(); + if (callback) + callback(exit_status); + }); + async_execute_thread.detach(); } void Terminal::kill_last_async_process(bool force) { - std::unique_lock<std::mutex> lock(processes_mutex); - if (processes.empty()) - Info::get().print("No running processes"); - else - processes.back()->kill(force); + std::unique_lock<std::mutex> lock(processes_mutex); + if (processes.empty()) + Info::get().print("No running processes"); + else + processes.back()->kill(force); } void Terminal::kill_async_processes(bool force) { - std::unique_lock<std::mutex> lock(processes_mutex); - for (auto &process: processes) - process->kill(force); + std::unique_lock<std::mutex> lock(processes_mutex); + for (auto &process: processes) + process->kill(force); } bool Terminal::on_motion_notify_event(GdkEventMotion *event) { - Gtk::TextIter iter; - int location_x, location_y; - window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, event->x, event->y, location_x, location_y); - get_iter_at_location(iter, location_x, location_y); - if (iter.has_tag(link_tag)) - get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->set_cursor(link_mouse_cursor); - else - get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->set_cursor(default_mouse_cursor); - - // Workaround for drag-and-drop crash on MacOS - // TODO 2018: check if this bug has been fixed + Gtk::TextIter iter; + int location_x, location_y; + window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, event->x, event->y, location_x, location_y); + get_iter_at_location(iter, location_x, location_y); + if (iter.has_tag(link_tag)) + get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->set_cursor(link_mouse_cursor); + else + get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->set_cursor(default_mouse_cursor); + + // Workaround for drag-and-drop crash on MacOS + // TODO 2018: check if this bug has been fixed #ifdef __APPLE__ - if((event->state & GDK_BUTTON1_MASK) == 0) - return Gtk::TextView::on_motion_notify_event(event); - else { - int x, y; - window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, event->x, event->y, x, y); - Gtk::TextIter iter; - get_iter_at_location(iter, x, y); - get_buffer()->select_range(get_buffer()->get_insert()->get_iter(), iter); - return true; - } -#else + if((event->state & GDK_BUTTON1_MASK) == 0) return Gtk::TextView::on_motion_notify_event(event); + else { + int x, y; + window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, event->x, event->y, x, y); + Gtk::TextIter iter; + get_iter_at_location(iter, x, y); + get_buffer()->select_range(get_buffer()->get_insert()->get_iter(), iter); + return true; + } +#else + return Gtk::TextView::on_motion_notify_event(event); #endif - return Gtk::TextView::on_motion_notify_event(event); + return Gtk::TextView::on_motion_notify_event(event); } std::tuple<size_t, size_t, std::string, std::string, std::string> Terminal::find_link(const std::string &line) { - const static std::regex link_regex("^([A-Z]:)?([^:]+):([0-9]+):([0-9]+): .*$|" //compile warning/error/rename usages - "^Assertion failed: .*file ([A-Z]:)?([^:]+), line ([0-9]+)\\.$|" //clang assert() - "^[^:]*: ([A-Z]:)?([^:]+):([0-9]+): .* Assertion .* failed\\.$|" //gcc assert() - "^ERROR:([A-Z]:)?([^:]+):([0-9]+):.*$"); //g_assert (glib.h) - size_t start_position = -1, end_position = -1; - std::string path, line_number, line_offset; - std::smatch sm; - if (std::regex_match(line, sm, link_regex)) { - for (size_t sub = 1; sub < link_regex.mark_count();) { - size_t subs = sub == 1 ? 4 : 3; - if (sm.length(sub + 1)) { - start_position = sm.position(sub + 1) - sm.length(sub); - end_position = sm.position(sub + subs - 1) + sm.length(sub + subs - 1); - if (sm.length(sub)) - path += sm[sub].str(); - path += sm[sub + 1].str(); - line_number = sm[sub + 2].str(); - line_offset = subs == 4 ? sm[sub + 3].str() : "1"; - break; - } - sub += subs; - } + const static std::regex link_regex("^([A-Z]:)?([^:]+):([0-9]+):([0-9]+): .*$|" //compile warning/error/rename usages + "^Assertion failed: .*file ([A-Z]:)?([^:]+), line ([0-9]+)\\.$|" //clang assert() + "^[^:]*: ([A-Z]:)?([^:]+):([0-9]+): .* Assertion .* failed\\.$|" //gcc assert() + "^ERROR:([A-Z]:)?([^:]+):([0-9]+):.*$"); //g_assert (glib.h) + size_t start_position = -1, end_position = -1; + std::string path, line_number, line_offset; + std::smatch sm; + if (std::regex_match(line, sm, link_regex)) { + for (size_t sub = 1; sub < link_regex.mark_count();) { + size_t subs = sub == 1 ? 4 : 3; + if (sm.length(sub + 1)) { + start_position = sm.position(sub + 1) - sm.length(sub); + end_position = sm.position(sub + subs - 1) + sm.length(sub + subs - 1); + if (sm.length(sub)) + path += sm[sub].str(); + path += sm[sub + 1].str(); + line_number = sm[sub + 2].str(); + line_offset = subs == 4 ? sm[sub + 3].str() : "1"; + break; + } + sub += subs; } - return std::make_tuple(start_position, end_position, path, line_number, line_offset); + } + return std::make_tuple(start_position, end_position, path, line_number, line_offset); } void Terminal::apply_link_tags(Gtk::TextIter start_iter, Gtk::TextIter end_iter) { - auto iter = start_iter; - Gtk::TextIter line_start; - bool line_start_set = false; - bool delimiter_found = false; - bool dot_found = false; - bool number_found = false; - do { - if (iter.starts_line()) { - line_start = iter; - line_start_set = true; - delimiter_found = false; - dot_found = false; - number_found = false; - } - if (line_start_set && (*iter == '\\' || *iter == '/')) - delimiter_found = true; - else if (line_start_set && *iter == '.') - dot_found = true; - else if (line_start_set && (*iter >= '0' && *iter <= '9')) - number_found = true; - else if (line_start_set && delimiter_found && dot_found && number_found && iter.ends_line()) { - auto line = get_buffer()->get_text(line_start, iter); - //Convert to ascii for std::regex and Gtk::Iter::forward_chars - for (size_t c = 0; c < line.size(); ++c) { - if (line[c] > 127) - line.replace(c, 1, "a"); - } - auto link = find_link(line.raw()); - if (std::get<0>(link) != static_cast<size_t>(-1)) { - auto link_start = line_start; - auto link_end = line_start; - link_start.forward_chars(std::get<0>(link)); - link_end.forward_chars(std::get<1>(link)); - get_buffer()->apply_tag(link_tag, link_start, link_end); - } - line_start_set = false; - } - } while (iter.forward_char() && iter != end_iter); + auto iter = start_iter; + Gtk::TextIter line_start; + bool line_start_set = false; + bool delimiter_found = false; + bool dot_found = false; + bool number_found = false; + do { + if (iter.starts_line()) { + line_start = iter; + line_start_set = true; + delimiter_found = false; + dot_found = false; + number_found = false; + } + if (line_start_set && (*iter == '\\' || *iter == '/')) + delimiter_found = true; + else if (line_start_set && *iter == '.') + dot_found = true; + else if (line_start_set && (*iter >= '0' && *iter <= '9')) + number_found = true; + else if (line_start_set && delimiter_found && dot_found && number_found && iter.ends_line()) { + auto line = get_buffer()->get_text(line_start, iter); + //Convert to ascii for std::regex and Gtk::Iter::forward_chars + for (size_t c = 0; c < line.size(); ++c) { + if (line[c] > 127) + line.replace(c, 1, "a"); + } + auto link = find_link(line.raw()); + if (std::get<0>(link) != static_cast<size_t>(-1)) { + auto link_start = line_start; + auto link_end = line_start; + link_start.forward_chars(std::get<0>(link)); + link_end.forward_chars(std::get<1>(link)); + get_buffer()->apply_tag(link_tag, link_start, link_end); + } + line_start_set = false; + } + } while (iter.forward_char() && iter != end_iter); } size_t Terminal::print(const std::string &message, bool bold) { #ifdef _WIN32 - //Remove color codes - auto message_no_color = message; //copy here since operations on Glib::ustring is too slow - size_t pos = 0; - while ((pos = message_no_color.find('\e', pos)) != std::string::npos) { - if ((pos + 2) >= message_no_color.size()) - break; - if (message_no_color[pos + 1] == '[') { - size_t end_pos = pos + 2; - bool color_code_found = false; - while (end_pos < message_no_color.size()) { - if ((message_no_color[end_pos] >= '0' && message_no_color[end_pos] <= '9') || - message_no_color[end_pos] == ';') - end_pos++; - else if (message_no_color[end_pos] == 'm') { - color_code_found = true; - break; - } else - break; - } - if (color_code_found) - message_no_color.erase(pos, end_pos - pos + 1); - } + //Remove color codes + auto message_no_color = message; //copy here since operations on Glib::ustring is too slow + size_t pos = 0; + while ((pos = message_no_color.find('\e', pos)) != std::string::npos) { + if ((pos + 2) >= message_no_color.size()) + break; + if (message_no_color[pos + 1] == '[') { + size_t end_pos = pos + 2; + bool color_code_found = false; + while (end_pos < message_no_color.size()) { + if ((message_no_color[end_pos] >= '0' && message_no_color[end_pos] <= '9') || + message_no_color[end_pos] == ';') + end_pos++; + else if (message_no_color[end_pos] == 'm') { + color_code_found = true; + break; + } else + break; + } + if (color_code_found) + message_no_color.erase(pos, end_pos - pos + 1); } - Glib::ustring umessage = message_no_color; + } + Glib::ustring umessage = message_no_color; #else - Glib::ustring umessage=message; + Glib::ustring umessage=message; #endif - Glib::ustring::iterator iter; - while (!umessage.validate(iter)) { - auto next_char_iter = iter; - next_char_iter++; - umessage.replace(iter, next_char_iter, "?"); - } - - auto start_mark = get_buffer()->create_mark(get_buffer()->get_iter_at_line(get_buffer()->end().get_line())); - if (bold) - get_buffer()->insert_with_tag(get_buffer()->end(), umessage, bold_tag); - else - get_buffer()->insert(get_buffer()->end(), umessage); - auto start_iter = start_mark->get_iter(); - get_buffer()->delete_mark(start_mark); - auto end_iter = get_buffer()->get_insert()->get_iter(); - - apply_link_tags(start_iter, end_iter); - - if (get_buffer()->get_line_count() > Config::get().terminal.history_size) { - int lines = get_buffer()->get_line_count() - Config::get().terminal.history_size; - get_buffer()->erase(get_buffer()->begin(), get_buffer()->get_iter_at_line(lines)); - deleted_lines += static_cast<size_t>(lines); - } - - return static_cast<size_t>(get_buffer()->end().get_line()) + deleted_lines; + Glib::ustring::iterator iter; + while (!umessage.validate(iter)) { + auto next_char_iter = iter; + next_char_iter++; + umessage.replace(iter, next_char_iter, "?"); + } + + auto start_mark = get_buffer()->create_mark(get_buffer()->get_iter_at_line(get_buffer()->end().get_line())); + if (bold) + get_buffer()->insert_with_tag(get_buffer()->end(), umessage, bold_tag); + else + get_buffer()->insert(get_buffer()->end(), umessage); + auto start_iter = start_mark->get_iter(); + get_buffer()->delete_mark(start_mark); + auto end_iter = get_buffer()->get_insert()->get_iter(); + + apply_link_tags(start_iter, end_iter); + + if (get_buffer()->get_line_count() > Config::get().terminal.history_size) { + int lines = get_buffer()->get_line_count() - Config::get().terminal.history_size; + get_buffer()->erase(get_buffer()->begin(), get_buffer()->get_iter_at_line(lines)); + deleted_lines += static_cast<size_t>(lines); + } + + return static_cast<size_t>(get_buffer()->end().get_line()) + deleted_lines; } void Terminal::async_print(const std::string &message, bool bold) { - dispatcher.post([message, bold] { - Terminal::get().print(message, bold); - }); + dispatcher.post([message, bold] { + Terminal::get().print(message, bold); + }); } void Terminal::async_print(size_t line_nr, const std::string &message) { - dispatcher.post([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, "?"); - } + dispatcher.post([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); - }); + 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); + }); } void Terminal::configure() { - link_tag->property_foreground_rgba() = get_style_context()->get_color(Gtk::StateFlags::STATE_FLAG_LINK); - - if (Config::get().terminal.font.size() > 0) { - override_font(Pango::FontDescription(Config::get().terminal.font)); - } else if (Config::get().source.font.size() > 0) { - Pango::FontDescription font_description(Config::get().source.font); - auto font_description_size = font_description.get_size(); - if (font_description_size == 0) { - Pango::FontDescription default_font_description(Gtk::Settings::get_default()->property_gtk_font_name()); - font_description_size = default_font_description.get_size(); - } - if (font_description_size > 0) - font_description.set_size(font_description_size * 0.95); - override_font(font_description); + link_tag->property_foreground_rgba() = get_style_context()->get_color(Gtk::StateFlags::STATE_FLAG_LINK); + + if (Config::get().terminal.font.size() > 0) { + override_font(Pango::FontDescription(Config::get().terminal.font)); + } else if (Config::get().source.font.size() > 0) { + Pango::FontDescription font_description(Config::get().source.font); + auto font_description_size = font_description.get_size(); + if (font_description_size == 0) { + Pango::FontDescription default_font_description(Gtk::Settings::get_default()->property_gtk_font_name()); + font_description_size = default_font_description.get_size(); } + if (font_description_size > 0) + font_description.set_size(font_description_size * 0.95); + override_font(font_description); + } } void Terminal::clear() { - get_buffer()->set_text(""); + get_buffer()->set_text(""); } bool Terminal::on_button_press_event(GdkEventButton *button_event) { - //open clicked link in terminal - if (button_event->type == GDK_BUTTON_PRESS && button_event->button == GDK_BUTTON_PRIMARY) { - Gtk::TextIter iter; - int location_x, location_y; - window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, button_event->x, button_event->y, location_x, - location_y); - get_iter_at_location(iter, location_x, location_y); - if (iter.has_tag(link_tag)) { - auto start_iter = get_buffer()->get_iter_at_line(iter.get_line()); - auto end_iter = start_iter; - while (!end_iter.ends_line() && end_iter.forward_char()) {} - auto link = find_link(get_buffer()->get_text(start_iter, end_iter).raw()); - if (std::get<0>(link) != static_cast<size_t>(-1)) { - boost::filesystem::path path = std::get<2>(link); - std::string line = std::get<3>(link); - std::string index = std::get<4>(link); - - if (!path.empty() && *path.begin() == "~") { // boost::filesystem does not recognize ~ - boost::filesystem::path corrected_path; - corrected_path = filesystem::get_home_path(); - if (!corrected_path.empty()) { - auto it = path.begin(); - ++it; - for (; it != path.end(); ++it) - corrected_path /= *it; - path = corrected_path; - } - } - - if (path.is_relative()) { - if (Project::current) { - auto absolute_path = Project::current->build->get_default_path() / path; - if (boost::filesystem::exists(absolute_path)) - path = absolute_path; - else - path = Project::current->build->get_debug_path() / path; - } else - return Gtk::TextView::on_button_press_event(button_event); - } - if (boost::filesystem::is_regular_file(path)) { - Notebook::get().open(path); - if (auto view = Notebook::get().get_current_view()) { - try { - int line_int = std::stoi(line) - 1; - int index_int = std::stoi(index) - 1; - view->place_cursor_at_line_index(line_int, index_int); - view->scroll_to_cursor_delayed(view, true, true); - return true; - } - catch (...) {} - } - } + //open clicked link in terminal + if (button_event->type == GDK_BUTTON_PRESS && button_event->button == GDK_BUTTON_PRIMARY) { + Gtk::TextIter iter; + int location_x, location_y; + window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, button_event->x, button_event->y, location_x, + location_y); + get_iter_at_location(iter, location_x, location_y); + if (iter.has_tag(link_tag)) { + auto start_iter = get_buffer()->get_iter_at_line(iter.get_line()); + auto end_iter = start_iter; + while (!end_iter.ends_line() && end_iter.forward_char()) {} + auto link = find_link(get_buffer()->get_text(start_iter, end_iter).raw()); + if (std::get<0>(link) != static_cast<size_t>(-1)) { + boost::filesystem::path path = std::get<2>(link); + std::string line = std::get<3>(link); + std::string index = std::get<4>(link); + + if (!path.empty() && *path.begin() == "~") { // boost::filesystem does not recognize ~ + boost::filesystem::path corrected_path; + corrected_path = filesystem::get_home_path(); + if (!corrected_path.empty()) { + auto it = path.begin(); + ++it; + for (; it != path.end(); ++it) + corrected_path /= *it; + path = corrected_path; + } + } + + if (path.is_relative()) { + if (Project::current) { + auto absolute_path = Project::current->build->get_default_path() / path; + if (boost::filesystem::exists(absolute_path)) + path = absolute_path; + else + path = Project::current->build->get_debug_path() / path; + } else + return Gtk::TextView::on_button_press_event(button_event); + } + if (boost::filesystem::is_regular_file(path)) { + Notebook::get().open(path); + if (auto view = Notebook::get().get_current_view()) { + try { + int line_int = std::stoi(line) - 1; + int index_int = std::stoi(index) - 1; + view->place_cursor_at_line_index(line_int, index_int); + view->scroll_to_cursor_delayed(view, true, true); + return true; } + catch (...) {} + } } + } } - return Gtk::TextView::on_button_press_event(button_event); + } + return Gtk::TextView::on_button_press_event(button_event); } bool Terminal::on_key_press_event(GdkEventKey *event) { - std::unique_lock<std::mutex> lock(processes_mutex); - bool debug_is_running = false; + std::unique_lock<std::mutex> lock(processes_mutex); + bool debug_is_running = false; #ifdef JUCI_ENABLE_DEBUG - debug_is_running=Project::current?Project::current->debug_is_running():false; + debug_is_running=Project::current?Project::current->debug_is_running():false; #endif - if (processes.size() > 0 || debug_is_running) { - auto unicode = gdk_keyval_to_unicode(event->keyval); - if (unicode >= 32 && unicode != 126 && unicode != 0) { - get_buffer()->place_cursor(get_buffer()->end()); - stdin_buffer += unicode; - get_buffer()->insert_at_cursor(stdin_buffer.substr(stdin_buffer.size() - 1)); - } else if (event->keyval == GDK_KEY_BackSpace) { - get_buffer()->place_cursor(get_buffer()->end()); - if (stdin_buffer.size() > 0 && get_buffer()->get_char_count() > 0) { - auto iter = get_buffer()->end(); - iter--; - stdin_buffer.erase(stdin_buffer.size() - 1); - get_buffer()->erase(iter, get_buffer()->end()); - } - } else if (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) { - get_buffer()->place_cursor(get_buffer()->end()); - stdin_buffer += '\n'; - if (debug_is_running) { + if (processes.size() > 0 || debug_is_running) { + auto unicode = gdk_keyval_to_unicode(event->keyval); + if (unicode >= 32 && unicode != 126 && unicode != 0) { + get_buffer()->place_cursor(get_buffer()->end()); + stdin_buffer += unicode; + get_buffer()->insert_at_cursor(stdin_buffer.substr(stdin_buffer.size() - 1)); + } else if (event->keyval == GDK_KEY_BackSpace) { + get_buffer()->place_cursor(get_buffer()->end()); + if (stdin_buffer.size() > 0 && get_buffer()->get_char_count() > 0) { + auto iter = get_buffer()->end(); + iter--; + stdin_buffer.erase(stdin_buffer.size() - 1); + get_buffer()->erase(iter, get_buffer()->end()); + } + } else if (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) { + get_buffer()->place_cursor(get_buffer()->end()); + stdin_buffer += '\n'; + if (debug_is_running) { #ifdef JUCI_ENABLE_DEBUG - Project::current->debug_write(stdin_buffer); + Project::current->debug_write(stdin_buffer); #endif - } else - processes.back()->write(stdin_buffer); - get_buffer()->insert_at_cursor(stdin_buffer.substr(stdin_buffer.size() - 1)); - stdin_buffer.clear(); - } + } else + processes.back()->write(stdin_buffer); + get_buffer()->insert_at_cursor(stdin_buffer.substr(stdin_buffer.size() - 1)); + stdin_buffer.clear(); } - return true; + } + return true; } diff --git a/src/terminal.h b/src/terminal.h index 88eb03df..a93118c8 100644 --- a/src/terminal.h +++ b/src/terminal.h @@ -10,56 +10,56 @@ #include <tuple> class Terminal : public Gtk::TextView { - Terminal(); + Terminal(); public: - static Terminal &get() { - static Terminal singleton; - return singleton; - } + static Terminal &get() { + static Terminal singleton; + return singleton; + } - int process(const std::string &command, const boost::filesystem::path &path = "", bool use_pipes = true); + int process(const std::string &command, const boost::filesystem::path &path = "", bool use_pipes = true); - int process(std::istream &stdin_stream, std::ostream &stdout_stream, const std::string &command, - const boost::filesystem::path &path = "", std::ostream *stderr_stream = nullptr); + int process(std::istream &stdin_stream, std::ostream &stdout_stream, const std::string &command, + const boost::filesystem::path &path = "", std::ostream *stderr_stream = nullptr); - void async_process(const std::string &command, const boost::filesystem::path &path = "", - std::function<void(int exit_status)> callback = nullptr, bool quiet = false); + void async_process(const std::string &command, const boost::filesystem::path &path = "", + std::function<void(int exit_status)> callback = nullptr, bool quiet = false); - void kill_last_async_process(bool force = false); + void kill_last_async_process(bool force = false); - void kill_async_processes(bool force = false); + void kill_async_processes(bool force = false); - size_t print(const std::string &message, bool bold = false); + size_t print(const std::string &message, bool bold = false); - void async_print(const std::string &message, bool bold = false); + void async_print(const std::string &message, bool bold = false); - void async_print(size_t line_nr, const std::string &message); + void async_print(size_t line_nr, const std::string &message); - void configure(); + void configure(); - void clear(); + void clear(); protected: - bool on_motion_notify_event(GdkEventMotion *motion_event) override; + bool on_motion_notify_event(GdkEventMotion *motion_event) override; - bool on_button_press_event(GdkEventButton *button_event) override; + bool on_button_press_event(GdkEventButton *button_event) override; - bool on_key_press_event(GdkEventKey *event) override; + bool on_key_press_event(GdkEventKey *event) override; private: - Dispatcher dispatcher; - Glib::RefPtr<Gtk::TextTag> bold_tag; - Glib::RefPtr<Gtk::TextTag> link_tag; - Glib::RefPtr<Gdk::Cursor> link_mouse_cursor; - Glib::RefPtr<Gdk::Cursor> default_mouse_cursor; - size_t deleted_lines = 0; + Dispatcher dispatcher; + Glib::RefPtr<Gtk::TextTag> bold_tag; + Glib::RefPtr<Gtk::TextTag> link_tag; + Glib::RefPtr<Gdk::Cursor> link_mouse_cursor; + Glib::RefPtr<Gdk::Cursor> default_mouse_cursor; + size_t deleted_lines = 0; - std::tuple<size_t, size_t, std::string, std::string, std::string> find_link(const std::string &line); + std::tuple<size_t, size_t, std::string, std::string, std::string> find_link(const std::string &line); - void apply_link_tags(Gtk::TextIter start_iter, Gtk::TextIter end_iter); + void apply_link_tags(Gtk::TextIter start_iter, Gtk::TextIter end_iter); - std::vector<std::shared_ptr<TinyProcessLib::Process>> processes; - std::mutex processes_mutex; - Glib::ustring stdin_buffer; + std::vector<std::shared_ptr<TinyProcessLib::Process>> processes; + std::mutex processes_mutex; + Glib::ustring stdin_buffer; }; diff --git a/src/tooltips.cc b/src/tooltips.cc index 22437cba..69e30e51 100644 --- a/src/tooltips.cc +++ b/src/tooltips.cc @@ -6,249 +6,249 @@ Gdk::Rectangle Tooltips::drawn_tooltips_rectangle = Gdk::Rectangle(); Tooltip::Tooltip(std::function<Glib::RefPtr<Gtk::TextBuffer>()> create_tooltip_buffer, Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, Glib::RefPtr<Gtk::TextBuffer::Mark> end_mark) - : start_mark(start_mark), end_mark(end_mark), create_tooltip_buffer(create_tooltip_buffer), - text_view(text_view) {} + : start_mark(start_mark), end_mark(end_mark), create_tooltip_buffer(create_tooltip_buffer), + text_view(text_view) {} Tooltip::~Tooltip() { - Tooltips::shown_tooltips.erase(this); - if (text_view) { - text_view->get_buffer()->delete_mark(start_mark); - text_view->get_buffer()->delete_mark(end_mark); - } + Tooltips::shown_tooltips.erase(this); + if (text_view) { + text_view->get_buffer()->delete_mark(start_mark); + text_view->get_buffer()->delete_mark(end_mark); + } } void Tooltip::update() { - if (text_view) { - auto iter = start_mark->get_iter(); - auto end_iter = end_mark->get_iter(); - text_view->get_iter_location(iter, activation_rectangle); - if (iter.get_offset() < end_iter.get_offset()) { - while (iter.forward_char() && iter != end_iter) { - Gdk::Rectangle rectangle; - text_view->get_iter_location(iter, rectangle); - activation_rectangle.join(rectangle); - } - } - int location_window_x, location_window_y; - text_view->buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, activation_rectangle.get_x(), - activation_rectangle.get_y(), location_window_x, location_window_y); - activation_rectangle.set_x(location_window_x); - activation_rectangle.set_y(location_window_y); + if (text_view) { + auto iter = start_mark->get_iter(); + auto end_iter = end_mark->get_iter(); + text_view->get_iter_location(iter, activation_rectangle); + if (iter.get_offset() < end_iter.get_offset()) { + while (iter.forward_char() && iter != end_iter) { + Gdk::Rectangle rectangle; + text_view->get_iter_location(iter, rectangle); + activation_rectangle.join(rectangle); + } } + int location_window_x, location_window_y; + text_view->buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, activation_rectangle.get_x(), + activation_rectangle.get_y(), location_window_x, location_window_y); + activation_rectangle.set_x(location_window_x); + activation_rectangle.set_y(location_window_y); + } } void Tooltip::show(bool disregard_drawn, const std::function<void()> &on_motion) { - Tooltips::shown_tooltips.emplace(this); + Tooltips::shown_tooltips.emplace(this); - if (!window) { - //init window - window = std::make_unique<Gtk::Window>(Gtk::WindowType::WINDOW_POPUP); + if (!window) { + //init window + window = std::make_unique<Gtk::Window>(Gtk::WindowType::WINDOW_POPUP); - auto g_application = g_application_get_default(); - auto gio_application = Glib::wrap(g_application, true); - auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application); - window->set_transient_for(*application->get_active_window()); + auto g_application = g_application_get_default(); + auto gio_application = Glib::wrap(g_application, true); + auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application); + window->set_transient_for(*application->get_active_window()); - window->set_type_hint(Gdk::WindowTypeHint::WINDOW_TYPE_HINT_TOOLTIP); + window->set_type_hint(Gdk::WindowTypeHint::WINDOW_TYPE_HINT_TOOLTIP); - window->set_events(Gdk::POINTER_MOTION_MASK); - window->property_decorated() = false; - window->set_accept_focus(false); - window->set_skip_taskbar_hint(true); - window->set_default_size(0, 0); + window->set_events(Gdk::POINTER_MOTION_MASK); + window->property_decorated() = false; + window->set_accept_focus(false); + window->set_skip_taskbar_hint(true); + window->set_default_size(0, 0); - window->signal_motion_notify_event().connect([on_motion](GdkEventMotion *event) { - if (on_motion) - on_motion(); - return false; - }); + window->signal_motion_notify_event().connect([on_motion](GdkEventMotion *event) { + if (on_motion) + on_motion(); + return false; + }); - window->get_style_context()->add_class("juci_tooltip_window"); - auto visual = window->get_screen()->get_rgba_visual(); - if (visual) - gtk_widget_set_visual(reinterpret_cast<GtkWidget *>(window->gobj()), visual->gobj()); + window->get_style_context()->add_class("juci_tooltip_window"); + auto visual = window->get_screen()->get_rgba_visual(); + if (visual) + gtk_widget_set_visual(reinterpret_cast<GtkWidget *>(window->gobj()), visual->gobj()); - auto box = Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL)); - box->get_style_context()->add_class("juci_tooltip_box"); - window->add(*box); + auto box = Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL)); + box->get_style_context()->add_class("juci_tooltip_box"); + window->add(*box); - text_buffer = create_tooltip_buffer(); - wrap_lines(); + text_buffer = create_tooltip_buffer(); + wrap_lines(); - auto tooltip_text_view = Gtk::manage(new Gtk::TextView(text_buffer)); - tooltip_text_view->get_style_context()->add_class("juci_tooltip_text_view"); - tooltip_text_view->set_editable(false); + auto tooltip_text_view = Gtk::manage(new Gtk::TextView(text_buffer)); + tooltip_text_view->get_style_context()->add_class("juci_tooltip_text_view"); + tooltip_text_view->set_editable(false); #if GTK_VERSION_GE(3, 20) - box->add(*tooltip_text_view); + box->add(*tooltip_text_view); #else - auto box2=Gtk::manage(new Gtk::Box()); - box2->pack_start(*tooltip_text_view, true, true, 3); - box->pack_start(*box2, true, true, 3); + auto box2=Gtk::manage(new Gtk::Box()); + box2->pack_start(*tooltip_text_view, true, true, 3); + box->pack_start(*box2, true, true, 3); #endif - auto layout = Pango::Layout::create(tooltip_text_view->get_pango_context()); - layout->set_text(text_buffer->get_text()); - layout->get_pixel_size(size.first, size.second); - size.first += 6; // 2xpadding - size.second += 8; // 2xpadding + 2 - - window->signal_realize().connect([this] { - if (!text_view) { - auto &dialog = SelectionDialog::get(); - if (dialog && dialog->is_visible()) { - int root_x, root_y; - dialog->get_position(root_x, root_y); - root_x -= 3; // -1xpadding - rectangle.set_x(root_x); - rectangle.set_y(root_y - size.second); - if (rectangle.get_y() < 0) - rectangle.set_y(0); - } - } - window->move(rectangle.get_x(), rectangle.get_y()); - }); - } - - int root_x = 0, root_y = 0; - if (text_view) { - //Adjust if tooltip is left of text_view - Gdk::Rectangle visible_rect; - text_view->get_visible_rect(visible_rect); - int visible_x, visible_y; - text_view->buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, visible_rect.get_x(), - visible_rect.get_y(), visible_x, visible_y); - auto activation_rectangle_x = std::max(activation_rectangle.get_x(), visible_x); - - text_view->get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->get_root_coords(activation_rectangle_x, - activation_rectangle.get_y(), - root_x, root_y); - root_x -= 3; // -1xpadding - if (root_y < size.second) - root_x += visible_rect.get_width() * 0.1; - } - rectangle.set_x(root_x); - rectangle.set_y(std::max(0, root_y - size.second)); - rectangle.set_width(size.first); - rectangle.set_height(size.second); - - if (!disregard_drawn) { - if (Tooltips::drawn_tooltips_rectangle.get_width() != 0) { - if (rectangle.intersects(Tooltips::drawn_tooltips_rectangle)) { - int new_y = Tooltips::drawn_tooltips_rectangle.get_y() - size.second; - if (new_y >= 0) - rectangle.set_y(new_y); - else - rectangle.set_x(Tooltips::drawn_tooltips_rectangle.get_x() + - Tooltips::drawn_tooltips_rectangle.get_width() + 2); - } - Tooltips::drawn_tooltips_rectangle.join(rectangle); - } else - Tooltips::drawn_tooltips_rectangle = rectangle; - } - - if (window->get_realized()) - window->move(rectangle.get_x(), rectangle.get_y()); - window->show_all(); - shown = true; + auto layout = Pango::Layout::create(tooltip_text_view->get_pango_context()); + layout->set_text(text_buffer->get_text()); + layout->get_pixel_size(size.first, size.second); + size.first += 6; // 2xpadding + size.second += 8; // 2xpadding + 2 + + window->signal_realize().connect([this] { + if (!text_view) { + auto &dialog = SelectionDialog::get(); + if (dialog && dialog->is_visible()) { + int root_x, root_y; + dialog->get_position(root_x, root_y); + root_x -= 3; // -1xpadding + rectangle.set_x(root_x); + rectangle.set_y(root_y - size.second); + if (rectangle.get_y() < 0) + rectangle.set_y(0); + } + } + window->move(rectangle.get_x(), rectangle.get_y()); + }); + } + + int root_x = 0, root_y = 0; + if (text_view) { + //Adjust if tooltip is left of text_view + Gdk::Rectangle visible_rect; + text_view->get_visible_rect(visible_rect); + int visible_x, visible_y; + text_view->buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, visible_rect.get_x(), + visible_rect.get_y(), visible_x, visible_y); + auto activation_rectangle_x = std::max(activation_rectangle.get_x(), visible_x); + + text_view->get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->get_root_coords(activation_rectangle_x, + activation_rectangle.get_y(), + root_x, root_y); + root_x -= 3; // -1xpadding + if (root_y < size.second) + root_x += visible_rect.get_width() * 0.1; + } + rectangle.set_x(root_x); + rectangle.set_y(std::max(0, root_y - size.second)); + rectangle.set_width(size.first); + rectangle.set_height(size.second); + + if (!disregard_drawn) { + if (Tooltips::drawn_tooltips_rectangle.get_width() != 0) { + if (rectangle.intersects(Tooltips::drawn_tooltips_rectangle)) { + int new_y = Tooltips::drawn_tooltips_rectangle.get_y() - size.second; + if (new_y >= 0) + rectangle.set_y(new_y); + else + rectangle.set_x(Tooltips::drawn_tooltips_rectangle.get_x() + + Tooltips::drawn_tooltips_rectangle.get_width() + 2); + } + Tooltips::drawn_tooltips_rectangle.join(rectangle); + } else + Tooltips::drawn_tooltips_rectangle = rectangle; + } + + if (window->get_realized()) + window->move(rectangle.get_x(), rectangle.get_y()); + window->show_all(); + shown = true; } void Tooltip::hide(const std::pair<int, int> &last_mouse_pos, const std::pair<int, int> &mouse_pos) { - // Keep tooltip if mouse is moving towards it - // Calculated using dot product between the mouse_pos vector and the corners of the tooltip window - if (text_view && window && shown && last_mouse_pos.first != -1 && last_mouse_pos.second != -1 && - mouse_pos.first != -1 && mouse_pos.second != -1) { - static int root_x, root_y; - text_view->get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->get_root_coords(last_mouse_pos.first, - last_mouse_pos.second, root_x, - root_y); - int diff_x = mouse_pos.first - last_mouse_pos.first; - int diff_y = mouse_pos.second - last_mouse_pos.second; - class Corner { - public: - Corner(int x, int y) : x(x - root_x), y(y - root_y) {} - - int x, y; - }; - std::vector<Corner> corners; - corners.emplace_back(rectangle.get_x(), rectangle.get_y()); - corners.emplace_back(rectangle.get_x() + rectangle.get_width(), rectangle.get_y()); - corners.emplace_back(rectangle.get_x(), rectangle.get_y() + rectangle.get_height()); - corners.emplace_back(rectangle.get_x() + rectangle.get_width(), rectangle.get_y() + rectangle.get_height()); - for (auto &corner: corners) { - if (diff_x * corner.x + diff_y * corner.y >= 0) - return; - } + // Keep tooltip if mouse is moving towards it + // Calculated using dot product between the mouse_pos vector and the corners of the tooltip window + if (text_view && window && shown && last_mouse_pos.first != -1 && last_mouse_pos.second != -1 && + mouse_pos.first != -1 && mouse_pos.second != -1) { + static int root_x, root_y; + text_view->get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->get_root_coords(last_mouse_pos.first, + last_mouse_pos.second, root_x, + root_y); + int diff_x = mouse_pos.first - last_mouse_pos.first; + int diff_y = mouse_pos.second - last_mouse_pos.second; + class Corner { + public: + Corner(int x, int y) : x(x - root_x), y(y - root_y) {} + + int x, y; + }; + std::vector<Corner> corners; + corners.emplace_back(rectangle.get_x(), rectangle.get_y()); + corners.emplace_back(rectangle.get_x() + rectangle.get_width(), rectangle.get_y()); + corners.emplace_back(rectangle.get_x(), rectangle.get_y() + rectangle.get_height()); + corners.emplace_back(rectangle.get_x() + rectangle.get_width(), rectangle.get_y() + rectangle.get_height()); + for (auto &corner: corners) { + if (diff_x * corner.x + diff_y * corner.y >= 0) + return; } - Tooltips::shown_tooltips.erase(this); - if (window) - window->hide(); - shown = false; + } + Tooltips::shown_tooltips.erase(this); + if (window) + window->hide(); + shown = false; } void Tooltip::wrap_lines() { - if (!text_buffer) - return; - - auto iter = text_buffer->begin(); - - while (iter) { - auto last_space = text_buffer->end(); - bool end = false; - for (unsigned c = 0; c <= 80; c++) { - if (!iter) { - end = true; - break; - } - if (*iter == ' ') - last_space = iter; - if (*iter == '\n') { - end = true; - iter.forward_char(); - break; - } - iter.forward_char(); - } - if (!end) { - while (!last_space && iter) { //If no space (word longer than 80) - iter.forward_char(); - if (iter && *iter == ' ') - last_space = iter; - } - if (iter && last_space) { - auto mark = text_buffer->create_mark(last_space); - auto last_space_p = last_space; - last_space.forward_char(); - text_buffer->erase(last_space_p, last_space); - text_buffer->insert(mark->get_iter(), "\n"); - - iter = mark->get_iter(); - iter.forward_char(); - - text_buffer->delete_mark(mark); - } - } + if (!text_buffer) + return; + + auto iter = text_buffer->begin(); + + while (iter) { + auto last_space = text_buffer->end(); + bool end = false; + for (unsigned c = 0; c <= 80; c++) { + if (!iter) { + end = true; + break; + } + if (*iter == ' ') + last_space = iter; + if (*iter == '\n') { + end = true; + iter.forward_char(); + break; + } + iter.forward_char(); } + if (!end) { + while (!last_space && iter) { //If no space (word longer than 80) + iter.forward_char(); + if (iter && *iter == ' ') + last_space = iter; + } + if (iter && last_space) { + auto mark = text_buffer->create_mark(last_space); + auto last_space_p = last_space; + last_space.forward_char(); + text_buffer->erase(last_space_p, last_space); + text_buffer->insert(mark->get_iter(), "\n"); + + iter = mark->get_iter(); + iter.forward_char(); + + text_buffer->delete_mark(mark); + } + } + } } void Tooltips::show(const Gdk::Rectangle &rectangle, bool disregard_drawn) { - for (auto &tooltip : tooltip_list) { - tooltip.update(); - if (rectangle.intersects(tooltip.activation_rectangle)) - tooltip.show(disregard_drawn, on_motion); - else - tooltip.hide(); - } + for (auto &tooltip : tooltip_list) { + tooltip.update(); + if (rectangle.intersects(tooltip.activation_rectangle)) + tooltip.show(disregard_drawn, on_motion); + else + tooltip.hide(); + } } void Tooltips::show(bool disregard_drawn) { - for (auto &tooltip : tooltip_list) { - tooltip.update(); - tooltip.show(disregard_drawn, on_motion); - } + for (auto &tooltip : tooltip_list) { + tooltip.update(); + tooltip.show(disregard_drawn, on_motion); + } } void Tooltips::hide(const std::pair<int, int> &last_mouse_pos, const std::pair<int, int> &mouse_pos) { - for (auto &tooltip : tooltip_list) - tooltip.hide(last_mouse_pos, mouse_pos); + for (auto &tooltip : tooltip_list) + tooltip.hide(last_mouse_pos, mouse_pos); } diff --git a/src/tooltips.h b/src/tooltips.h index 2cca4554..2b9ac105 100644 --- a/src/tooltips.h +++ b/src/tooltips.h @@ -8,62 +8,62 @@ class Tooltip { public: - Tooltip(std::function<Glib::RefPtr<Gtk::TextBuffer>()> create_tooltip_buffer, Gtk::TextView *text_view, - Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, Glib::RefPtr<Gtk::TextBuffer::Mark> end_mark); + Tooltip(std::function<Glib::RefPtr<Gtk::TextBuffer>()> create_tooltip_buffer, Gtk::TextView *text_view, + Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark, Glib::RefPtr<Gtk::TextBuffer::Mark> end_mark); - Tooltip(std::function<Glib::RefPtr<Gtk::TextBuffer>()> create_tooltip_buffer) : Tooltip(create_tooltip_buffer, - nullptr, - Glib::RefPtr<Gtk::TextBuffer::Mark>(), - Glib::RefPtr<Gtk::TextBuffer::Mark>()) {} + Tooltip(std::function<Glib::RefPtr<Gtk::TextBuffer>()> create_tooltip_buffer) : Tooltip(create_tooltip_buffer, + nullptr, + Glib::RefPtr<Gtk::TextBuffer::Mark>(), + Glib::RefPtr<Gtk::TextBuffer::Mark>()) {} - ~Tooltip(); + ~Tooltip(); - void update(); + void update(); - void show(bool disregard_drawn = false, const std::function<void()> &on_motion = nullptr); + void show(bool disregard_drawn = false, const std::function<void()> &on_motion = nullptr); - void hide(const std::pair<int, int> &last_mouse_pos = {-1, -1}, const std::pair<int, int> &mouse_pos = {-1, -1}); + void hide(const std::pair<int, int> &last_mouse_pos = {-1, -1}, const std::pair<int, int> &mouse_pos = {-1, -1}); - Gdk::Rectangle activation_rectangle; - Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark; - Glib::RefPtr<Gtk::TextBuffer::Mark> end_mark; + Gdk::Rectangle activation_rectangle; + Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark; + Glib::RefPtr<Gtk::TextBuffer::Mark> end_mark; - Glib::RefPtr<Gtk::TextBuffer> text_buffer; + Glib::RefPtr<Gtk::TextBuffer> text_buffer; private: - std::unique_ptr<Gtk::Window> window; + std::unique_ptr<Gtk::Window> window; - void wrap_lines(); + void wrap_lines(); - std::function<Glib::RefPtr<Gtk::TextBuffer>()> create_tooltip_buffer; - Gtk::TextView *text_view; - std::pair<int, int> size; - Gdk::Rectangle rectangle; + std::function<Glib::RefPtr<Gtk::TextBuffer>()> create_tooltip_buffer; + Gtk::TextView *text_view; + std::pair<int, int> size; + Gdk::Rectangle rectangle; - bool shown = false; + bool shown = false; }; class Tooltips { public: - static std::set<Tooltip *> shown_tooltips; - static Gdk::Rectangle drawn_tooltips_rectangle; + static std::set<Tooltip *> shown_tooltips; + static Gdk::Rectangle drawn_tooltips_rectangle; - static void init() { drawn_tooltips_rectangle = Gdk::Rectangle(); } + static void init() { drawn_tooltips_rectangle = Gdk::Rectangle(); } - void show(const Gdk::Rectangle &rectangle, bool disregard_drawn = false); + void show(const Gdk::Rectangle &rectangle, bool disregard_drawn = false); - void show(bool disregard_drawn = false); + void show(bool disregard_drawn = false); - void hide(const std::pair<int, int> &last_mouse_pos = {-1, -1}, const std::pair<int, int> &mouse_pos = {-1, -1}); + void hide(const std::pair<int, int> &last_mouse_pos = {-1, -1}, const std::pair<int, int> &mouse_pos = {-1, -1}); - void clear() { tooltip_list.clear(); }; + void clear() { tooltip_list.clear(); }; - template<typename... Ts> - void emplace_back(Ts &&... params) { - tooltip_list.emplace_back(std::forward<Ts>(params)...); - } + template<typename... Ts> + void emplace_back(Ts &&... params) { + tooltip_list.emplace_back(std::forward<Ts>(params)...); + } - std::function<void()> on_motion; + std::function<void()> on_motion; private: - std::list<Tooltip> tooltip_list; + std::list<Tooltip> tooltip_list; }; diff --git a/src/usages_clang.cc b/src/usages_clang.cc index 71719539..22c53190 100644 --- a/src/usages_clang.cc +++ b/src/usages_clang.cc @@ -13,7 +13,7 @@ #include <windows.h> DWORD get_current_process_id() { - return GetCurrentProcessId(); + return GetCurrentProcessId(); } #else @@ -29,100 +29,100 @@ std::mutex Usages::Clang::caches_mutex; std::atomic<size_t> Usages::Clang::cache_in_progress_count(0); bool Usages::Clang::Cache::Cursor::operator==(const Cursor &o) { - for (auto &usr : usrs) { - if (clangmm::Cursor::is_similar_kind(o.kind, kind) && o.usrs.count(usr)) - return true; - } - return false; + for (auto &usr : usrs) { + if (clangmm::Cursor::is_similar_kind(o.kind, kind) && o.usrs.count(usr)) + return true; + } + return false; } Usages::Clang::Cache::Cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path, std::time_t before_parse_time, clangmm::TranslationUnit *translation_unit, clangmm::Tokens *clang_tokens) - : project_path(project_path), build_path(build_path) { - for (auto &clang_token : *clang_tokens) { - tokens.emplace_back(Token{clang_token.get_spelling(), clang_token.get_source_range().get_offsets(), - static_cast<size_t>(-1)}); - - if (clang_token.is_identifier()) { - auto clang_cursor = clang_token.get_cursor().get_referenced(); - if (clang_cursor) { - Cursor cursor{clang_cursor.get_kind(), clang_cursor.get_all_usr_extended()}; - for (size_t c = 0; c < cursors.size(); ++c) { - if (cursor == cursors[c]) { - tokens.back().cursor_id = c; - break; - } - } - if (tokens.back().cursor_id == static_cast<size_t>(-1)) { - cursors.emplace_back(cursor); - tokens.back().cursor_id = cursors.size() - 1; - } - } + : project_path(project_path), build_path(build_path) { + for (auto &clang_token : *clang_tokens) { + tokens.emplace_back(Token{clang_token.get_spelling(), clang_token.get_source_range().get_offsets(), + static_cast<size_t>(-1)}); + + if (clang_token.is_identifier()) { + auto clang_cursor = clang_token.get_cursor().get_referenced(); + if (clang_cursor) { + Cursor cursor{clang_cursor.get_kind(), clang_cursor.get_all_usr_extended()}; + for (size_t c = 0; c < cursors.size(); ++c) { + if (cursor == cursors[c]) { + tokens.back().cursor_id = c; + break; + } + } + if (tokens.back().cursor_id == static_cast<size_t>(-1)) { + cursors.emplace_back(cursor); + tokens.back().cursor_id = cursors.size() - 1; } + } } - boost::system::error_code ec; - auto last_write_time = boost::filesystem::last_write_time(path, ec); - if (ec) - last_write_time = 0; - if (last_write_time > before_parse_time) - last_write_time = 0; - paths_and_last_write_times.emplace(path, last_write_time); - - class VisitorData { - public: - const boost::filesystem::path &project_path; - const boost::filesystem::path &path; - std::time_t before_parse_time; - std::map<boost::filesystem::path, std::time_t> &paths_and_last_write_times; - }; - VisitorData visitor_data{project_path, path, before_parse_time, paths_and_last_write_times}; - - clang_getInclusions(translation_unit->cx_tu, - [](CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, - CXClientData data) { - auto visitor_data = static_cast<VisitorData *>(data); - auto path = filesystem::get_normal_path( - clangmm::to_string(clang_getFileName(included_file))); - if (filesystem::file_in_path(path, visitor_data->project_path)) { - for (unsigned c = 0; c < include_len; ++c) { - auto from_path = filesystem::get_normal_path( - clangmm::SourceLocation(inclusion_stack[c]).get_path()); - if (from_path == visitor_data->path) { - boost::system::error_code ec; - auto last_write_time = boost::filesystem::last_write_time(path, ec); - if (ec) - last_write_time = 0; - if (last_write_time > visitor_data->before_parse_time) - last_write_time = 0; - visitor_data->paths_and_last_write_times.emplace(path, last_write_time); - break; - } - } + } + boost::system::error_code ec; + auto last_write_time = boost::filesystem::last_write_time(path, ec); + if (ec) + last_write_time = 0; + if (last_write_time > before_parse_time) + last_write_time = 0; + paths_and_last_write_times.emplace(path, last_write_time); + + class VisitorData { + public: + const boost::filesystem::path &project_path; + const boost::filesystem::path &path; + std::time_t before_parse_time; + std::map<boost::filesystem::path, std::time_t> &paths_and_last_write_times; + }; + VisitorData visitor_data{project_path, path, before_parse_time, paths_and_last_write_times}; + + clang_getInclusions(translation_unit->cx_tu, + [](CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, + CXClientData data) { + auto visitor_data = static_cast<VisitorData *>(data); + auto path = filesystem::get_normal_path( + clangmm::to_string(clang_getFileName(included_file))); + if (filesystem::file_in_path(path, visitor_data->project_path)) { + for (unsigned c = 0; c < include_len; ++c) { + auto from_path = filesystem::get_normal_path( + clangmm::SourceLocation(inclusion_stack[c]).get_path()); + if (from_path == visitor_data->path) { + boost::system::error_code ec; + auto last_write_time = boost::filesystem::last_write_time(path, ec); + if (ec) + last_write_time = 0; + if (last_write_time > visitor_data->before_parse_time) + last_write_time = 0; + visitor_data->paths_and_last_write_times.emplace(path, last_write_time); + break; } - }, - &visitor_data); + } + } + }, + &visitor_data); } std::vector<std::pair<clangmm::Offset, clangmm::Offset>> Usages::Clang::Cache::get_similar_token_offsets(clangmm::Cursor::Kind kind, const std::string &spelling, const std::unordered_set<std::string> &usrs) const { - std::vector<std::pair<clangmm::Offset, clangmm::Offset>> offsets; - for (auto &token : tokens) { - if (token.cursor_id != static_cast<size_t>(-1)) { - auto &cursor = cursors[token.cursor_id]; - if (clangmm::Cursor::is_similar_kind(cursor.kind, kind) && token.spelling == spelling) { - for (auto &usr : cursor.usrs) { - if (usrs.count(usr)) { - offsets.emplace_back(token.offsets); - break; - } - } - } + std::vector<std::pair<clangmm::Offset, clangmm::Offset>> offsets; + for (auto &token : tokens) { + if (token.cursor_id != static_cast<size_t>(-1)) { + auto &cursor = cursors[token.cursor_id]; + if (clangmm::Cursor::is_similar_kind(cursor.kind, kind) && token.spelling == spelling) { + for (auto &usr : cursor.usrs) { + if (usrs.count(usr)) { + offsets.emplace_back(token.offsets); + break; + } } + } } - return offsets; + } + return offsets; } std::vector<Usages::Clang::Usages> @@ -130,296 +130,296 @@ Usages::Clang::get_usages(const boost::filesystem::path &project_path, const boo const boost::filesystem::path &debug_path, const std::string &spelling, const clangmm::Cursor &cursor, const std::vector<clangmm::TranslationUnit *> &translation_units) { - std::vector<Usages> usages; + std::vector<Usages> usages; - if (spelling.empty()) - return usages; + if (spelling.empty()) + return usages; - PathSet visited; + PathSet visited; - auto usr_extended = cursor.get_usr_extended(); - if (!usr_extended.empty() && usr_extended[0] >= '0' && - usr_extended[0] <= '9') { //if declared within a function, return - if (!translation_units.empty()) - add_usages(project_path, build_path, boost::filesystem::path(), usages, visited, spelling, cursor, - translation_units.front(), false); - return usages; - } + auto usr_extended = cursor.get_usr_extended(); + if (!usr_extended.empty() && usr_extended[0] >= '0' && + usr_extended[0] <= '9') { //if declared within a function, return + if (!translation_units.empty()) + add_usages(project_path, build_path, boost::filesystem::path(), usages, visited, spelling, cursor, + translation_units.front(), false); + return usages; + } - for (auto &translation_unit : translation_units) - add_usages(project_path, build_path, boost::filesystem::path(), usages, visited, spelling, cursor, - translation_unit, false); - - for (auto &translation_unit : translation_units) - add_usages_from_includes(project_path, build_path, usages, visited, spelling, cursor, translation_unit, false); - - if (project_path.empty()) - return usages; - - auto paths = find_paths(project_path, build_path, debug_path); - auto pair = parse_paths(spelling, paths); - PathSet all_cursors_paths; - auto canonical = cursor.get_canonical(); - all_cursors_paths.emplace(canonical.get_source_location().get_path()); - for (auto &cursor: canonical.get_all_overridden_cursors()) - all_cursors_paths.emplace(cursor.get_source_location().get_path()); - auto pair2 = find_potential_paths(all_cursors_paths, project_path, pair.first, pair.second); - auto &potential_paths = pair2.first; - auto &all_includes = pair2.second; - - // Remove visited paths - for (auto it = potential_paths.begin(); it != potential_paths.end();) { - if (visited.find(*it) != visited.end()) - it = potential_paths.erase(it); - else - ++it; - } + for (auto &translation_unit : translation_units) + add_usages(project_path, build_path, boost::filesystem::path(), usages, visited, spelling, cursor, + translation_unit, false); - // Wait for current caching to finish - std::unique_ptr<Dialog::Message> message; - const std::string message_string = "Please wait while finding usages"; - if (cache_in_progress_count != 0) { - message = std::make_unique<Dialog::Message>(message_string); - while (cache_in_progress_count != 0) { - while (Gtk::Main::events_pending()) - Gtk::Main::iteration(false); - } - } + for (auto &translation_unit : translation_units) + add_usages_from_includes(project_path, build_path, usages, visited, spelling, cursor, translation_unit, false); - // Use cache - for (auto it = potential_paths.begin(); it != potential_paths.end();) { - std::unique_lock<std::mutex> lock(caches_mutex); - auto caches_it = caches.find(*it); - - // Load cache from file if not found in memory and if cache file exists - if (caches_it == caches.end()) { - auto cache = read_cache(project_path, build_path, *it); - if (cache) { - auto pair = caches.emplace(*it, std::move(cache)); - caches_it = pair.first; - } - } + if (project_path.empty()) + return usages; - if (caches_it != caches.end()) { - if (add_usages_from_cache(caches_it->first, usages, visited, spelling, cursor, caches_it->second)) - it = potential_paths.erase(it); - else { - caches.erase(caches_it); - ++it; - } - } else - ++it; + auto paths = find_paths(project_path, build_path, debug_path); + auto pair = parse_paths(spelling, paths); + PathSet all_cursors_paths; + auto canonical = cursor.get_canonical(); + all_cursors_paths.emplace(canonical.get_source_location().get_path()); + for (auto &cursor: canonical.get_all_overridden_cursors()) + all_cursors_paths.emplace(cursor.get_source_location().get_path()); + auto pair2 = find_potential_paths(all_cursors_paths, project_path, pair.first, pair.second); + auto &potential_paths = pair2.first; + auto &all_includes = pair2.second; + + // Remove visited paths + for (auto it = potential_paths.begin(); it != potential_paths.end();) { + if (visited.find(*it) != visited.end()) + it = potential_paths.erase(it); + else + ++it; + } + + // Wait for current caching to finish + std::unique_ptr<Dialog::Message> message; + const std::string message_string = "Please wait while finding usages"; + if (cache_in_progress_count != 0) { + message = std::make_unique<Dialog::Message>(message_string); + while (cache_in_progress_count != 0) { + while (Gtk::Main::events_pending()) + Gtk::Main::iteration(false); } + } - // Remove paths that has been included - for (auto it = potential_paths.begin(); it != potential_paths.end();) { - if (all_includes.find(*it) != all_includes.end()) - it = potential_paths.erase(it); - else - ++it; + // Use cache + for (auto it = potential_paths.begin(); it != potential_paths.end();) { + std::unique_lock<std::mutex> lock(caches_mutex); + auto caches_it = caches.find(*it); + + // Load cache from file if not found in memory and if cache file exists + if (caches_it == caches.end()) { + auto cache = read_cache(project_path, build_path, *it); + if (cache) { + auto pair = caches.emplace(*it, std::move(cache)); + caches_it = pair.first; + } } - // Parse potential paths - if (!potential_paths.empty()) { - if (!message) - message = std::make_unique<Dialog::Message>(message_string); - - std::vector<std::thread> threads; - auto it = potential_paths.begin(); - auto number_of_threads = Config::get().source.clang_usages_threads; - if (number_of_threads == static_cast<unsigned>(-1)) { - number_of_threads = std::thread::hardware_concurrency(); - if (number_of_threads == 0) - number_of_threads = 1; - } - for (unsigned thread_id = 0; thread_id < number_of_threads; ++thread_id) { - threads.emplace_back([&potential_paths, &it, &build_path, - &project_path, &usages, &visited, &spelling, &cursor] { - while (true) { - boost::filesystem::path path; - { - static std::mutex mutex; - std::unique_lock<std::mutex> lock(mutex); - if (it == potential_paths.end()) - return; - path = *it; - ++it; - } - clangmm::Index index(0, 0); - - { - static std::mutex mutex; - std::unique_lock<std::mutex> lock(mutex); - // std::cout << "parsing: " << path << std::endl; - } - // auto before_time = std::chrono::system_clock::now(); - - std::ifstream stream(path.string(), std::ifstream::binary); - std::string buffer; - buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>()); - - auto arguments = CompileCommands::get_arguments(build_path, path); - arguments.emplace_back("-w"); // Disable all warnings - for (auto it = arguments.begin(); it != arguments.end();) { // remove comments from system headers - if (*it == "-fretain-comments-from-system-headers") - it = arguments.erase(it); - else - ++it; - } - int flags = CXTranslationUnit_Incomplete; + if (caches_it != caches.end()) { + if (add_usages_from_cache(caches_it->first, usages, visited, spelling, cursor, caches_it->second)) + it = potential_paths.erase(it); + else { + caches.erase(caches_it); + ++it; + } + } else + ++it; + } + + // Remove paths that has been included + for (auto it = potential_paths.begin(); it != potential_paths.end();) { + if (all_includes.find(*it) != all_includes.end()) + it = potential_paths.erase(it); + else + ++it; + } + + // Parse potential paths + if (!potential_paths.empty()) { + if (!message) + message = std::make_unique<Dialog::Message>(message_string); + + std::vector<std::thread> threads; + auto it = potential_paths.begin(); + auto number_of_threads = Config::get().source.clang_usages_threads; + if (number_of_threads == static_cast<unsigned>(-1)) { + number_of_threads = std::thread::hardware_concurrency(); + if (number_of_threads == 0) + number_of_threads = 1; + } + for (unsigned thread_id = 0; thread_id < number_of_threads; ++thread_id) { + threads.emplace_back([&potential_paths, &it, &build_path, + &project_path, &usages, &visited, &spelling, &cursor] { + while (true) { + boost::filesystem::path path; + { + static std::mutex mutex; + std::unique_lock<std::mutex> lock(mutex); + if (it == potential_paths.end()) + return; + path = *it; + ++it; + } + clangmm::Index index(0, 0); + + { + static std::mutex mutex; + std::unique_lock<std::mutex> lock(mutex); + // std::cout << "parsing: " << path << std::endl; + } + // auto before_time = std::chrono::system_clock::now(); + + std::ifstream stream(path.string(), std::ifstream::binary); + std::string buffer; + buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>()); + + auto arguments = CompileCommands::get_arguments(build_path, path); + arguments.emplace_back("-w"); // Disable all warnings + for (auto it = arguments.begin(); it != arguments.end();) { // remove comments from system headers + if (*it == "-fretain-comments-from-system-headers") + it = arguments.erase(it); + else + ++it; + } + int flags = CXTranslationUnit_Incomplete; #if CINDEX_VERSION_MAJOR > 0 || (CINDEX_VERSION_MAJOR == 0 && CINDEX_VERSION_MINOR >= 35) - flags |= CXTranslationUnit_KeepGoing; + flags |= CXTranslationUnit_KeepGoing; #endif - clangmm::TranslationUnit translation_unit(index, path.string(), arguments, buffer, flags); - - { - static std::mutex mutex; - std::unique_lock<std::mutex> lock(mutex); - add_usages(project_path, build_path, path, usages, visited, spelling, cursor, &translation_unit, - true); - add_usages_from_includes(project_path, build_path, usages, visited, spelling, cursor, - &translation_unit, true); - } - - // auto time = std::chrono::system_clock::now(); - // std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(time - before_time).count() << std::endl; - } - }); + clangmm::TranslationUnit translation_unit(index, path.string(), arguments, buffer, flags); + + { + static std::mutex mutex; + std::unique_lock<std::mutex> lock(mutex); + add_usages(project_path, build_path, path, usages, visited, spelling, cursor, &translation_unit, + true); + add_usages_from_includes(project_path, build_path, usages, visited, spelling, cursor, + &translation_unit, true); + } + + // auto time = std::chrono::system_clock::now(); + // std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(time - before_time).count() << std::endl; } - for (auto &thread : threads) - thread.join(); + }); } + for (auto &thread : threads) + thread.join(); + } - if (message) - message->hide(); + if (message) + message->hide(); - return usages; + return usages; } void Usages::Clang::cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path, std::time_t before_parse_time, const PathSet &project_paths_in_use, clangmm::TranslationUnit *translation_unit, clangmm::Tokens *tokens) { - class ScopeExit { - public: - std::function<void()> f; + class ScopeExit { + public: + std::function<void()> f; - ~ScopeExit() { - f(); - } - }; - ScopeExit scope_exit{[] { - --cache_in_progress_count; - }}; - - if (project_path.empty()) - return; - - { - std::unique_lock<std::mutex> lock(caches_mutex); - if (project_paths_in_use.count(project_path)) { - caches.erase(path); - caches.emplace(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens)); - } else - write_cache(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens)); + ~ScopeExit() { + f(); } + }; + ScopeExit scope_exit{[] { + --cache_in_progress_count; + }}; - class VisitorData { - public: - const boost::filesystem::path &project_path; - PathSet paths; - }; - VisitorData visitor_data{project_path, {}}; + if (project_path.empty()) + return; - auto translation_unit_cursor = clang_getTranslationUnitCursor(translation_unit->cx_tu); - clang_visitChildren(translation_unit_cursor, [](CXCursor cx_cursor, CXCursor cx_parent, CXClientData data) { - auto visitor_data = static_cast<VisitorData *>(data); + { + std::unique_lock<std::mutex> lock(caches_mutex); + if (project_paths_in_use.count(project_path)) { + caches.erase(path); + caches.emplace(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens)); + } else + write_cache(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens)); + } + + class VisitorData { + public: + const boost::filesystem::path &project_path; + PathSet paths; + }; + VisitorData visitor_data{project_path, {}}; - auto path = filesystem::get_normal_path(clangmm::Cursor(cx_cursor).get_source_location().get_path()); - if (filesystem::file_in_path(path, visitor_data->project_path)) - visitor_data->paths.emplace(path); + auto translation_unit_cursor = clang_getTranslationUnitCursor(translation_unit->cx_tu); + clang_visitChildren(translation_unit_cursor, [](CXCursor cx_cursor, CXCursor cx_parent, CXClientData data) { + auto visitor_data = static_cast<VisitorData *>(data); - return CXChildVisit_Continue; - }, - &visitor_data); + auto path = filesystem::get_normal_path(clangmm::Cursor(cx_cursor).get_source_location().get_path()); + if (filesystem::file_in_path(path, visitor_data->project_path)) + visitor_data->paths.emplace(path); - visitor_data.paths.erase(path); + return CXChildVisit_Continue; + }, + &visitor_data); - for (auto &path : visitor_data.paths) { - boost::system::error_code ec; - auto file_size = boost::filesystem::file_size(path, ec); - if (file_size == static_cast<boost::uintmax_t>(-1) || ec) - continue; - auto tokens = translation_unit->get_tokens(path.string(), 0, file_size - 1); - std::unique_lock<std::mutex> lock(caches_mutex); - if (project_paths_in_use.count(project_path)) { - caches.erase(path); - caches.emplace(path, - Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens.get())); - } else - write_cache(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens.get())); - } + visitor_data.paths.erase(path); + + for (auto &path : visitor_data.paths) { + boost::system::error_code ec; + auto file_size = boost::filesystem::file_size(path, ec); + if (file_size == static_cast<boost::uintmax_t>(-1) || ec) + continue; + auto tokens = translation_unit->get_tokens(path.string(), 0, file_size - 1); + std::unique_lock<std::mutex> lock(caches_mutex); + if (project_paths_in_use.count(project_path)) { + caches.erase(path); + caches.emplace(path, + Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens.get())); + } else + write_cache(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens.get())); + } } void Usages::Clang::erase_unused_caches(const PathSet &project_paths_in_use) { - std::unique_lock<std::mutex> lock(caches_mutex); - for (auto it = caches.begin(); it != caches.end();) { - bool found = false; - for (auto &project_path : project_paths_in_use) { - if (filesystem::file_in_path(it->first, project_path)) { - found = true; - break; - } - } - if (!found) { - write_cache(it->first, it->second); - it = caches.erase(it); - } else - ++it; + std::unique_lock<std::mutex> lock(caches_mutex); + for (auto it = caches.begin(); it != caches.end();) { + bool found = false; + for (auto &project_path : project_paths_in_use) { + if (filesystem::file_in_path(it->first, project_path)) { + found = true; + break; + } } + if (!found) { + write_cache(it->first, it->second); + it = caches.erase(it); + } else + ++it; + } } void Usages::Clang::erase_cache(const boost::filesystem::path &path) { - std::unique_lock<std::mutex> lock(caches_mutex); + std::unique_lock<std::mutex> lock(caches_mutex); - auto it = caches.find(path); - if (it == caches.end()) - return; + auto it = caches.find(path); + if (it == caches.end()) + return; - auto paths_and_last_write_times = std::move(it->second.paths_and_last_write_times); - for (auto &path_and_last_write_time : paths_and_last_write_times) - caches.erase(path_and_last_write_time.first); + auto paths_and_last_write_times = std::move(it->second.paths_and_last_write_times); + for (auto &path_and_last_write_time : paths_and_last_write_times) + caches.erase(path_and_last_write_time.first); } void Usages::Clang::erase_all_caches_for_project(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path) { - if (project_path.empty()) - return; - - if (cache_in_progress_count != 0) - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - - std::unique_lock<std::mutex> lock(caches_mutex); - boost::system::error_code ec; - auto usages_clang_path = build_path / cache_folder; - if (boost::filesystem::exists(usages_clang_path, ec) && boost::filesystem::is_directory(usages_clang_path, ec)) { - for (boost::filesystem::directory_iterator it(usages_clang_path), end; it != end; ++it) { - if (it->path().extension() == ".usages") - boost::filesystem::remove(it->path(), ec); - } + if (project_path.empty()) + return; + + if (cache_in_progress_count != 0) + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + std::unique_lock<std::mutex> lock(caches_mutex); + boost::system::error_code ec; + auto usages_clang_path = build_path / cache_folder; + if (boost::filesystem::exists(usages_clang_path, ec) && boost::filesystem::is_directory(usages_clang_path, ec)) { + for (boost::filesystem::directory_iterator it(usages_clang_path), end; it != end; ++it) { + if (it->path().extension() == ".usages") + boost::filesystem::remove(it->path(), ec); } + } - for (auto it = caches.begin(); it != caches.end();) { - if (filesystem::file_in_path(it->first, project_path)) - it = caches.erase(it); - else - ++it; - } + for (auto it = caches.begin(); it != caches.end();) { + if (filesystem::file_in_path(it->first, project_path)) + it = caches.erase(it); + else + ++it; + } } void Usages::Clang::cache_in_progress() { - ++cache_in_progress_count; + ++cache_in_progress_count; } void Usages::Clang::add_usages(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, @@ -427,86 +427,86 @@ void Usages::Clang::add_usages(const boost::filesystem::path &project_path, cons std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, clangmm::Cursor cursor, clangmm::TranslationUnit *translation_unit, bool store_in_cache) { - std::unique_ptr<clangmm::Tokens> tokens; - boost::filesystem::path path; - auto before_parse_time = std::time(nullptr); - auto all_usr_extended = cursor.get_all_usr_extended(); - if (path_.empty()) { - path = clangmm::to_string(clang_getTranslationUnitSpelling(translation_unit->cx_tu)); - if (visited.find(path) != visited.end() || !filesystem::file_in_path(path, project_path)) - return; - tokens = translation_unit->get_tokens(); - } else { - path = path_; - if (visited.find(path) != visited.end() || !filesystem::file_in_path(path, project_path)) - return; - boost::system::error_code ec; - auto file_size = boost::filesystem::file_size(path, ec); - if (file_size == static_cast<boost::uintmax_t>(-1) || ec) - return; - tokens = translation_unit->get_tokens(path.string(), 0, file_size - 1); - } - - auto offsets = tokens->get_similar_token_offsets(cursor.get_kind(), spelling, all_usr_extended); - std::vector<std::string> lines; - for (auto &offset : offsets) { - std::string line; - auto line_nr = offset.second.line; - for (auto &token : *tokens) { - auto offset = token.get_source_location().get_offset(); - if (offset.line == line_nr) { - while (line.size() < offset.index - 1) - line += ' '; - line += token.get_spelling(); - } - } - lines.emplace_back(std::move(line)); + std::unique_ptr<clangmm::Tokens> tokens; + boost::filesystem::path path; + auto before_parse_time = std::time(nullptr); + auto all_usr_extended = cursor.get_all_usr_extended(); + if (path_.empty()) { + path = clangmm::to_string(clang_getTranslationUnitSpelling(translation_unit->cx_tu)); + if (visited.find(path) != visited.end() || !filesystem::file_in_path(path, project_path)) + return; + tokens = translation_unit->get_tokens(); + } else { + path = path_; + if (visited.find(path) != visited.end() || !filesystem::file_in_path(path, project_path)) + return; + boost::system::error_code ec; + auto file_size = boost::filesystem::file_size(path, ec); + if (file_size == static_cast<boost::uintmax_t>(-1) || ec) + return; + tokens = translation_unit->get_tokens(path.string(), 0, file_size - 1); + } + + auto offsets = tokens->get_similar_token_offsets(cursor.get_kind(), spelling, all_usr_extended); + std::vector<std::string> lines; + for (auto &offset : offsets) { + std::string line; + auto line_nr = offset.second.line; + for (auto &token : *tokens) { + auto offset = token.get_source_location().get_offset(); + if (offset.line == line_nr) { + while (line.size() < offset.index - 1) + line += ' '; + line += token.get_spelling(); + } } + lines.emplace_back(std::move(line)); + } - if (store_in_cache && filesystem::file_in_path(path, project_path)) { - std::unique_lock<std::mutex> lock(caches_mutex); - caches.erase(path); - caches.emplace(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens.get())); - } + if (store_in_cache && filesystem::file_in_path(path, project_path)) { + std::unique_lock<std::mutex> lock(caches_mutex); + caches.erase(path); + caches.emplace(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens.get())); + } - visited.emplace(path); - if (!offsets.empty()) - usages.emplace_back(Usages{std::move(path), std::move(offsets), lines}); + visited.emplace(path); + if (!offsets.empty()) + usages.emplace_back(Usages{std::move(path), std::move(offsets), lines}); } bool Usages::Clang::add_usages_from_cache(const boost::filesystem::path &path, std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, const clangmm::Cursor &cursor, const Cache &cache) { - for (auto &path_and_last_write_time : cache.paths_and_last_write_times) { - boost::system::error_code ec; - auto last_write_time = boost::filesystem::last_write_time(path_and_last_write_time.first, ec); - if (ec || last_write_time != path_and_last_write_time.second) { - // std::cout << "updated file: " << path_and_last_write_time.first << ", included from " << path << std::endl; - return false; - } + for (auto &path_and_last_write_time : cache.paths_and_last_write_times) { + boost::system::error_code ec; + auto last_write_time = boost::filesystem::last_write_time(path_and_last_write_time.first, ec); + if (ec || last_write_time != path_and_last_write_time.second) { + // std::cout << "updated file: " << path_and_last_write_time.first << ", included from " << path << std::endl; + return false; } - - auto offsets = cache.get_similar_token_offsets(cursor.get_kind(), spelling, cursor.get_all_usr_extended()); - - std::vector<std::string> lines; - for (auto &offset : offsets) { - std::string line; - auto line_nr = offset.second.line; - for (auto &token : cache.tokens) { - auto &offset = token.offsets.first; - if (offset.line == line_nr) { - while (line.size() < offset.index - 1) - line += ' '; - line += token.spelling; - } - } - lines.emplace_back(std::move(line)); + } + + auto offsets = cache.get_similar_token_offsets(cursor.get_kind(), spelling, cursor.get_all_usr_extended()); + + std::vector<std::string> lines; + for (auto &offset : offsets) { + std::string line; + auto line_nr = offset.second.line; + for (auto &token : cache.tokens) { + auto &offset = token.offsets.first; + if (offset.line == line_nr) { + while (line.size() < offset.index - 1) + line += ' '; + line += token.spelling; + } } + lines.emplace_back(std::move(line)); + } - visited.emplace(path); - if (!offsets.empty()) - usages.emplace_back(Usages{path, std::move(offsets), lines}); - return true; + visited.emplace(path); + if (!offsets.empty()) + usages.emplace_back(Usages{path, std::move(offsets), lines}); + return true; } void Usages::Clang::add_usages_from_includes(const boost::filesystem::path &project_path, @@ -514,268 +514,268 @@ void Usages::Clang::add_usages_from_includes(const boost::filesystem::path &proj std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, const clangmm::Cursor &cursor, clangmm::TranslationUnit *translation_unit, bool store_in_cache) { - if (project_path.empty()) - return; - - class VisitorData { - public: - const boost::filesystem::path &project_path; - const std::string &spelling; - PathSet &visited; - PathSet paths; - }; - VisitorData visitor_data{project_path, spelling, visited, {}}; - - auto translation_unit_cursor = clang_getTranslationUnitCursor(translation_unit->cx_tu); - clang_visitChildren(translation_unit_cursor, [](CXCursor cx_cursor, CXCursor cx_parent, CXClientData data) { - auto visitor_data = static_cast<VisitorData *>(data); - - auto path = filesystem::get_normal_path(clangmm::Cursor(cx_cursor).get_source_location().get_path()); - if (visitor_data->visited.find(path) == visitor_data->visited.end() && - filesystem::file_in_path(path, visitor_data->project_path)) - visitor_data->paths.emplace(path); - - return CXChildVisit_Continue; - }, - &visitor_data); - - for (auto &path : visitor_data.paths) - add_usages(project_path, build_path, path, usages, visited, spelling, cursor, translation_unit, store_in_cache); + if (project_path.empty()) + return; + + class VisitorData { + public: + const boost::filesystem::path &project_path; + const std::string &spelling; + PathSet &visited; + PathSet paths; + }; + VisitorData visitor_data{project_path, spelling, visited, {}}; + + auto translation_unit_cursor = clang_getTranslationUnitCursor(translation_unit->cx_tu); + clang_visitChildren(translation_unit_cursor, [](CXCursor cx_cursor, CXCursor cx_parent, CXClientData data) { + auto visitor_data = static_cast<VisitorData *>(data); + + auto path = filesystem::get_normal_path(clangmm::Cursor(cx_cursor).get_source_location().get_path()); + if (visitor_data->visited.find(path) == visitor_data->visited.end() && + filesystem::file_in_path(path, visitor_data->project_path)) + visitor_data->paths.emplace(path); + + return CXChildVisit_Continue; + }, + &visitor_data); + + for (auto &path : visitor_data.paths) + add_usages(project_path, build_path, path, usages, visited, spelling, cursor, translation_unit, store_in_cache); } Usages::Clang::PathSet Usages::Clang::find_paths(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &debug_path) { - PathSet paths; + PathSet paths; - CompileCommands compile_commands(build_path); + CompileCommands compile_commands(build_path); - for (boost::filesystem::recursive_directory_iterator it(project_path), end; it != end; ++it) { - auto &path = it->path(); - if (!boost::filesystem::is_regular_file(path)) { - if (path == build_path || path == debug_path || path.filename() == ".git") - it.no_push(); - continue; - } + for (boost::filesystem::recursive_directory_iterator it(project_path), end; it != end; ++it) { + auto &path = it->path(); + if (!boost::filesystem::is_regular_file(path)) { + if (path == build_path || path == debug_path || path.filename() == ".git") + it.no_push(); + continue; + } - if (is_header(path)) - paths.emplace(path); - else if (is_source(path)) { - for (auto &command : compile_commands.commands) { - if (filesystem::get_normal_path(command.file) == path) { - paths.emplace(path); - break; - } - } + if (is_header(path)) + paths.emplace(path); + else if (is_source(path)) { + for (auto &command : compile_commands.commands) { + if (filesystem::get_normal_path(command.file) == path) { + paths.emplace(path); + break; } + } } + } - return paths; + return paths; } bool Usages::Clang::is_header(const boost::filesystem::path &path) { - auto ext = path.extension(); - if (ext == ".h" || // c headers - ext == ".hh" || ext == ".hp" || ext == ".hpp" || ext == ".h++" || ext == ".tcc" || // c++ headers - ext == ".cuh") // CUDA headers - return true; - else - return false; + auto ext = path.extension(); + if (ext == ".h" || // c headers + ext == ".hh" || ext == ".hp" || ext == ".hpp" || ext == ".h++" || ext == ".tcc" || // c++ headers + ext == ".cuh") // CUDA headers + return true; + else + return false; } bool Usages::Clang::is_source(const boost::filesystem::path &path) { - auto ext = path.extension(); - if (ext == ".c" || // c sources - ext == ".cpp" || ext == ".cxx" || ext == ".cc" || ext == ".C" || ext == ".c++" || // c++ sources - ext == ".cu" || // CUDA sources - ext == ".cl") // OpenCL sources - return true; - else - return false; + auto ext = path.extension(); + if (ext == ".c" || // c sources + ext == ".cpp" || ext == ".cxx" || ext == ".cc" || ext == ".C" || ext == ".c++" || // c++ sources + ext == ".cu" || // CUDA sources + ext == ".cl") // OpenCL sources + return true; + else + return false; } std::pair<std::map<boost::filesystem::path, Usages::Clang::PathSet>, Usages::Clang::PathSet> Usages::Clang::parse_paths(const std::string &spelling, const PathSet &paths) { - std::map<boost::filesystem::path, PathSet> paths_includes; - PathSet paths_with_spelling; - - const static std::regex include_regex("^[ \t]*#[ \t]*include[ \t]*[\"]([^\"]+)[\"].*$"); - - auto is_spelling_char = [](char chr) { - return (chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z') || (chr >= '0' && chr <= '9') || chr == '_'; - }; - - for (auto &path : paths) { - auto paths_includes_it = paths_includes.emplace(path, PathSet()).first; - bool paths_with_spelling_emplaced = false; - - std::ifstream stream(path.string(), std::ifstream::binary); - if (!stream) + std::map<boost::filesystem::path, PathSet> paths_includes; + PathSet paths_with_spelling; + + const static std::regex include_regex("^[ \t]*#[ \t]*include[ \t]*[\"]([^\"]+)[\"].*$"); + + auto is_spelling_char = [](char chr) { + return (chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z') || (chr >= '0' && chr <= '9') || chr == '_'; + }; + + for (auto &path : paths) { + auto paths_includes_it = paths_includes.emplace(path, PathSet()).first; + bool paths_with_spelling_emplaced = false; + + std::ifstream stream(path.string(), std::ifstream::binary); + if (!stream) + continue; + std::string line; + while (std::getline(stream, line)) { + std::smatch sm; + if (std::regex_match(line, sm, include_regex)) { + boost::filesystem::path path(sm[1].str()); + boost::filesystem::path include_path; + // remove .. and . + for (auto &part : path) { + if (part == "..") + include_path = include_path.parent_path(); + else if (part == ".") continue; - std::string line; - while (std::getline(stream, line)) { - std::smatch sm; - if (std::regex_match(line, sm, include_regex)) { - boost::filesystem::path path(sm[1].str()); - boost::filesystem::path include_path; - // remove .. and . - for (auto &part : path) { - if (part == "..") - include_path = include_path.parent_path(); - else if (part == ".") - continue; - else - include_path /= part; - } - auto distance = std::distance(include_path.begin(), include_path.end()); - for (auto &path : paths) { - auto path_distance = std::distance(path.begin(), path.end()); - if (path_distance >= distance) { - auto it = path.begin(); - std::advance(it, path_distance - distance); - if (std::equal(it, path.end(), include_path.begin(), include_path.end())) - paths_includes_it->second.emplace(path); - } - } - } else if (!paths_with_spelling_emplaced) { - auto pos = line.find(spelling); - if (pos != std::string::npos && - ((!spelling.empty() && !is_spelling_char(spelling[0])) || - ((pos == 0 || !is_spelling_char(line[pos - 1])) && - (pos + spelling.size() >= line.size() - 1 || !is_spelling_char(line[pos + spelling.size()]))))) { - paths_with_spelling.emplace(path); - paths_with_spelling_emplaced = true; - } - } + else + include_path /= part; + } + auto distance = std::distance(include_path.begin(), include_path.end()); + for (auto &path : paths) { + auto path_distance = std::distance(path.begin(), path.end()); + if (path_distance >= distance) { + auto it = path.begin(); + std::advance(it, path_distance - distance); + if (std::equal(it, path.end(), include_path.begin(), include_path.end())) + paths_includes_it->second.emplace(path); + } + } + } else if (!paths_with_spelling_emplaced) { + auto pos = line.find(spelling); + if (pos != std::string::npos && + ((!spelling.empty() && !is_spelling_char(spelling[0])) || + ((pos == 0 || !is_spelling_char(line[pos - 1])) && + (pos + spelling.size() >= line.size() - 1 || !is_spelling_char(line[pos + spelling.size()]))))) { + paths_with_spelling.emplace(path); + paths_with_spelling_emplaced = true; } + } } - return {paths_includes, paths_with_spelling}; + } + return {paths_includes, paths_with_spelling}; } Usages::Clang::PathSet Usages::Clang::get_all_includes(const boost::filesystem::path &path, const std::map<boost::filesystem::path, PathSet> &paths_includes) { - PathSet all_includes; - - class Recursive { - public: - static void f(PathSet &all_includes, const boost::filesystem::path &path, - const std::map<boost::filesystem::path, PathSet> &paths_includes) { - auto paths_includes_it = paths_includes.find(path); - if (paths_includes_it != paths_includes.end()) { - for (auto &include : paths_includes_it->second) { - auto pair = all_includes.emplace(include); - if (pair.second) - f(all_includes, include, paths_includes); - } - } + PathSet all_includes; + + class Recursive { + public: + static void f(PathSet &all_includes, const boost::filesystem::path &path, + const std::map<boost::filesystem::path, PathSet> &paths_includes) { + auto paths_includes_it = paths_includes.find(path); + if (paths_includes_it != paths_includes.end()) { + for (auto &include : paths_includes_it->second) { + auto pair = all_includes.emplace(include); + if (pair.second) + f(all_includes, include, paths_includes); } - }; - Recursive::f(all_includes, path, paths_includes); + } + } + }; + Recursive::f(all_includes, path, paths_includes); - return all_includes; + return all_includes; } std::pair<Usages::Clang::PathSet, Usages::Clang::PathSet> Usages::Clang::find_potential_paths(const PathSet &paths, const boost::filesystem::path &project_path, const std::map<boost::filesystem::path, PathSet> &paths_includes, const PathSet &paths_with_spelling) { - PathSet potential_paths; - PathSet all_includes; - - bool first = true; - for (auto &path: paths) { - if (filesystem::file_in_path(path, project_path)) { - for (auto &path_with_spelling : paths_with_spelling) { - auto path_all_includes = get_all_includes(path_with_spelling, paths_includes); - if ((path_all_includes.find(path) != path_all_includes.end() || path_with_spelling == path)) { - potential_paths.emplace(path_with_spelling); - - for (auto &include : path_all_includes) - all_includes.emplace(include); - } - } - } else { - if (first) { - for (auto &path_with_spelling : paths_with_spelling) { - potential_paths.emplace(path_with_spelling); - - auto path_all_includes = get_all_includes(path_with_spelling, paths_includes); - for (auto &include : path_all_includes) - all_includes.emplace(include); - } - first = false; - } + PathSet potential_paths; + PathSet all_includes; + + bool first = true; + for (auto &path: paths) { + if (filesystem::file_in_path(path, project_path)) { + for (auto &path_with_spelling : paths_with_spelling) { + auto path_all_includes = get_all_includes(path_with_spelling, paths_includes); + if ((path_all_includes.find(path) != path_all_includes.end() || path_with_spelling == path)) { + potential_paths.emplace(path_with_spelling); + + for (auto &include : path_all_includes) + all_includes.emplace(include); + } + } + } else { + if (first) { + for (auto &path_with_spelling : paths_with_spelling) { + potential_paths.emplace(path_with_spelling); + + auto path_all_includes = get_all_includes(path_with_spelling, paths_includes); + for (auto &include : path_all_includes) + all_includes.emplace(include); } + first = false; + } } + } - return {potential_paths, all_includes}; + return {potential_paths, all_includes}; } void Usages::Clang::write_cache(const boost::filesystem::path &path, const Clang::Cache &cache) { - auto cache_path = cache.build_path / cache_folder; - boost::system::error_code ec; - if (!boost::filesystem::exists(cache_path, ec)) { - boost::filesystem::create_directory(cache_path, ec); - if (ec) - return; - } else if (!boost::filesystem::is_directory(cache_path, ec) || ec) - return; - - auto path_str = filesystem::get_relative_path(path, cache.project_path).string(); - for (auto &chr : path_str) { - if (chr == '/' || chr == '\\') - chr = '_'; - } - path_str += ".usages"; - - auto full_cache_path = cache_path / path_str; - auto tmp_file = boost::filesystem::temp_directory_path(ec); + auto cache_path = cache.build_path / cache_folder; + boost::system::error_code ec; + if (!boost::filesystem::exists(cache_path, ec)) { + boost::filesystem::create_directory(cache_path, ec); if (ec) - return; - tmp_file /= ("jucipp" + std::to_string(get_current_process_id()) + path_str); - - std::ofstream stream(tmp_file.string()); - if (stream) { - try { - boost::archive::text_oarchive text_oarchive(stream); - text_oarchive << cache; - stream.close(); - boost::filesystem::rename(tmp_file, full_cache_path, ec); - if (ec) { - boost::filesystem::copy_file(tmp_file, full_cache_path, - boost::filesystem::copy_option::overwrite_if_exists); - boost::filesystem::remove(tmp_file, ec); - } - } - catch (...) { - boost::filesystem::remove(tmp_file, ec); - } + return; + } else if (!boost::filesystem::is_directory(cache_path, ec) || ec) + return; + + auto path_str = filesystem::get_relative_path(path, cache.project_path).string(); + for (auto &chr : path_str) { + if (chr == '/' || chr == '\\') + chr = '_'; + } + path_str += ".usages"; + + auto full_cache_path = cache_path / path_str; + auto tmp_file = boost::filesystem::temp_directory_path(ec); + if (ec) + return; + tmp_file /= ("jucipp" + std::to_string(get_current_process_id()) + path_str); + + std::ofstream stream(tmp_file.string()); + if (stream) { + try { + boost::archive::text_oarchive text_oarchive(stream); + text_oarchive << cache; + stream.close(); + boost::filesystem::rename(tmp_file, full_cache_path, ec); + if (ec) { + boost::filesystem::copy_file(tmp_file, full_cache_path, + boost::filesystem::copy_option::overwrite_if_exists); + boost::filesystem::remove(tmp_file, ec); + } + } + catch (...) { + boost::filesystem::remove(tmp_file, ec); } + } } Usages::Clang::Cache Usages::Clang::read_cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path) { - auto path_str = filesystem::get_relative_path(path, project_path).string(); - for (auto &chr : path_str) { - if (chr == '/' || chr == '\\') - chr = '_'; - } - auto cache_path = build_path / cache_folder / (path_str + ".usages"); - - boost::system::error_code ec; - if (boost::filesystem::exists(cache_path, ec)) { - std::ifstream stream(cache_path.string()); - if (stream) { - Cache cache; - boost::archive::text_iarchive text_iarchive(stream); - try { - text_iarchive >> cache; - return cache; - } - catch (...) { - } - } + auto path_str = filesystem::get_relative_path(path, project_path).string(); + for (auto &chr : path_str) { + if (chr == '/' || chr == '\\') + chr = '_'; + } + auto cache_path = build_path / cache_folder / (path_str + ".usages"); + + boost::system::error_code ec; + if (boost::filesystem::exists(cache_path, ec)) { + std::ifstream stream(cache_path.string()); + if (stream) { + Cache cache; + boost::archive::text_iarchive text_iarchive(stream); + try { + text_iarchive >> cache; + return cache; + } + catch (...) { + } } - return Cache(); + } + return Cache(); } diff --git a/src/usages_clang.h b/src/usages_clang.h index 61f127fa..e3bf4835 100644 --- a/src/usages_clang.h +++ b/src/usages_clang.h @@ -15,167 +15,167 @@ #include <unordered_set> namespace boost { - namespace serialization { - template<class Archive> - void serialize(Archive &ar, boost::filesystem::path &path, const unsigned int version) { - std::string path_str; - if (Archive::is_saving::value) - path_str = path.string(); - ar & path_str; - if (Archive::is_loading::value) - path = path_str; - } - } // namespace serialization + namespace serialization { + template<class Archive> + void serialize(Archive &ar, boost::filesystem::path &path, const unsigned int version) { + std::string path_str; + if (Archive::is_saving::value) + path_str = path.string(); + ar & path_str; + if (Archive::is_loading::value) + path = path_str; + } + } // namespace serialization } // namespace boost namespace Usages { - class Clang { + class Clang { + public: + typedef std::set<boost::filesystem::path> PathSet; + + class Usages { public: - typedef std::set<boost::filesystem::path> PathSet; - - class Usages { - public: - boost::filesystem::path path; - std::vector<std::pair<clangmm::Offset, clangmm::Offset>> offsets; - std::vector<std::string> lines; - }; - - class Cache { - friend class boost::serialization::access; - - template<class Archive> - void serialize(Archive &ar, const unsigned int version) { - ar & project_path; - ar & build_path; - ar & tokens; - ar & cursors; - ar & paths_and_last_write_times; - } - - public: - class Cursor { - friend class boost::serialization::access; - - template<class Archive> - void serialize(Archive &ar, const unsigned int version) { - ar & kind; - ar & usrs; - } - - public: - clangmm::Cursor::Kind kind; - std::unordered_set<std::string> usrs; - - bool operator==(const Cursor &o); - }; - - class Token { - friend class boost::serialization::access; - - template<class Archive> - void serialize(Archive &ar, const unsigned int version) { - ar & spelling; - ar & offsets.first.line & offsets.first.index; - ar & offsets.second.line & offsets.second.index; - ar & cursor_id; - } - - public: - std::string spelling; - std::pair<clangmm::Offset, clangmm::Offset> offsets; - size_t cursor_id; - }; - - boost::filesystem::path project_path; - boost::filesystem::path build_path; - - std::vector<Token> tokens; - std::vector<Cursor> cursors; - std::map<boost::filesystem::path, std::time_t> paths_and_last_write_times; - - Cache() {} - - Cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, - const boost::filesystem::path &path, - std::time_t before_parse_time, clangmm::TranslationUnit *translation_unit, - clangmm::Tokens *clang_tokens); - - operator bool() const { return !paths_and_last_write_times.empty(); } - - std::vector<std::pair<clangmm::Offset, clangmm::Offset>> - get_similar_token_offsets(clangmm::Cursor::Kind kind, const std::string &spelling, - const std::unordered_set<std::string> &usrs) const; - }; - - private: - const static boost::filesystem::path cache_folder; - - static std::map<boost::filesystem::path, Cache> caches; - static std::mutex caches_mutex; - - static std::atomic<size_t> cache_in_progress_count; + boost::filesystem::path path; + std::vector<std::pair<clangmm::Offset, clangmm::Offset>> offsets; + std::vector<std::string> lines; + }; + + class Cache { + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive &ar, const unsigned int version) { + ar & project_path; + ar & build_path; + ar & tokens; + ar & cursors; + ar & paths_and_last_write_times; + } public: - static std::vector<Usages> - get_usages(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, - const boost::filesystem::path &debug_path, - const std::string &spelling, const clangmm::Cursor &cursor, - const std::vector<clangmm::TranslationUnit *> &translation_units); + class Cursor { + friend class boost::serialization::access; + + template<class Archive> + void serialize(Archive &ar, const unsigned int version) { + ar & kind; + ar & usrs; + } - static void cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, - const boost::filesystem::path &path, - std::time_t before_parse_time, const PathSet &project_paths_in_use, - clangmm::TranslationUnit *translation_unit, clangmm::Tokens *tokens); + public: + clangmm::Cursor::Kind kind; + std::unordered_set<std::string> usrs; - static void erase_unused_caches(const PathSet &project_paths_in_use); + bool operator==(const Cursor &o); + }; - static void erase_cache(const boost::filesystem::path &path); + class Token { + friend class boost::serialization::access; - static void erase_all_caches_for_project(const boost::filesystem::path &project_path, - const boost::filesystem::path &build_path); + template<class Archive> + void serialize(Archive &ar, const unsigned int version) { + ar & spelling; + ar & offsets.first.line & offsets.first.index; + ar & offsets.second.line & offsets.second.index; + ar & cursor_id; + } - static void cache_in_progress(); + public: + std::string spelling; + std::pair<clangmm::Offset, clangmm::Offset> offsets; + size_t cursor_id; + }; - private: - static void add_usages(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, - const boost::filesystem::path &path_, - std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, - clangmm::Cursor cursor, - clangmm::TranslationUnit *translation_unit, bool store_in_cache); + boost::filesystem::path project_path; + boost::filesystem::path build_path; - static bool - add_usages_from_cache(const boost::filesystem::path &path, std::vector<Usages> &usages, PathSet &visited, - const std::string &spelling, const clangmm::Cursor &cursor, const Cache &cache); + std::vector<Token> tokens; + std::vector<Cursor> cursors; + std::map<boost::filesystem::path, std::time_t> paths_and_last_write_times; - static void - add_usages_from_includes(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, - std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, - const clangmm::Cursor &cursor, - clangmm::TranslationUnit *translation_unit, bool store_in_cache); + Cache() {} - static PathSet find_paths(const boost::filesystem::path &project_path, - const boost::filesystem::path &build_path, const boost::filesystem::path &debug_path); + Cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, + const boost::filesystem::path &path, + std::time_t before_parse_time, clangmm::TranslationUnit *translation_unit, + clangmm::Tokens *clang_tokens); - static bool is_header(const boost::filesystem::path &path); + operator bool() const { return !paths_and_last_write_times.empty(); } - static bool is_source(const boost::filesystem::path &path); + std::vector<std::pair<clangmm::Offset, clangmm::Offset>> + get_similar_token_offsets(clangmm::Cursor::Kind kind, const std::string &spelling, + const std::unordered_set<std::string> &usrs) const; + }; - static std::pair<std::map<boost::filesystem::path, PathSet>, PathSet> - parse_paths(const std::string &spelling, const PathSet &paths); + private: + const static boost::filesystem::path cache_folder; - /// Recursively find and return all the include paths of path - static PathSet get_all_includes(const boost::filesystem::path &path, - const std::map<boost::filesystem::path, PathSet> &paths_includes); + static std::map<boost::filesystem::path, Cache> caches; + static std::mutex caches_mutex; - /// Based on cursor paths, paths_includes and paths_with_spelling return potential paths that might contain the sought after symbol - static std::pair<Clang::PathSet, Clang::PathSet> - find_potential_paths(const PathSet &paths, const boost::filesystem::path &project_path, - const std::map<boost::filesystem::path, PathSet> &paths_includes, - const PathSet &paths_with_spelling); + static std::atomic<size_t> cache_in_progress_count; - static void write_cache(const boost::filesystem::path &path, const Cache &cache); + public: + static std::vector<Usages> + get_usages(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, + const boost::filesystem::path &debug_path, + const std::string &spelling, const clangmm::Cursor &cursor, + const std::vector<clangmm::TranslationUnit *> &translation_units); - static Cache read_cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, - const boost::filesystem::path &path); - }; + static void cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, + const boost::filesystem::path &path, + std::time_t before_parse_time, const PathSet &project_paths_in_use, + clangmm::TranslationUnit *translation_unit, clangmm::Tokens *tokens); + + static void erase_unused_caches(const PathSet &project_paths_in_use); + + static void erase_cache(const boost::filesystem::path &path); + + static void erase_all_caches_for_project(const boost::filesystem::path &project_path, + const boost::filesystem::path &build_path); + + static void cache_in_progress(); + + private: + static void add_usages(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, + const boost::filesystem::path &path_, + std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, + clangmm::Cursor cursor, + clangmm::TranslationUnit *translation_unit, bool store_in_cache); + + static bool + add_usages_from_cache(const boost::filesystem::path &path, std::vector<Usages> &usages, PathSet &visited, + const std::string &spelling, const clangmm::Cursor &cursor, const Cache &cache); + + static void + add_usages_from_includes(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, + std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, + const clangmm::Cursor &cursor, + clangmm::TranslationUnit *translation_unit, bool store_in_cache); + + static PathSet find_paths(const boost::filesystem::path &project_path, + const boost::filesystem::path &build_path, const boost::filesystem::path &debug_path); + + static bool is_header(const boost::filesystem::path &path); + + static bool is_source(const boost::filesystem::path &path); + + static std::pair<std::map<boost::filesystem::path, PathSet>, PathSet> + parse_paths(const std::string &spelling, const PathSet &paths); + + /// Recursively find and return all the include paths of path + static PathSet get_all_includes(const boost::filesystem::path &path, + const std::map<boost::filesystem::path, PathSet> &paths_includes); + + /// Based on cursor paths, paths_includes and paths_with_spelling return potential paths that might contain the sought after symbol + static std::pair<Clang::PathSet, Clang::PathSet> + find_potential_paths(const PathSet &paths, const boost::filesystem::path &project_path, + const std::map<boost::filesystem::path, PathSet> &paths_includes, + const PathSet &paths_with_spelling); + + static void write_cache(const boost::filesystem::path &path, const Cache &cache); + + static Cache read_cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, + const boost::filesystem::path &path); + }; } // namespace Usages diff --git a/src/window.cc b/src/window.cc index 9371207a..04eb6bbb 100644 --- a/src/window.cc +++ b/src/window.cc @@ -12,1776 +12,1776 @@ #include "terminal.h" Window::Window() { - Gsv::init(); + Gsv::init(); - set_title("juCi++"); - set_events(Gdk::POINTER_MOTION_MASK | Gdk::FOCUS_CHANGE_MASK | Gdk::SCROLL_MASK | Gdk::LEAVE_NOTIFY_MASK); + set_title("juCi++"); + set_events(Gdk::POINTER_MOTION_MASK | Gdk::FOCUS_CHANGE_MASK | Gdk::SCROLL_MASK | Gdk::LEAVE_NOTIFY_MASK); - auto provider = Gtk::CssProvider::create(); - auto screen = get_screen(); - std::string border_radius_style; - if (screen->get_rgba_visual()) - border_radius_style = "border-radius: 5px; "; + auto provider = Gtk::CssProvider::create(); + auto screen = get_screen(); + std::string border_radius_style; + if (screen->get_rgba_visual()) + border_radius_style = "border-radius: 5px; "; #if GTK_VERSION_GE(3, 20) - std::string notebook_style(".juci_notebook tab {border-radius: 5px 5px 0 0; padding: 0 4px; margin: 0;}"); + std::string notebook_style(".juci_notebook tab {border-radius: 5px 5px 0 0; padding: 0 4px; margin: 0;}"); #else - std::string notebook_style(".juci_notebook {-GtkNotebook-tab-overlap: 0px;} .juci_notebook tab {border-radius: 5px 5px 0 0; padding: 4px 4px;}"); + std::string notebook_style(".juci_notebook {-GtkNotebook-tab-overlap: 0px;} .juci_notebook tab {border-radius: 5px 5px 0 0; padding: 4px 4px;}"); #endif - provider->load_from_data(R"( + provider->load_from_data(R"( .juci_directories *:selected {border-left-color: inherit; color: inherit; background-color: rgba(128, 128, 128 , 0.2); background-image: inherit;} )" + notebook_style + R"( .juci_info {border-radius: 5px;} .juci_tooltip_window {background-color: transparent;} .juci_tooltip_box {)" + border_radius_style + R"(padding: 3px;} )"); - get_style_context()->add_provider_for_screen(screen, provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + get_style_context()->add_provider_for_screen(screen, provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); - set_menu_actions(); - configure(); - Menu::get().toggle_menu_items(); + set_menu_actions(); + configure(); + Menu::get().toggle_menu_items(); - Menu::get().right_click_line_menu->attach_to_widget(*this); - Menu::get().right_click_selected_menu->attach_to_widget(*this); + Menu::get().right_click_line_menu->attach_to_widget(*this); + Menu::get().right_click_selected_menu->attach_to_widget(*this); - EntryBox::get().signal_hide().connect([]() { - if (auto view = Notebook::get().get_current_view()) - view->grab_focus(); - }); + EntryBox::get().signal_hide().connect([]() { + if (auto view = Notebook::get().get_current_view()) + view->grab_focus(); + }); - Notebook::get().on_change_page = [this](Source::View *view) { - if (search_entry_shown && EntryBox::get().labels.size() > 0) { - view->update_search_occurrences = [](int number) { - EntryBox::get().labels.begin()->update(0, std::to_string(number)); - }; - view->search_highlight(last_search, case_sensitive_search, regex_search); - } + Notebook::get().on_change_page = [this](Source::View *view) { + if (search_entry_shown && EntryBox::get().labels.size() > 0) { + view->update_search_occurrences = [](int number) { + EntryBox::get().labels.begin()->update(0, std::to_string(number)); + }; + view->search_highlight(last_search, case_sensitive_search, regex_search); + } - Menu::get().toggle_menu_items(); + Menu::get().toggle_menu_items(); - Directories::get().select(view->file_path); + Directories::get().select(view->file_path); - if (view->full_reparse_needed) - view->full_reparse(); - else if (view->soft_reparse_needed) - view->soft_reparse(); + if (view->full_reparse_needed) + view->full_reparse(); + else if (view->soft_reparse_needed) + view->soft_reparse(); - Notebook::get().update_status(view); + Notebook::get().update_status(view); #ifdef JUCI_ENABLE_DEBUG - if(Project::debugging) - Project::debug_update_stop(); + if(Project::debugging) + Project::debug_update_stop(); #endif - }; - Notebook::get().on_close_page = [](Source::View *view) { + }; + Notebook::get().on_close_page = [](Source::View *view) { #ifdef JUCI_ENABLE_DEBUG - if(Project::current && Project::debugging) { - auto iter=view->get_buffer()->begin(); - while(view->get_source_buffer()->forward_iter_to_source_mark(iter, "debug_breakpoint") || - view->get_source_buffer()->get_source_marks_at_iter(iter, "debug_breakpoint").size()) { - auto end_iter=view->get_iter_at_line_end(iter.get_line()); - view->get_source_buffer()->remove_source_marks(iter, end_iter, "debug_breakpoint"); - Project::current->debug_remove_breakpoint(view->file_path, iter.get_line()+1, view->get_buffer()->get_line_count()+1); - } - } + if(Project::current && Project::debugging) { + auto iter=view->get_buffer()->begin(); + while(view->get_source_buffer()->forward_iter_to_source_mark(iter, "debug_breakpoint") || + view->get_source_buffer()->get_source_marks_at_iter(iter, "debug_breakpoint").size()) { + auto end_iter=view->get_iter_at_line_end(iter.get_line()); + view->get_source_buffer()->remove_source_marks(iter, end_iter, "debug_breakpoint"); + Project::current->debug_remove_breakpoint(view->file_path, iter.get_line()+1, view->get_buffer()->get_line_count()+1); + } + } #endif - EntryBox::get().hide(); - if (auto view = Notebook::get().get_current_view()) - Notebook::get().update_status(view); - else { - Notebook::get().clear_status(); - - Menu::get().toggle_menu_items(); - } - }; - - signal_focus_out_event().connect([](GdkEventFocus *event) { - if (auto view = Notebook::get().get_current_view()) { - view->hide_tooltips(); - view->hide_dialogs(); - } - return false; - }); + EntryBox::get().hide(); + if (auto view = Notebook::get().get_current_view()) + Notebook::get().update_status(view); + else { + Notebook::get().clear_status(); - signal_hide().connect([] { - while (!Source::View::non_deleted_views.empty()) { - while (Gtk::Main::events_pending()) - Gtk::Main::iteration(false); - } - // TODO 2022 (after Debian Stretch LTS has ended, see issue #354): remove: - Project::current = nullptr; - }); + Menu::get().toggle_menu_items(); + } + }; - Gtk::Settings::get_default()->connect_property_changed("gtk-theme-name", [] { - Directories::get().update(); - if (auto view = Notebook::get().get_current_view()) - Notebook::get().update_status(view); - }); + signal_focus_out_event().connect([](GdkEventFocus *event) { + if (auto view = Notebook::get().get_current_view()) { + view->hide_tooltips(); + view->hide_dialogs(); + } + return false; + }); - about.signal_response().connect([this](int d) { - about.hide(); - }); + signal_hide().connect([] { + while (!Source::View::non_deleted_views.empty()) { + while (Gtk::Main::events_pending()) + Gtk::Main::iteration(false); + } + // TODO 2022 (after Debian Stretch LTS has ended, see issue #354): remove: + Project::current = nullptr; + }); - about.set_logo_icon_name("juci"); - about.set_version(Config::get().window.version); - about.set_authors({"(in order of appearance)", - "Ted Johan Kristoffersen", - "Jørgen Lien Sellæg", - "Geir Morten Larsen", - "Ole Christian Eidheim"}); - about.set_name("About juCi++"); - about.set_program_name("juCi++"); - about.set_comments("This is an open source IDE with high-end features to make your programming experience juicy"); - about.set_license_type(Gtk::License::LICENSE_MIT_X11); - about.set_transient_for(*this); + Gtk::Settings::get_default()->connect_property_changed("gtk-theme-name", [] { + Directories::get().update(); + if (auto view = Notebook::get().get_current_view()) + Notebook::get().update_status(view); + }); + + about.signal_response().connect([this](int d) { + about.hide(); + }); + + about.set_logo_icon_name("juci"); + about.set_version(Config::get().window.version); + about.set_authors({"(in order of appearance)", + "Ted Johan Kristoffersen", + "Jørgen Lien Sellæg", + "Geir Morten Larsen", + "Ole Christian Eidheim"}); + about.set_name("About juCi++"); + about.set_program_name("juCi++"); + about.set_comments("This is an open source IDE with high-end features to make your programming experience juicy"); + about.set_license_type(Gtk::License::LICENSE_MIT_X11); + about.set_transient_for(*this); } // Window constructor void Window::configure() { - Config::get().load(); - auto screen = get_screen(); - if (css_provider_theme) - Gtk::StyleContext::remove_provider_for_screen(screen, css_provider_theme); - if (Config::get().window.theme_name.empty()) { - css_provider_theme = Gtk::CssProvider::create(); - Gtk::Settings::get_default()->property_gtk_application_prefer_dark_theme() = ( - Config::get().window.theme_variant == "dark"); - } else - css_provider_theme = Gtk::CssProvider::get_named(Config::get().window.theme_name, - Config::get().window.theme_variant); - //TODO: add check if theme exists, or else write error to terminal - Gtk::StyleContext::add_provider_for_screen(screen, css_provider_theme, GTK_STYLE_PROVIDER_PRIORITY_SETTINGS); - - auto style_scheme_manager = Source::StyleSchemeManager::get_default(); - if (css_provider_tooltips) - Gtk::StyleContext::remove_provider_for_screen(screen, css_provider_tooltips); - else - css_provider_tooltips = Gtk::CssProvider::create(); - Glib::RefPtr<Gsv::Style> style; - if (Config::get().source.style.size() > 0) { - auto scheme = style_scheme_manager->get_scheme(Config::get().source.style); - if (scheme) - style = scheme->get_style("def:note"); - else { - Terminal::get().print("Error: Could not find gtksourceview style: " + Config::get().source.style + '\n', - true); - } + Config::get().load(); + auto screen = get_screen(); + if (css_provider_theme) + Gtk::StyleContext::remove_provider_for_screen(screen, css_provider_theme); + if (Config::get().window.theme_name.empty()) { + css_provider_theme = Gtk::CssProvider::create(); + Gtk::Settings::get_default()->property_gtk_application_prefer_dark_theme() = ( + Config::get().window.theme_variant == "dark"); + } else + css_provider_theme = Gtk::CssProvider::get_named(Config::get().window.theme_name, + Config::get().window.theme_variant); + //TODO: add check if theme exists, or else write error to terminal + Gtk::StyleContext::add_provider_for_screen(screen, css_provider_theme, GTK_STYLE_PROVIDER_PRIORITY_SETTINGS); + + auto style_scheme_manager = Source::StyleSchemeManager::get_default(); + if (css_provider_tooltips) + Gtk::StyleContext::remove_provider_for_screen(screen, css_provider_tooltips); + else + css_provider_tooltips = Gtk::CssProvider::create(); + Glib::RefPtr<Gsv::Style> style; + if (Config::get().source.style.size() > 0) { + auto scheme = style_scheme_manager->get_scheme(Config::get().source.style); + if (scheme) + style = scheme->get_style("def:note"); + else { + Terminal::get().print("Error: Could not find gtksourceview style: " + Config::get().source.style + '\n', + true); } - auto foreground_value = style && style->property_foreground_set() ? style->property_foreground().get_value() - : get_style_context()->get_color().to_string(); - auto background_value = style && style->property_background_set() ? style->property_background().get_value() - : get_style_context()->get_background_color().to_string(); + } + auto foreground_value = style && style->property_foreground_set() ? style->property_foreground().get_value() + : get_style_context()->get_color().to_string(); + auto background_value = style && style->property_background_set() ? style->property_background().get_value() + : get_style_context()->get_background_color().to_string(); #if GTK_VERSION_GE(3, 20) - css_provider_tooltips->load_from_data(".juci_tooltip_box {background-color: " + background_value + ";}" - ".juci_tooltip_text_view text {color: " + - foreground_value + ";background-color: " + background_value + ";}"); + css_provider_tooltips->load_from_data(".juci_tooltip_box {background-color: " + background_value + ";}" + ".juci_tooltip_text_view text {color: " + + foreground_value + ";background-color: " + background_value + ";}"); #else - css_provider_tooltips->load_from_data(".juci_tooltip_box {background-color: "+background_value+";}" - ".juci_tooltip_text_view *:not(:selected) {color: "+foreground_value+";background-color: "+background_value+";}"); + css_provider_tooltips->load_from_data(".juci_tooltip_box {background-color: "+background_value+";}" + ".juci_tooltip_text_view *:not(:selected) {color: "+foreground_value+";background-color: "+background_value+";}"); #endif - get_style_context()->add_provider_for_screen(screen, css_provider_tooltips, - GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); - - Menu::get().set_keys(); - Terminal::get().configure(); - Directories::get().update(); - if (auto view = Notebook::get().get_current_view()) - Notebook::get().update_status(view); + get_style_context()->add_provider_for_screen(screen, css_provider_tooltips, + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + Menu::get().set_keys(); + Terminal::get().configure(); + Directories::get().update(); + if (auto view = Notebook::get().get_current_view()) + Notebook::get().update_status(view); } void Window::set_menu_actions() { - auto &menu = Menu::get(); - - menu.add_action("about", [this]() { - about.show(); - about.present(); - }); - menu.add_action("preferences", []() { - Notebook::get().open(Config::get().home_juci_path / "config" / "config.json"); - }); - menu.add_action("quit", [this]() { - close(); - }); - - menu.add_action("file_new_file", []() { - boost::filesystem::path path = Dialog::new_file(Notebook::get().get_current_folder()); - if (path != "") { - if (boost::filesystem::exists(path)) { - Terminal::get().print("Error: " + path.string() + " already exists.\n", true); - } else { - if (filesystem::write(path)) { - if (Directories::get().path != "") - Directories::get().update(); - Notebook::get().open(path); - if (Directories::get().path != "") - Directories::get().on_save_file(path); - Terminal::get().print("New file " + path.string() + " created.\n"); - } else - Terminal::get().print("Error: could not create new file " + path.string() + ".\n", true); - } - } - }); - menu.add_action("file_new_folder", []() { - auto time_now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); - boost::filesystem::path path = Dialog::new_folder(Notebook::get().get_current_folder()); - if (path != "" && boost::filesystem::exists(path)) { - boost::system::error_code ec; - auto last_write_time = boost::filesystem::last_write_time(path, ec); - if (!ec && last_write_time >= time_now) { - if (Directories::get().path != "") - Directories::get().update(); - Terminal::get().print("New folder " + path.string() + " created.\n"); - } else - Terminal::get().print("Error: " + path.string() + " already exists.\n", true); - Directories::get().select(path); - } - }); - menu.add_action("file_new_project_c", []() { - boost::filesystem::path project_path = Dialog::new_folder(Notebook::get().get_current_folder()); - if (project_path != "") { - auto project_name = project_path.filename().string(); - for (size_t c = 0; c < project_name.size(); c++) { - if (project_name[c] == ' ') - project_name[c] = '_'; - } - auto cmakelists_path = project_path; - cmakelists_path /= "CMakeLists.txt"; - auto c_main_path = project_path; - c_main_path /= "main.c"; - if (boost::filesystem::exists(cmakelists_path)) { - Terminal::get().print("Error: " + cmakelists_path.string() + " already exists.\n", true); - return; - } - if (boost::filesystem::exists(c_main_path)) { - Terminal::get().print("Error: " + c_main_path.string() + " already exists.\n", true); - return; - } - std::string cmakelists = "cmake_minimum_required(VERSION 2.8)\n\nproject(" + project_name + - ")\n\nset(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -std=c11 -Wall -Wextra\")\n\nadd_executable(" + - project_name + " main.c)\n"; - std::string c_main = "#include <stdio.h>\n\nint main() {\n printf(\"Hello World!\\n\");\n}\n"; - if (filesystem::write(cmakelists_path, cmakelists) && filesystem::write(c_main_path, c_main)) { - Directories::get().open(project_path); - Notebook::get().open(c_main_path); - Directories::get().update(); - Terminal::get().print("C project " + project_name + " created.\n"); - } else - Terminal::get().print("Error: Could not create project " + project_path.string() + "\n", true); - } - }); - menu.add_action("file_new_project_cpp", []() { - boost::filesystem::path project_path = Dialog::new_folder(Notebook::get().get_current_folder()); - if (project_path != "") { - auto project_name = project_path.filename().string(); - for (size_t c = 0; c < project_name.size(); c++) { - if (project_name[c] == ' ') - project_name[c] = '_'; - } - auto cmakelists_path = project_path; - cmakelists_path /= "CMakeLists.txt"; - auto cpp_main_path = project_path; - cpp_main_path /= "main.cpp"; - if (boost::filesystem::exists(cmakelists_path)) { - Terminal::get().print("Error: " + cmakelists_path.string() + " already exists.\n", true); - return; - } - if (boost::filesystem::exists(cpp_main_path)) { - Terminal::get().print("Error: " + cpp_main_path.string() + " already exists.\n", true); - return; - } - std::string cmakelists = "cmake_minimum_required(VERSION 2.8)\n\nproject(" + project_name + - ")\n\nset(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -std=c++1y -Wall -Wextra\")\n\nadd_executable(" + - project_name + " main.cpp)\n"; - std::string cpp_main = "#include <iostream>\n\nint main() {\n std::cout << \"Hello World!\\n\";\n}\n"; - if (filesystem::write(cmakelists_path, cmakelists) && filesystem::write(cpp_main_path, cpp_main)) { - Directories::get().open(project_path); - Notebook::get().open(cpp_main_path); - Directories::get().update(); - Terminal::get().print("C++ project " + project_name + " created.\n"); - } else - Terminal::get().print("Error: Could not create project " + project_path.string() + "\n", true); - } - }); - - menu.add_action("file_open_file", []() { - auto folder_path = Notebook::get().get_current_folder(); - if (auto view = Notebook::get().get_current_view()) { - if (!Directories::get().path.empty() && !filesystem::file_in_path(view->file_path, Directories::get().path)) - folder_path = view->file_path.parent_path(); - } - auto path = Dialog::open_file(folder_path); - if (path != "") - Notebook::get().open(path); - }); - menu.add_action("file_open_folder", []() { - auto path = Dialog::open_folder(Notebook::get().get_current_folder()); - if (path != "" && boost::filesystem::exists(path)) - Directories::get().open(path); - }); - - menu.add_action("file_reload_file", []() { - if (auto view = Notebook::get().get_current_view()) { - if (boost::filesystem::exists(view->file_path)) { - std::ifstream can_read(view->file_path.string()); - if (!can_read) { - Terminal::get().print("Error: could not read " + view->file_path.string() + "\n", true); - return; - } - can_read.close(); - } else { - Terminal::get().print("Error: " + view->file_path.string() + " does not exist\n", true); - return; - } + auto &menu = Menu::get(); + + menu.add_action("about", [this]() { + about.show(); + about.present(); + }); + menu.add_action("preferences", []() { + Notebook::get().open(Config::get().home_juci_path / "config" / "config.json"); + }); + menu.add_action("quit", [this]() { + close(); + }); + + menu.add_action("file_new_file", []() { + boost::filesystem::path path = Dialog::new_file(Notebook::get().get_current_folder()); + if (path != "") { + if (boost::filesystem::exists(path)) { + Terminal::get().print("Error: " + path.string() + " already exists.\n", true); + } else { + if (filesystem::write(path)) { + if (Directories::get().path != "") + Directories::get().update(); + Notebook::get().open(path); + if (Directories::get().path != "") + Directories::get().on_save_file(path); + Terminal::get().print("New file " + path.string() + " created.\n"); + } else + Terminal::get().print("Error: could not create new file " + path.string() + ".\n", true); + } + } + }); + menu.add_action("file_new_folder", []() { + auto time_now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + boost::filesystem::path path = Dialog::new_folder(Notebook::get().get_current_folder()); + if (path != "" && boost::filesystem::exists(path)) { + boost::system::error_code ec; + auto last_write_time = boost::filesystem::last_write_time(path, ec); + if (!ec && last_write_time >= time_now) { + if (Directories::get().path != "") + Directories::get().update(); + Terminal::get().print("New folder " + path.string() + " created.\n"); + } else + Terminal::get().print("Error: " + path.string() + " already exists.\n", true); + Directories::get().select(path); + } + }); + menu.add_action("file_new_project_c", []() { + boost::filesystem::path project_path = Dialog::new_folder(Notebook::get().get_current_folder()); + if (project_path != "") { + auto project_name = project_path.filename().string(); + for (size_t c = 0; c < project_name.size(); c++) { + if (project_name[c] == ' ') + project_name[c] = '_'; + } + auto cmakelists_path = project_path; + cmakelists_path /= "CMakeLists.txt"; + auto c_main_path = project_path; + c_main_path /= "main.c"; + if (boost::filesystem::exists(cmakelists_path)) { + Terminal::get().print("Error: " + cmakelists_path.string() + " already exists.\n", true); + return; + } + if (boost::filesystem::exists(c_main_path)) { + Terminal::get().print("Error: " + c_main_path.string() + " already exists.\n", true); + return; + } + std::string cmakelists = "cmake_minimum_required(VERSION 2.8)\n\nproject(" + project_name + + ")\n\nset(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -std=c11 -Wall -Wextra\")\n\nadd_executable(" + + project_name + " main.c)\n"; + std::string c_main = "#include <stdio.h>\n\nint main() {\n printf(\"Hello World!\\n\");\n}\n"; + if (filesystem::write(cmakelists_path, cmakelists) && filesystem::write(c_main_path, c_main)) { + Directories::get().open(project_path); + Notebook::get().open(c_main_path); + Directories::get().update(); + Terminal::get().print("C project " + project_name + " created.\n"); + } else + Terminal::get().print("Error: Could not create project " + project_path.string() + "\n", true); + } + }); + menu.add_action("file_new_project_cpp", []() { + boost::filesystem::path project_path = Dialog::new_folder(Notebook::get().get_current_folder()); + if (project_path != "") { + auto project_name = project_path.filename().string(); + for (size_t c = 0; c < project_name.size(); c++) { + if (project_name[c] == ' ') + project_name[c] = '_'; + } + auto cmakelists_path = project_path; + cmakelists_path /= "CMakeLists.txt"; + auto cpp_main_path = project_path; + cpp_main_path /= "main.cpp"; + if (boost::filesystem::exists(cmakelists_path)) { + Terminal::get().print("Error: " + cmakelists_path.string() + " already exists.\n", true); + return; + } + if (boost::filesystem::exists(cpp_main_path)) { + Terminal::get().print("Error: " + cpp_main_path.string() + " already exists.\n", true); + return; + } + std::string cmakelists = "cmake_minimum_required(VERSION 2.8)\n\nproject(" + project_name + + ")\n\nset(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -std=c++1y -Wall -Wextra\")\n\nadd_executable(" + + project_name + " main.cpp)\n"; + std::string cpp_main = "#include <iostream>\n\nint main() {\n std::cout << \"Hello World!\\n\";\n}\n"; + if (filesystem::write(cmakelists_path, cmakelists) && filesystem::write(cpp_main_path, cpp_main)) { + Directories::get().open(project_path); + Notebook::get().open(cpp_main_path); + Directories::get().update(); + Terminal::get().print("C++ project " + project_name + " created.\n"); + } else + Terminal::get().print("Error: Could not create project " + project_path.string() + "\n", true); + } + }); - if (view->load()) - view->full_reparse(); + menu.add_action("file_open_file", []() { + auto folder_path = Notebook::get().get_current_folder(); + if (auto view = Notebook::get().get_current_view()) { + if (!Directories::get().path.empty() && !filesystem::file_in_path(view->file_path, Directories::get().path)) + folder_path = view->file_path.parent_path(); + } + auto path = Dialog::open_file(folder_path); + if (path != "") + Notebook::get().open(path); + }); + menu.add_action("file_open_folder", []() { + auto path = Dialog::open_folder(Notebook::get().get_current_folder()); + if (path != "" && boost::filesystem::exists(path)) + Directories::get().open(path); + }); + + menu.add_action("file_reload_file", []() { + if (auto view = Notebook::get().get_current_view()) { + if (boost::filesystem::exists(view->file_path)) { + std::ifstream can_read(view->file_path.string()); + if (!can_read) { + Terminal::get().print("Error: could not read " + view->file_path.string() + "\n", true); + return; } - }); + can_read.close(); + } else { + Terminal::get().print("Error: " + view->file_path.string() + " does not exist\n", true); + return; + } - menu.add_action("file_save", [this]() { - if (auto view = Notebook::get().get_current_view()) { - if (Notebook::get().save_current()) { - if (view->file_path == Config::get().home_juci_path / "config" / "config.json") { - configure(); - for (size_t c = 0; c < Notebook::get().size(); c++) { - Notebook::get().get_view(c)->configure(); - Notebook::get().configure(c); - } - } - } - } - }); - menu.add_action("file_save_as", []() { - if (auto view = Notebook::get().get_current_view()) { - auto path = Dialog::save_file_as(view->file_path); - if (path != "") { - std::ofstream file(path, std::ofstream::binary); - if (file) { - file << view->get_buffer()->get_text().raw(); - file.close(); - if (Directories::get().path != "") - Directories::get().update(); - Notebook::get().open(path); - Terminal::get().print( - "File saved to: " + Notebook::get().get_current_view()->file_path.string() + "\n"); - } else - Terminal::get().print("Error saving file\n", true); - } - } - }); + if (view->load()) + view->full_reparse(); + } + }); - menu.add_action("file_print", [this]() { - if (auto view = Notebook::get().get_current_view()) { - auto print_operation = Gtk::PrintOperation::create(); - auto print_compositor = Gsv::PrintCompositor::create(*view); - - print_operation->set_job_name(view->file_path.filename().string()); - print_compositor->set_wrap_mode(Gtk::WrapMode::WRAP_WORD_CHAR); - - print_operation->signal_begin_print().connect( - [print_operation, print_compositor](const Glib::RefPtr<Gtk::PrintContext> &print_context) { - while (!print_compositor->paginate(print_context)); - print_operation->set_n_pages(print_compositor->get_n_pages()); - }); - print_operation->signal_draw_page().connect( - [print_compositor](const Glib::RefPtr<Gtk::PrintContext> &print_context, int page_nr) { - print_compositor->draw_page(print_context, page_nr); - }); - - print_operation->run(Gtk::PRINT_OPERATION_ACTION_PRINT_DIALOG, *this); + menu.add_action("file_save", [this]() { + if (auto view = Notebook::get().get_current_view()) { + if (Notebook::get().save_current()) { + if (view->file_path == Config::get().home_juci_path / "config" / "config.json") { + configure(); + for (size_t c = 0; c < Notebook::get().size(); c++) { + Notebook::get().get_view(c)->configure(); + Notebook::get().configure(c); + } } - }); + } + } + }); + menu.add_action("file_save_as", []() { + if (auto view = Notebook::get().get_current_view()) { + auto path = Dialog::save_file_as(view->file_path); + if (path != "") { + std::ofstream file(path, std::ofstream::binary); + if (file) { + file << view->get_buffer()->get_text().raw(); + file.close(); + if (Directories::get().path != "") + Directories::get().update(); + Notebook::get().open(path); + Terminal::get().print( + "File saved to: " + Notebook::get().get_current_view()->file_path.string() + "\n"); + } else + Terminal::get().print("Error saving file\n", true); + } + } + }); - menu.add_action("edit_undo", []() { - if (auto view = Notebook::get().get_current_view()) { - auto undo_manager = view->get_source_buffer()->get_undo_manager(); - if (undo_manager->can_undo()) { - view->disable_spellcheck = true; - undo_manager->undo(); - view->disable_spellcheck = false; - view->scroll_to(view->get_buffer()->get_insert()); - } - } - }); - menu.add_action("edit_redo", []() { - if (auto view = Notebook::get().get_current_view()) { - auto undo_manager = view->get_source_buffer()->get_undo_manager(); - if (undo_manager->can_redo()) { - view->disable_spellcheck = true; - undo_manager->redo(); - view->disable_spellcheck = false; - view->scroll_to(view->get_buffer()->get_insert()); - } - } - }); + menu.add_action("file_print", [this]() { + if (auto view = Notebook::get().get_current_view()) { + auto print_operation = Gtk::PrintOperation::create(); + auto print_compositor = Gsv::PrintCompositor::create(*view); + + print_operation->set_job_name(view->file_path.filename().string()); + print_compositor->set_wrap_mode(Gtk::WrapMode::WRAP_WORD_CHAR); + + print_operation->signal_begin_print().connect( + [print_operation, print_compositor](const Glib::RefPtr<Gtk::PrintContext> &print_context) { + while (!print_compositor->paginate(print_context)); + print_operation->set_n_pages(print_compositor->get_n_pages()); + }); + print_operation->signal_draw_page().connect( + [print_compositor](const Glib::RefPtr<Gtk::PrintContext> &print_context, int page_nr) { + print_compositor->draw_page(print_context, page_nr); + }); + + print_operation->run(Gtk::PRINT_OPERATION_ACTION_PRINT_DIALOG, *this); + } + }); - menu.add_action("edit_cut", [this]() { - // Return if a shown tooltip has selected text - for (auto tooltip: Tooltips::shown_tooltips) { - auto buffer = tooltip->text_buffer; - if (buffer && buffer->get_has_selection()) - return; - } + menu.add_action("edit_undo", []() { + if (auto view = Notebook::get().get_current_view()) { + auto undo_manager = view->get_source_buffer()->get_undo_manager(); + if (undo_manager->can_undo()) { + view->disable_spellcheck = true; + undo_manager->undo(); + view->disable_spellcheck = false; + view->scroll_to(view->get_buffer()->get_insert()); + } + } + }); + menu.add_action("edit_redo", []() { + if (auto view = Notebook::get().get_current_view()) { + auto undo_manager = view->get_source_buffer()->get_undo_manager(); + if (undo_manager->can_redo()) { + view->disable_spellcheck = true; + undo_manager->redo(); + view->disable_spellcheck = false; + view->scroll_to(view->get_buffer()->get_insert()); + } + } + }); - auto widget = get_focus(); - if (auto entry = dynamic_cast<Gtk::Entry *>(widget)) - entry->cut_clipboard(); - else if (auto view = dynamic_cast<Gtk::TextView *>(widget)) { - if (!view->get_editable()) - return; - auto source_view = dynamic_cast<Source::View *>(view); - if (source_view) - source_view->disable_spellcheck = true; - if (!view->get_buffer()->get_has_selection()) { - auto start = view->get_buffer()->get_iter_at_line( - view->get_buffer()->get_insert()->get_iter().get_line()); - auto end = start; - if (!end.ends_line()) - end.forward_to_line_end(); - end.forward_char(); - if (!end.starts_line()) // In case of \r\n - end.forward_char(); - Gtk::Clipboard::get()->set_text(view->get_buffer()->get_text(start, end)); - view->get_buffer()->erase(start, end); - } else - view->get_buffer()->cut_clipboard(Gtk::Clipboard::get()); - if (source_view) - source_view->disable_spellcheck = false; - } - }); - menu.add_action("edit_copy", [this]() { - // Copy from a tooltip if it has selected text - for (auto tooltip: Tooltips::shown_tooltips) { - auto buffer = tooltip->text_buffer; - if (buffer && buffer->get_has_selection()) { - buffer->copy_clipboard(Gtk::Clipboard::get()); - return; - } - } + menu.add_action("edit_cut", [this]() { + // Return if a shown tooltip has selected text + for (auto tooltip: Tooltips::shown_tooltips) { + auto buffer = tooltip->text_buffer; + if (buffer && buffer->get_has_selection()) + return; + } - auto widget = get_focus(); - if (auto entry = dynamic_cast<Gtk::Entry *>(widget)) - entry->copy_clipboard(); - else if (auto view = dynamic_cast<Gtk::TextView *>(widget)) { - if (!view->get_buffer()->get_has_selection()) { - auto start = view->get_buffer()->get_iter_at_line( - view->get_buffer()->get_insert()->get_iter().get_line()); - auto end = start; - if (!end.ends_line()) - end.forward_to_line_end(); - end.forward_char(); - if (!end.starts_line()) // In case of \r\n - end.forward_char(); - Gtk::Clipboard::get()->set_text(view->get_buffer()->get_text(start, end)); - } else - view->get_buffer()->copy_clipboard(Gtk::Clipboard::get()); - } - }); - menu.add_action("edit_paste", [this]() { - auto widget = get_focus(); - if (auto entry = dynamic_cast<Gtk::Entry *>(widget)) - entry->paste_clipboard(); - else if (auto view = dynamic_cast<Gtk::TextView *>(widget)) { - auto source_view = dynamic_cast<Source::View *>(view); - if (source_view) { - source_view->disable_spellcheck = true; - source_view->paste(); - source_view->disable_spellcheck = false; - } else if (view->get_editable()) - view->get_buffer()->paste_clipboard(Gtk::Clipboard::get()); - } - }); + auto widget = get_focus(); + if (auto entry = dynamic_cast<Gtk::Entry *>(widget)) + entry->cut_clipboard(); + else if (auto view = dynamic_cast<Gtk::TextView *>(widget)) { + if (!view->get_editable()) + return; + auto source_view = dynamic_cast<Source::View *>(view); + if (source_view) + source_view->disable_spellcheck = true; + if (!view->get_buffer()->get_has_selection()) { + auto start = view->get_buffer()->get_iter_at_line( + view->get_buffer()->get_insert()->get_iter().get_line()); + auto end = start; + if (!end.ends_line()) + end.forward_to_line_end(); + end.forward_char(); + if (!end.starts_line()) // In case of \r\n + end.forward_char(); + Gtk::Clipboard::get()->set_text(view->get_buffer()->get_text(start, end)); + view->get_buffer()->erase(start, end); + } else + view->get_buffer()->cut_clipboard(Gtk::Clipboard::get()); + if (source_view) + source_view->disable_spellcheck = false; + } + }); + menu.add_action("edit_copy", [this]() { + // Copy from a tooltip if it has selected text + for (auto tooltip: Tooltips::shown_tooltips) { + auto buffer = tooltip->text_buffer; + if (buffer && buffer->get_has_selection()) { + buffer->copy_clipboard(Gtk::Clipboard::get()); + return; + } + } - menu.add_action("edit_find", [this]() { - search_and_replace_entry(); - }); + auto widget = get_focus(); + if (auto entry = dynamic_cast<Gtk::Entry *>(widget)) + entry->copy_clipboard(); + else if (auto view = dynamic_cast<Gtk::TextView *>(widget)) { + if (!view->get_buffer()->get_has_selection()) { + auto start = view->get_buffer()->get_iter_at_line( + view->get_buffer()->get_insert()->get_iter().get_line()); + auto end = start; + if (!end.ends_line()) + end.forward_to_line_end(); + end.forward_char(); + if (!end.starts_line()) // In case of \r\n + end.forward_char(); + Gtk::Clipboard::get()->set_text(view->get_buffer()->get_text(start, end)); + } else + view->get_buffer()->copy_clipboard(Gtk::Clipboard::get()); + } + }); + menu.add_action("edit_paste", [this]() { + auto widget = get_focus(); + if (auto entry = dynamic_cast<Gtk::Entry *>(widget)) + entry->paste_clipboard(); + else if (auto view = dynamic_cast<Gtk::TextView *>(widget)) { + auto source_view = dynamic_cast<Source::View *>(view); + if (source_view) { + source_view->disable_spellcheck = true; + source_view->paste(); + source_view->disable_spellcheck = false; + } else if (view->get_editable()) + view->get_buffer()->paste_clipboard(Gtk::Clipboard::get()); + } + }); - menu.add_action("source_spellcheck", []() { - if (auto view = Notebook::get().get_current_view()) { - view->remove_spellcheck_errors(); - view->spellcheck(); - } - }); - menu.add_action("source_spellcheck_clear", []() { - if (auto view = Notebook::get().get_current_view()) - view->remove_spellcheck_errors(); - }); - menu.add_action("source_spellcheck_next_error", []() { - if (auto view = Notebook::get().get_current_view()) - view->goto_next_spellcheck_error(); - }); + menu.add_action("edit_find", [this]() { + search_and_replace_entry(); + }); - menu.add_action("source_git_next_diff", []() { - if (auto view = Notebook::get().get_current_view()) - view->git_goto_next_diff(); - }); - menu.add_action("source_git_show_diff", []() { - if (auto view = Notebook::get().get_current_view()) { - auto diff_details = view->git_get_diff_details(); - if (diff_details.empty()) - return; - std::string postfix; - if (diff_details.size() > 2) { - size_t pos = diff_details.find("@@", 2); - if (pos != std::string::npos) - postfix = diff_details.substr(0, pos + 2); - } - Notebook::get().open(view->file_path.string() + postfix + ".diff"); - if (auto new_view = Notebook::get().get_current_view()) { - if (new_view->get_buffer()->get_text().empty()) { - new_view->get_source_buffer()->begin_not_undoable_action(); - new_view->get_buffer()->set_text(diff_details); - new_view->get_source_buffer()->end_not_undoable_action(); - new_view->get_buffer()->set_modified(false); - } - } - } - }); + menu.add_action("source_spellcheck", []() { + if (auto view = Notebook::get().get_current_view()) { + view->remove_spellcheck_errors(); + view->spellcheck(); + } + }); + menu.add_action("source_spellcheck_clear", []() { + if (auto view = Notebook::get().get_current_view()) + view->remove_spellcheck_errors(); + }); + menu.add_action("source_spellcheck_next_error", []() { + if (auto view = Notebook::get().get_current_view()) + view->goto_next_spellcheck_error(); + }); - menu.add_action("source_indentation_set_buffer_tab", [this]() { - set_tab_entry(); - }); - menu.add_action("source_indentation_auto_indent_buffer", []() { - auto view = Notebook::get().get_current_view(); - if (view && view->format_style) { - view->disable_spellcheck = true; - view->format_style(true); - view->disable_spellcheck = false; + menu.add_action("source_git_next_diff", []() { + if (auto view = Notebook::get().get_current_view()) + view->git_goto_next_diff(); + }); + menu.add_action("source_git_show_diff", []() { + if (auto view = Notebook::get().get_current_view()) { + auto diff_details = view->git_get_diff_details(); + if (diff_details.empty()) + return; + std::string postfix; + if (diff_details.size() > 2) { + size_t pos = diff_details.find("@@", 2); + if (pos != std::string::npos) + postfix = diff_details.substr(0, pos + 2); + } + Notebook::get().open(view->file_path.string() + postfix + ".diff"); + if (auto new_view = Notebook::get().get_current_view()) { + if (new_view->get_buffer()->get_text().empty()) { + new_view->get_source_buffer()->begin_not_undoable_action(); + new_view->get_buffer()->set_text(diff_details); + new_view->get_source_buffer()->end_not_undoable_action(); + new_view->get_buffer()->set_modified(false); } - }); + } + } + }); + + menu.add_action("source_indentation_set_buffer_tab", [this]() { + set_tab_entry(); + }); + menu.add_action("source_indentation_auto_indent_buffer", []() { + auto view = Notebook::get().get_current_view(); + if (view && view->format_style) { + view->disable_spellcheck = true; + view->format_style(true); + view->disable_spellcheck = false; + } + }); - menu.add_action("source_goto_line", [this]() { - goto_line_entry(); - }); - menu.add_action("source_center_cursor", []() { - if (auto view = Notebook::get().get_current_view()) - view->scroll_to_cursor_delayed(view, true, false); - }); - menu.add_action("source_cursor_history_back", []() { - if (Notebook::get().cursor_locations.size() == 0) - return; - if (Notebook::get().current_cursor_location == static_cast<size_t>(-1)) - Notebook::get().current_cursor_location = Notebook::get().cursor_locations.size() - 1; - - auto cursor_location = &Notebook::get().cursor_locations.at(Notebook::get().current_cursor_location); - // Move to current position if current position's view is not current view - // (in case one is looking at a new file but has not yet placed the cursor within the file) - if (cursor_location->view != Notebook::get().get_current_view()) - Notebook::get().open(cursor_location->view->file_path); - else { - if (Notebook::get().cursor_locations.size() <= 1) - return; - if (Notebook::get().current_cursor_location == 0) - return; - - --Notebook::get().current_cursor_location; - cursor_location = &Notebook::get().cursor_locations.at(Notebook::get().current_cursor_location); - if (Notebook::get().get_current_view() != cursor_location->view) - Notebook::get().open(cursor_location->view->file_path); - } - Notebook::get().disable_next_update_cursor_locations = true; - cursor_location->view->get_buffer()->place_cursor(cursor_location->mark->get_iter()); - cursor_location->view->scroll_to_cursor_delayed(cursor_location->view, true, false); - }); - menu.add_action("source_cursor_history_forward", []() { - if (Notebook::get().cursor_locations.size() <= 1) - return; - if (Notebook::get().current_cursor_location == static_cast<size_t>(-1)) - Notebook::get().current_cursor_location = Notebook::get().cursor_locations.size() - 1; - if (Notebook::get().current_cursor_location == Notebook::get().cursor_locations.size() - 1) - return; + menu.add_action("source_goto_line", [this]() { + goto_line_entry(); + }); + menu.add_action("source_center_cursor", []() { + if (auto view = Notebook::get().get_current_view()) + view->scroll_to_cursor_delayed(view, true, false); + }); + menu.add_action("source_cursor_history_back", []() { + if (Notebook::get().cursor_locations.size() == 0) + return; + if (Notebook::get().current_cursor_location == static_cast<size_t>(-1)) + Notebook::get().current_cursor_location = Notebook::get().cursor_locations.size() - 1; + + auto cursor_location = &Notebook::get().cursor_locations.at(Notebook::get().current_cursor_location); + // Move to current position if current position's view is not current view + // (in case one is looking at a new file but has not yet placed the cursor within the file) + if (cursor_location->view != Notebook::get().get_current_view()) + Notebook::get().open(cursor_location->view->file_path); + else { + if (Notebook::get().cursor_locations.size() <= 1) + return; + if (Notebook::get().current_cursor_location == 0) + return; - ++Notebook::get().current_cursor_location; - auto &cursor_location = Notebook::get().cursor_locations.at(Notebook::get().current_cursor_location); - if (Notebook::get().get_current_view() != cursor_location.view) - Notebook::get().open(cursor_location.view->file_path); - Notebook::get().disable_next_update_cursor_locations = true; - cursor_location.view->get_buffer()->place_cursor(cursor_location.mark->get_iter()); - cursor_location.view->scroll_to_cursor_delayed(cursor_location.view, true, false); - }); + --Notebook::get().current_cursor_location; + cursor_location = &Notebook::get().cursor_locations.at(Notebook::get().current_cursor_location); + if (Notebook::get().get_current_view() != cursor_location->view) + Notebook::get().open(cursor_location->view->file_path); + } + Notebook::get().disable_next_update_cursor_locations = true; + cursor_location->view->get_buffer()->place_cursor(cursor_location->mark->get_iter()); + cursor_location->view->scroll_to_cursor_delayed(cursor_location->view, true, false); + }); + menu.add_action("source_cursor_history_forward", []() { + if (Notebook::get().cursor_locations.size() <= 1) + return; + if (Notebook::get().current_cursor_location == static_cast<size_t>(-1)) + Notebook::get().current_cursor_location = Notebook::get().cursor_locations.size() - 1; + if (Notebook::get().current_cursor_location == Notebook::get().cursor_locations.size() - 1) + return; + + ++Notebook::get().current_cursor_location; + auto &cursor_location = Notebook::get().cursor_locations.at(Notebook::get().current_cursor_location); + if (Notebook::get().get_current_view() != cursor_location.view) + Notebook::get().open(cursor_location.view->file_path); + Notebook::get().disable_next_update_cursor_locations = true; + cursor_location.view->get_buffer()->place_cursor(cursor_location.mark->get_iter()); + cursor_location.view->scroll_to_cursor_delayed(cursor_location.view, true, false); + }); + + menu.add_action("source_show_completion", [] { + if (auto view = Notebook::get().get_current_view()) { + if (view->non_interactive_completion) + view->non_interactive_completion(); + else + g_signal_emit_by_name(view->gobj(), "show-completion"); + } + }); + + menu.add_action("source_find_symbol", []() { + auto project = Project::create(); + project->show_symbols(); + }); + + menu.add_action("source_find_file", []() { + auto view = Notebook::get().get_current_view(); + + boost::filesystem::path search_path; + if (view) + search_path = view->file_path.parent_path(); + else if (!Directories::get().path.empty()) + search_path = Directories::get().path; + else { + boost::system::error_code ec; + search_path = boost::filesystem::current_path(ec); + if (ec) { + Terminal::get().print("Error: could not find current path\n", true); + return; + } + } + auto build = Project::Build::create(search_path); + auto project_path = build->project_path; + boost::filesystem::path default_path, debug_path; + if (!project_path.empty()) { + search_path = project_path; + default_path = build->get_default_path(); + debug_path = build->get_debug_path(); + } - menu.add_action("source_show_completion", [] { - if (auto view = Notebook::get().get_current_view()) { - if (view->non_interactive_completion) - view->non_interactive_completion(); - else - g_signal_emit_by_name(view->gobj(), "show-completion"); - } - }); + if (view) { + auto dialog_iter = view->get_iter_for_dialog(); + SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); + } else + SelectionDialog::create(true, true); + + std::unordered_set<std::string> buffer_paths; + for (auto view: Notebook::get().get_views()) + buffer_paths.emplace(view->file_path.string()); + + std::vector<boost::filesystem::path> paths; + // populate with all files in search_path + for (boost::filesystem::recursive_directory_iterator iter(search_path), end; iter != end; iter++) { + auto path = iter->path(); + // ignore folders + if (!boost::filesystem::is_regular_file(path)) { + if (path == default_path || path == debug_path || path.filename() == ".git") + iter.no_push(); + continue; + } - menu.add_action("source_find_symbol", []() { - auto project = Project::create(); - project->show_symbols(); - }); + // remove project base path + auto row_str = filesystem::get_relative_path(path, search_path).string(); + if (buffer_paths.count(path.string())) + row_str = "<b>" + row_str + "</b>"; + paths.emplace_back(path); + SelectionDialog::get()->add_row(row_str); + } - menu.add_action("source_find_file", []() { - auto view = Notebook::get().get_current_view(); + if (paths.empty()) { + Info::get().print("No files found in current project"); + return; + } - boost::filesystem::path search_path; - if (view) - search_path = view->file_path.parent_path(); - else if (!Directories::get().path.empty()) - search_path = Directories::get().path; - else { - boost::system::error_code ec; - search_path = boost::filesystem::current_path(ec); - if (ec) { - Terminal::get().print("Error: could not find current path\n", true); - return; - } - } - auto build = Project::Build::create(search_path); - auto project_path = build->project_path; - boost::filesystem::path default_path, debug_path; - if (!project_path.empty()) { - search_path = project_path; - default_path = build->get_default_path(); - debug_path = build->get_debug_path(); - } + SelectionDialog::get()->on_select = [paths = std::move(paths)](unsigned int index, const std::string &text, + bool hide_window) { + if (index >= paths.size()) + return; + Notebook::get().open(paths[index]); + if (auto view = Notebook::get().get_current_view()) + view->hide_tooltips(); + }; - if (view) { - auto dialog_iter = view->get_iter_for_dialog(); - SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); - } else - SelectionDialog::create(true, true); - - std::unordered_set<std::string> buffer_paths; - for (auto view: Notebook::get().get_views()) - buffer_paths.emplace(view->file_path.string()); - - std::vector<boost::filesystem::path> paths; - // populate with all files in search_path - for (boost::filesystem::recursive_directory_iterator iter(search_path), end; iter != end; iter++) { - auto path = iter->path(); - // ignore folders - if (!boost::filesystem::is_regular_file(path)) { - if (path == default_path || path == debug_path || path.filename() == ".git") - iter.no_push(); - continue; - } + if (view) + view->hide_tooltips(); + SelectionDialog::get()->show(); - // remove project base path - auto row_str = filesystem::get_relative_path(path, search_path).string(); - if (buffer_paths.count(path.string())) - row_str = "<b>" + row_str + "</b>"; - paths.emplace_back(path); - SelectionDialog::get()->add_row(row_str); - } + }); - if (paths.empty()) { - Info::get().print("No files found in current project"); + menu.add_action("source_comments_toggle", []() { + if (auto view = Notebook::get().get_current_view()) { + if (view->toggle_comments) { + view->toggle_comments(); + } + } + }); + menu.add_action("source_comments_add_documentation", []() { + if (auto view = Notebook::get().get_current_view()) { + if (view->get_documentation_template) { + auto documentation_template = view->get_documentation_template(); + auto offset = std::get<0>(documentation_template); + if (offset) { + if (!boost::filesystem::is_regular_file(offset.file_path)) return; + Notebook::get().open(offset.file_path); + auto view = Notebook::get().get_current_view(); + auto iter = view->get_iter_at_line_pos(offset.line, offset.index); + view->get_buffer()->insert(iter, std::get<1>(documentation_template)); + iter = view->get_iter_at_line_pos(offset.line, offset.index); + iter.forward_chars(std::get<2>(documentation_template)); + view->get_buffer()->place_cursor(iter); + view->scroll_to_cursor_delayed(view, true, false); } - - SelectionDialog::get()->on_select = [paths = std::move(paths)](unsigned int index, const std::string &text, - bool hide_window) { - if (index >= paths.size()) - return; - Notebook::get().open(paths[index]); - if (auto view = Notebook::get().get_current_view()) - view->hide_tooltips(); - }; - - if (view) - view->hide_tooltips(); - SelectionDialog::get()->show(); - - }); - - menu.add_action("source_comments_toggle", []() { - if (auto view = Notebook::get().get_current_view()) { - if (view->toggle_comments) { - view->toggle_comments(); + } + } + }); + menu.add_action("source_find_documentation", []() { + if (auto view = Notebook::get().get_current_view()) { + if (view->get_token_data) { + auto data = view->get_token_data(); + std::string url; + if (data.size() == 1) + url = data[0]; + else if (data.size() > 1) { + auto documentation_search = Config::get().source.documentation_searches.find(data[0]); + if (documentation_search != Config::get().source.documentation_searches.end()) { + std::string token_query; + for (size_t c = 1; c < data.size(); c++) { + if (data[c].size() > 0) { + if (token_query.size() > 0) + token_query += documentation_search->second.separator; + token_query += data[c]; + } } - } - }); - menu.add_action("source_comments_add_documentation", []() { - if (auto view = Notebook::get().get_current_view()) { - if (view->get_documentation_template) { - auto documentation_template = view->get_documentation_template(); - auto offset = std::get<0>(documentation_template); - if (offset) { - if (!boost::filesystem::is_regular_file(offset.file_path)) - return; - Notebook::get().open(offset.file_path); - auto view = Notebook::get().get_current_view(); - auto iter = view->get_iter_at_line_pos(offset.line, offset.index); - view->get_buffer()->insert(iter, std::get<1>(documentation_template)); - iter = view->get_iter_at_line_pos(offset.line, offset.index); - iter.forward_chars(std::get<2>(documentation_template)); - view->get_buffer()->place_cursor(iter); - view->scroll_to_cursor_delayed(view, true, false); - } + if (token_query.size() > 0) { + std::unordered_map<std::string, std::string>::iterator query; + if (data[1].size() > 0) + query = documentation_search->second.queries.find(data[1]); + else + query = documentation_search->second.queries.find("@empty"); + if (query == documentation_search->second.queries.end()) + query = documentation_search->second.queries.find("@any"); + + if (query != documentation_search->second.queries.end()) + url = query->second + token_query; } + } } - }); - menu.add_action("source_find_documentation", []() { - if (auto view = Notebook::get().get_current_view()) { - if (view->get_token_data) { - auto data = view->get_token_data(); - std::string url; - if (data.size() == 1) - url = data[0]; - else if (data.size() > 1) { - auto documentation_search = Config::get().source.documentation_searches.find(data[0]); - if (documentation_search != Config::get().source.documentation_searches.end()) { - std::string token_query; - for (size_t c = 1; c < data.size(); c++) { - if (data[c].size() > 0) { - if (token_query.size() > 0) - token_query += documentation_search->second.separator; - token_query += data[c]; - } - } - if (token_query.size() > 0) { - std::unordered_map<std::string, std::string>::iterator query; - if (data[1].size() > 0) - query = documentation_search->second.queries.find(data[1]); - else - query = documentation_search->second.queries.find("@empty"); - if (query == documentation_search->second.queries.end()) - query = documentation_search->second.queries.find("@any"); - - if (query != documentation_search->second.queries.end()) - url = query->second + token_query; - } - } - } - if (!url.empty()) { + if (!url.empty()) { #ifdef __APPLE__ - Terminal::get().process("open \""+url+"\""); + Terminal::get().process("open \""+url+"\""); #else - GError *error = nullptr; + GError *error = nullptr; #if GTK_VERSION_GE(3, 22) - gtk_show_uri_on_window(nullptr, url.c_str(), GDK_CURRENT_TIME, &error); + gtk_show_uri_on_window(nullptr, url.c_str(), GDK_CURRENT_TIME, &error); #else - gtk_show_uri(nullptr, url.c_str(), GDK_CURRENT_TIME, &error); + gtk_show_uri(nullptr, url.c_str(), GDK_CURRENT_TIME, &error); #endif - g_clear_error(&error); + g_clear_error(&error); #endif - } - } } - }); - - menu.add_action("source_goto_declaration", []() { - if (auto view = Notebook::get().get_current_view()) { - if (view->get_declaration_location) { - auto location = view->get_declaration_location(); - if (location) { - if (!boost::filesystem::is_regular_file(location.file_path)) - return; - Notebook::get().open(location.file_path); - auto view = Notebook::get().get_current_view(); - auto line = static_cast<int>(location.line); - auto index = static_cast<int>(location.index); - view->place_cursor_at_line_pos(line, index); - view->scroll_to_cursor_delayed(view, true, false); - } - } - } - }); - menu.add_action("source_goto_type_declaration", []() { - if (auto view = Notebook::get().get_current_view()) { - if (view->get_type_declaration_location) { - auto location = view->get_type_declaration_location(); - if (location) { - if (!boost::filesystem::is_regular_file(location.file_path)) - return; - Notebook::get().open(location.file_path); - auto view = Notebook::get().get_current_view(); - auto line = static_cast<int>(location.line); - auto index = static_cast<int>(location.index); - view->place_cursor_at_line_pos(line, index); - view->scroll_to_cursor_delayed(view, true, false); - } - } - } - }); - auto goto_selected_location = [](Source::View *view, const std::vector<Source::Offset> &locations) { - if (!locations.empty()) { - auto dialog_iter = view->get_iter_for_dialog(); - SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); - std::vector<Source::Offset> rows; - auto project_path = Project::Build::create(view->file_path)->project_path; - if (project_path.empty()) { - if (!Directories::get().path.empty()) - project_path = Directories::get().path; - else - project_path = view->file_path.parent_path(); - } - for (auto &location: locations) { - auto path = filesystem::get_relative_path(filesystem::get_normal_path(location.file_path), - project_path); - if (path.empty()) - path = location.file_path.filename(); - auto row = path.string() + ":" + std::to_string(location.line + 1); - rows.emplace_back(location); - SelectionDialog::get()->add_row(row); - } + } + } + }); - if (rows.size() == 0) - return; - else if (rows.size() == 1) { - auto location = *rows.begin(); - if (!boost::filesystem::is_regular_file(location.file_path)) - return; - Notebook::get().open(location.file_path); - auto view = Notebook::get().get_current_view(); - auto line = static_cast<int>(location.line); - auto index = static_cast<int>(location.index); - view->place_cursor_at_line_pos(line, index); - view->scroll_to_cursor_delayed(view, true, false); - return; - } - SelectionDialog::get()->on_select = [rows = std::move(rows)](unsigned int index, const std::string &text, - bool hide_window) { - if (index >= rows.size()) - return; - auto location = rows[index]; - if (!boost::filesystem::is_regular_file(location.file_path)) - return; - Notebook::get().open(location.file_path); - auto view = Notebook::get().get_current_view(); - view->place_cursor_at_line_pos(location.line, location.index); - view->scroll_to_cursor_delayed(view, true, false); - view->hide_tooltips(); - }; - view->hide_tooltips(); - SelectionDialog::get()->show(); - } - }; - menu.add_action("source_goto_implementation", [goto_selected_location]() { - if (auto view = Notebook::get().get_current_view()) { - if (view->get_implementation_locations) - goto_selected_location(view, view->get_implementation_locations()); + menu.add_action("source_goto_declaration", []() { + if (auto view = Notebook::get().get_current_view()) { + if (view->get_declaration_location) { + auto location = view->get_declaration_location(); + if (location) { + if (!boost::filesystem::is_regular_file(location.file_path)) + return; + Notebook::get().open(location.file_path); + auto view = Notebook::get().get_current_view(); + auto line = static_cast<int>(location.line); + auto index = static_cast<int>(location.index); + view->place_cursor_at_line_pos(line, index); + view->scroll_to_cursor_delayed(view, true, false); } - }); - menu.add_action("source_goto_declaration_or_implementation", [goto_selected_location]() { - if (auto view = Notebook::get().get_current_view()) { - if (view->get_declaration_or_implementation_locations) - goto_selected_location(view, view->get_declaration_or_implementation_locations()); + } + } + }); + menu.add_action("source_goto_type_declaration", []() { + if (auto view = Notebook::get().get_current_view()) { + if (view->get_type_declaration_location) { + auto location = view->get_type_declaration_location(); + if (location) { + if (!boost::filesystem::is_regular_file(location.file_path)) + return; + Notebook::get().open(location.file_path); + auto view = Notebook::get().get_current_view(); + auto line = static_cast<int>(location.line); + auto index = static_cast<int>(location.index); + view->place_cursor_at_line_pos(line, index); + view->scroll_to_cursor_delayed(view, true, false); } - }); + } + } + }); + auto goto_selected_location = [](Source::View *view, const std::vector<Source::Offset> &locations) { + if (!locations.empty()) { + auto dialog_iter = view->get_iter_for_dialog(); + SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); + std::vector<Source::Offset> rows; + auto project_path = Project::Build::create(view->file_path)->project_path; + if (project_path.empty()) { + if (!Directories::get().path.empty()) + project_path = Directories::get().path; + else + project_path = view->file_path.parent_path(); + } + for (auto &location: locations) { + auto path = filesystem::get_relative_path(filesystem::get_normal_path(location.file_path), + project_path); + if (path.empty()) + path = location.file_path.filename(); + auto row = path.string() + ":" + std::to_string(location.line + 1); + rows.emplace_back(location); + SelectionDialog::get()->add_row(row); + } - menu.add_action("source_goto_usage", []() { - if (auto view = Notebook::get().get_current_view()) { - if (view->get_usages) { - auto usages = view->get_usages(); - if (!usages.empty()) { - auto dialog_iter = view->get_iter_for_dialog(); - SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); - std::vector<Source::Offset> rows; - - auto iter = view->get_buffer()->get_insert()->get_iter(); - for (auto &usage: usages) { - std::string row; - bool current_page = true; - //add file name if usage is not in current page - if (view->file_path != usage.first.file_path) { - row = usage.first.file_path.filename().string() + ":"; - current_page = false; - } - row += std::to_string(usage.first.line + 1) + ": " + usage.second; - rows.emplace_back(usage.first); - SelectionDialog::get()->add_row(row); - - //Set dialog cursor to the last row if the textview cursor is at the same line - if (current_page && - iter.get_line() == static_cast<int>(usage.first.line) && - iter.get_line_index() >= static_cast<int>(usage.first.index)) { - SelectionDialog::get()->set_cursor_at_last_row(); - } - } - - if (rows.size() == 0) - return; - SelectionDialog::get()->on_select = [rows = std::move(rows)](unsigned int index, - const std::string &text, - bool hide_window) { - if (index >= rows.size()) - return; - auto offset = rows[index]; - if (!boost::filesystem::is_regular_file(offset.file_path)) - return; - Notebook::get().open(offset.file_path); - auto view = Notebook::get().get_current_view(); - view->place_cursor_at_line_pos(offset.line, offset.index); - view->scroll_to_cursor_delayed(view, true, false); - view->hide_tooltips(); - }; - view->hide_tooltips(); - SelectionDialog::get()->show(); - } - } - } - }); - menu.add_action("source_goto_method", []() { - if (auto view = Notebook::get().get_current_view()) { - if (view->get_methods) { - auto methods = Notebook::get().get_current_view()->get_methods(); - if (!methods.empty()) { - auto dialog_iter = view->get_iter_for_dialog(); - SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); - std::vector<Source::Offset> rows; - auto iter = view->get_buffer()->get_insert()->get_iter(); - for (auto &method: methods) { - rows.emplace_back(method.first); - SelectionDialog::get()->add_row(method.second); - if (iter.get_line() >= static_cast<int>(method.first.line)) - SelectionDialog::get()->set_cursor_at_last_row(); - } - SelectionDialog::get()->on_select = [view, rows = std::move(rows)](unsigned int index, - const std::string &text, - bool hide_window) { - if (index >= rows.size()) - return; - auto offset = rows[index]; - view->get_buffer()->place_cursor(view->get_iter_at_line_pos(offset.line, offset.index)); - view->scroll_to(view->get_buffer()->get_insert(), 0.0, 1.0, 0.5); - view->hide_tooltips(); - }; - view->hide_tooltips(); - SelectionDialog::get()->show(); - } - } - } - }); - menu.add_action("source_rename", [this]() { - rename_token_entry(); - }); - menu.add_action("source_implement_method", []() { - const static std::string button_text = "Insert Method Implementation"; - - if (auto view = Notebook::get().get_current_view()) { - if (view->get_method) { - auto iter = view->get_buffer()->get_insert()->get_iter(); - if (!EntryBox::get().buttons.empty() && EntryBox::get().buttons.back().get_label() == button_text && - iter.ends_line() && iter.starts_line()) { - EntryBox::get().buttons.back().activate(); - return; - } - auto method = view->get_method(); - if (method.empty()) - return; - - EntryBox::get().clear(); - EntryBox::get().labels.emplace_back(); - EntryBox::get().labels.back().set_text(method); - EntryBox::get().buttons.emplace_back(button_text, [method = std::move(method)]() { - if (auto view = Notebook::get().get_current_view()) { - view->get_buffer()->insert_at_cursor(method); - EntryBox::get().clear(); - } - }); - EntryBox::get().show(); - } - } - }); + if (rows.size() == 0) + return; + else if (rows.size() == 1) { + auto location = *rows.begin(); + if (!boost::filesystem::is_regular_file(location.file_path)) + return; + Notebook::get().open(location.file_path); + auto view = Notebook::get().get_current_view(); + auto line = static_cast<int>(location.line); + auto index = static_cast<int>(location.index); + view->place_cursor_at_line_pos(line, index); + view->scroll_to_cursor_delayed(view, true, false); + return; + } + SelectionDialog::get()->on_select = [rows = std::move(rows)](unsigned int index, const std::string &text, + bool hide_window) { + if (index >= rows.size()) + return; + auto location = rows[index]; + if (!boost::filesystem::is_regular_file(location.file_path)) + return; + Notebook::get().open(location.file_path); + auto view = Notebook::get().get_current_view(); + view->place_cursor_at_line_pos(location.line, location.index); + view->scroll_to_cursor_delayed(view, true, false); + view->hide_tooltips(); + }; + view->hide_tooltips(); + SelectionDialog::get()->show(); + } + }; + menu.add_action("source_goto_implementation", [goto_selected_location]() { + if (auto view = Notebook::get().get_current_view()) { + if (view->get_implementation_locations) + goto_selected_location(view, view->get_implementation_locations()); + } + }); + menu.add_action("source_goto_declaration_or_implementation", [goto_selected_location]() { + if (auto view = Notebook::get().get_current_view()) { + if (view->get_declaration_or_implementation_locations) + goto_selected_location(view, view->get_declaration_or_implementation_locations()); + } + }); - menu.add_action("source_goto_next_diagnostic", []() { - if (auto view = Notebook::get().get_current_view()) { - if (view->goto_next_diagnostic) { - view->goto_next_diagnostic(); + menu.add_action("source_goto_usage", []() { + if (auto view = Notebook::get().get_current_view()) { + if (view->get_usages) { + auto usages = view->get_usages(); + if (!usages.empty()) { + auto dialog_iter = view->get_iter_for_dialog(); + SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); + std::vector<Source::Offset> rows; + + auto iter = view->get_buffer()->get_insert()->get_iter(); + for (auto &usage: usages) { + std::string row; + bool current_page = true; + //add file name if usage is not in current page + if (view->file_path != usage.first.file_path) { + row = usage.first.file_path.filename().string() + ":"; + current_page = false; } - } - }); - menu.add_action("source_apply_fix_its", []() { - if (auto view = Notebook::get().get_current_view()) { - if (view->get_fix_its) { - auto buffer = view->get_buffer(); - auto fix_its = view->get_fix_its(); - std::vector<std::pair<Glib::RefPtr<Gtk::TextMark>, Glib::RefPtr<Gtk::TextMark> > > fix_it_marks; - for (auto &fix_it: fix_its) { - auto start_iter = view->get_iter_at_line_pos(fix_it.offsets.first.line, fix_it.offsets.first.index); - auto end_iter = view->get_iter_at_line_pos(fix_it.offsets.second.line, fix_it.offsets.second.index); - fix_it_marks.emplace_back(buffer->create_mark(start_iter), buffer->create_mark(end_iter)); - } - size_t c = 0; - buffer->begin_user_action(); - for (auto &fix_it: fix_its) { - if (fix_it.type == Source::FixIt::Type::INSERT) { - buffer->insert(fix_it_marks[c].first->get_iter(), fix_it.source); - } - if (fix_it.type == Source::FixIt::Type::REPLACE) { - buffer->erase(fix_it_marks[c].first->get_iter(), fix_it_marks[c].second->get_iter()); - buffer->insert(fix_it_marks[c].first->get_iter(), fix_it.source); - } - if (fix_it.type == Source::FixIt::Type::ERASE) { - buffer->erase(fix_it_marks[c].first->get_iter(), fix_it_marks[c].second->get_iter()); - } - c++; - } - for (auto &mark_pair: fix_it_marks) { - buffer->delete_mark(mark_pair.first); - buffer->delete_mark(mark_pair.second); - } - buffer->end_user_action(); + row += std::to_string(usage.first.line + 1) + ": " + usage.second; + rows.emplace_back(usage.first); + SelectionDialog::get()->add_row(row); + + //Set dialog cursor to the last row if the textview cursor is at the same line + if (current_page && + iter.get_line() == static_cast<int>(usage.first.line) && + iter.get_line_index() >= static_cast<int>(usage.first.index)) { + SelectionDialog::get()->set_cursor_at_last_row(); } - } - }); + } - menu.add_action("project_set_run_arguments", []() { - auto project = Project::create(); - auto run_arguments = project->get_run_arguments(); - if (run_arguments.second.empty()) + if (rows.size() == 0) return; + SelectionDialog::get()->on_select = [rows = std::move(rows)](unsigned int index, + const std::string &text, + bool hide_window) { + if (index >= rows.size()) + return; + auto offset = rows[index]; + if (!boost::filesystem::is_regular_file(offset.file_path)) + return; + Notebook::get().open(offset.file_path); + auto view = Notebook::get().get_current_view(); + view->place_cursor_at_line_pos(offset.line, offset.index); + view->scroll_to_cursor_delayed(view, true, false); + view->hide_tooltips(); + }; + view->hide_tooltips(); + SelectionDialog::get()->show(); + } + } + } + }); + menu.add_action("source_goto_method", []() { + if (auto view = Notebook::get().get_current_view()) { + if (view->get_methods) { + auto methods = Notebook::get().get_current_view()->get_methods(); + if (!methods.empty()) { + auto dialog_iter = view->get_iter_for_dialog(); + SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); + std::vector<Source::Offset> rows; + auto iter = view->get_buffer()->get_insert()->get_iter(); + for (auto &method: methods) { + rows.emplace_back(method.first); + SelectionDialog::get()->add_row(method.second); + if (iter.get_line() >= static_cast<int>(method.first.line)) + SelectionDialog::get()->set_cursor_at_last_row(); + } + SelectionDialog::get()->on_select = [view, rows = std::move(rows)](unsigned int index, + const std::string &text, + bool hide_window) { + if (index >= rows.size()) + return; + auto offset = rows[index]; + view->get_buffer()->place_cursor(view->get_iter_at_line_pos(offset.line, offset.index)); + view->scroll_to(view->get_buffer()->get_insert(), 0.0, 1.0, 0.5); + view->hide_tooltips(); + }; + view->hide_tooltips(); + SelectionDialog::get()->show(); + } + } + } + }); + menu.add_action("source_rename", [this]() { + rename_token_entry(); + }); + menu.add_action("source_implement_method", []() { + const static std::string button_text = "Insert Method Implementation"; + + if (auto view = Notebook::get().get_current_view()) { + if (view->get_method) { + auto iter = view->get_buffer()->get_insert()->get_iter(); + if (!EntryBox::get().buttons.empty() && EntryBox::get().buttons.back().get_label() == button_text && + iter.ends_line() && iter.starts_line()) { + EntryBox::get().buttons.back().activate(); + return; + } + auto method = view->get_method(); + if (method.empty()) + return; EntryBox::get().clear(); EntryBox::get().labels.emplace_back(); - auto label_it = EntryBox::get().labels.begin(); - label_it->update = [label_it](int state, const std::string &message) { - label_it->set_text( - "Synopsis: [environment_variable=value]... executable [argument]...\nSet empty to let juCi++ deduce executable."); - }; - label_it->update(0, ""); - EntryBox::get().entries.emplace_back(run_arguments.second, - [run_arguments_first = std::move(run_arguments.first)]( - const std::string &content) { - Project::run_arguments[run_arguments_first] = content; - EntryBox::get().hide(); - }, 50); - auto entry_it = EntryBox::get().entries.begin(); - entry_it->set_placeholder_text("Project: Set Run Arguments"); - EntryBox::get().buttons.emplace_back("Project: set run arguments", [entry_it]() { - entry_it->activate(); + EntryBox::get().labels.back().set_text(method); + EntryBox::get().buttons.emplace_back(button_text, [method = std::move(method)]() { + if (auto view = Notebook::get().get_current_view()) { + view->get_buffer()->insert_at_cursor(method); + EntryBox::get().clear(); + } }); EntryBox::get().show(); - }); - menu.add_action("project_compile_and_run", []() { - if (Project::compiling || Project::debugging) { - Info::get().print("Compile or debug in progress"); - return; - } + } + } + }); - Project::current = Project::create(); + menu.add_action("source_goto_next_diagnostic", []() { + if (auto view = Notebook::get().get_current_view()) { + if (view->goto_next_diagnostic) { + view->goto_next_diagnostic(); + } + } + }); + menu.add_action("source_apply_fix_its", []() { + if (auto view = Notebook::get().get_current_view()) { + if (view->get_fix_its) { + auto buffer = view->get_buffer(); + auto fix_its = view->get_fix_its(); + std::vector<std::pair<Glib::RefPtr<Gtk::TextMark>, Glib::RefPtr<Gtk::TextMark> > > fix_it_marks; + for (auto &fix_it: fix_its) { + auto start_iter = view->get_iter_at_line_pos(fix_it.offsets.first.line, fix_it.offsets.first.index); + auto end_iter = view->get_iter_at_line_pos(fix_it.offsets.second.line, fix_it.offsets.second.index); + fix_it_marks.emplace_back(buffer->create_mark(start_iter), buffer->create_mark(end_iter)); + } + size_t c = 0; + buffer->begin_user_action(); + for (auto &fix_it: fix_its) { + if (fix_it.type == Source::FixIt::Type::INSERT) { + buffer->insert(fix_it_marks[c].first->get_iter(), fix_it.source); + } + if (fix_it.type == Source::FixIt::Type::REPLACE) { + buffer->erase(fix_it_marks[c].first->get_iter(), fix_it_marks[c].second->get_iter()); + buffer->insert(fix_it_marks[c].first->get_iter(), fix_it.source); + } + if (fix_it.type == Source::FixIt::Type::ERASE) { + buffer->erase(fix_it_marks[c].first->get_iter(), fix_it_marks[c].second->get_iter()); + } + c++; + } + for (auto &mark_pair: fix_it_marks) { + buffer->delete_mark(mark_pair.first); + buffer->delete_mark(mark_pair.second); + } + buffer->end_user_action(); + } + } + }); - if (Config::get().project.save_on_compile_or_run) - Project::save_files(Project::current->build->project_path); + menu.add_action("project_set_run_arguments", []() { + auto project = Project::create(); + auto run_arguments = project->get_run_arguments(); + if (run_arguments.second.empty()) + return; - Project::current->compile_and_run(); + EntryBox::get().clear(); + EntryBox::get().labels.emplace_back(); + auto label_it = EntryBox::get().labels.begin(); + label_it->update = [label_it](int state, const std::string &message) { + label_it->set_text( + "Synopsis: [environment_variable=value]... executable [argument]...\nSet empty to let juCi++ deduce executable."); + }; + label_it->update(0, ""); + EntryBox::get().entries.emplace_back(run_arguments.second, + [run_arguments_first = std::move(run_arguments.first)]( + const std::string &content) { + Project::run_arguments[run_arguments_first] = content; + EntryBox::get().hide(); + }, 50); + auto entry_it = EntryBox::get().entries.begin(); + entry_it->set_placeholder_text("Project: Set Run Arguments"); + EntryBox::get().buttons.emplace_back("Project: set run arguments", [entry_it]() { + entry_it->activate(); }); - menu.add_action("project_compile", []() { - if (Project::compiling || Project::debugging) { - Info::get().print("Compile or debug in progress"); - return; - } - - Project::current = Project::create(); + EntryBox::get().show(); + }); + menu.add_action("project_compile_and_run", []() { + if (Project::compiling || Project::debugging) { + Info::get().print("Compile or debug in progress"); + return; + } - if (Config::get().project.save_on_compile_or_run) - Project::save_files(Project::current->build->project_path); + Project::current = Project::create(); - Project::current->compile(); - }); - menu.add_action("project_recreate_build", []() { - if (Project::compiling || Project::debugging) { - Info::get().print("Compile or debug in progress"); - return; - } + if (Config::get().project.save_on_compile_or_run) + Project::save_files(Project::current->build->project_path); - Project::current = Project::create(); + Project::current->compile_and_run(); + }); + menu.add_action("project_compile", []() { + if (Project::compiling || Project::debugging) { + Info::get().print("Compile or debug in progress"); + return; + } - Project::current->recreate_build(); - }); + Project::current = Project::create(); - menu.add_action("project_run_command", [this]() { - EntryBox::get().clear(); - EntryBox::get().labels.emplace_back(); - auto label_it = EntryBox::get().labels.begin(); - label_it->update = [label_it](int state, const std::string &message) { - label_it->set_text("Run Command directory order: opened directory, file path, current directory"); - }; - label_it->update(0, ""); - EntryBox::get().entries.emplace_back(last_run_command, [this](const std::string &content) { - if (content != "") { - last_run_command = content; - auto run_path = Notebook::get().get_current_folder(); - Terminal::get().async_print("Running: " + content + '\n'); - - Terminal::get().async_process(content, run_path, [content](int exit_status) { - Terminal::get().async_print(content + " returned: " + std::to_string(exit_status) + '\n'); - }); - } - EntryBox::get().hide(); - }, 30); - auto entry_it = EntryBox::get().entries.begin(); - entry_it->set_placeholder_text("Command"); - EntryBox::get().buttons.emplace_back("Run command", [entry_it]() { - entry_it->activate(); - }); - EntryBox::get().show(); - }); + if (Config::get().project.save_on_compile_or_run) + Project::save_files(Project::current->build->project_path); - menu.add_action("project_kill_last_running", []() { - Terminal::get().kill_last_async_process(); - }); - menu.add_action("project_force_kill_last_running", []() { - Terminal::get().kill_last_async_process(true); - }); + Project::current->compile(); + }); + menu.add_action("project_recreate_build", []() { + if (Project::compiling || Project::debugging) { + Info::get().print("Compile or debug in progress"); + return; + } -#ifdef JUCI_ENABLE_DEBUG - menu.add_action("debug_set_run_arguments", []() { - auto project=Project::create(); - auto run_arguments=project->debug_get_run_arguments(); - if(run_arguments.second.empty()) - return; + Project::current = Project::create(); - EntryBox::get().clear(); - EntryBox::get().labels.emplace_back(); - auto label_it=EntryBox::get().labels.begin(); - label_it->update=[label_it](int state, const std::string& message){ - label_it->set_text("Synopsis: [environment_variable=value]... executable [argument]...\nSet empty to let juCi++ deduce executable."); - }; - label_it->update(0, ""); - EntryBox::get().entries.emplace_back(run_arguments.second, [run_arguments_first=std::move(run_arguments.first)](const std::string& content){ - Project::debug_run_arguments[run_arguments_first].arguments=content; - EntryBox::get().hide(); - }, 50); - auto entry_it=EntryBox::get().entries.begin(); - entry_it->set_placeholder_text("Debug: Set Run Arguments"); + Project::current->recreate_build(); + }); - if(auto options=project->debug_get_options()) { - EntryBox::get().buttons.emplace_back("", [options]() { - options->show_all(); + menu.add_action("project_run_command", [this]() { + EntryBox::get().clear(); + EntryBox::get().labels.emplace_back(); + auto label_it = EntryBox::get().labels.begin(); + label_it->update = [label_it](int state, const std::string &message) { + label_it->set_text("Run Command directory order: opened directory, file path, current directory"); + }; + label_it->update(0, ""); + EntryBox::get().entries.emplace_back(last_run_command, [this](const std::string &content) { + if (content != "") { + last_run_command = content; + auto run_path = Notebook::get().get_current_folder(); + Terminal::get().async_print("Running: " + content + '\n'); + + Terminal::get().async_process(content, run_path, [content](int exit_status) { + Terminal::get().async_print(content + " returned: " + std::to_string(exit_status) + '\n'); }); - EntryBox::get().buttons.back().set_image_from_icon_name("preferences-system"); - EntryBox::get().buttons.back().set_always_show_image(true); - EntryBox::get().buttons.back().set_tooltip_text("Additional Options"); - options->set_relative_to(EntryBox::get().buttons.back()); } - - EntryBox::get().buttons.emplace_back("Debug: set run arguments", [entry_it](){ - entry_it->activate(); - }); - EntryBox::get().show(); + EntryBox::get().hide(); + }, 30); + auto entry_it = EntryBox::get().entries.begin(); + entry_it->set_placeholder_text("Command"); + EntryBox::get().buttons.emplace_back("Run command", [entry_it]() { + entry_it->activate(); }); - menu.add_action("debug_start_continue", [](){ - if(Project::compiling) { - Info::get().print("Compile in progress"); - return; - } - else if(Project::debugging) { - Project::current->debug_continue(); - return; - } + EntryBox::get().show(); + }); - Project::current=Project::create(); + menu.add_action("project_kill_last_running", []() { + Terminal::get().kill_last_async_process(); + }); + menu.add_action("project_force_kill_last_running", []() { + Terminal::get().kill_last_async_process(true); + }); - if(Config::get().project.save_on_compile_or_run) - Project::save_files(Project::current->build->project_path); +#ifdef JUCI_ENABLE_DEBUG + menu.add_action("debug_set_run_arguments", []() { + auto project=Project::create(); + auto run_arguments=project->debug_get_run_arguments(); + if(run_arguments.second.empty()) + return; - Project::current->debug_start(); - }); - menu.add_action("debug_stop", []() { - if(Project::current) - Project::current->debug_stop(); - }); - menu.add_action("debug_kill", []() { - if(Project::current) - Project::current->debug_kill(); - }); - menu.add_action("debug_step_over", []() { - if(Project::current) - Project::current->debug_step_over(); - }); - menu.add_action("debug_step_into", []() { - if(Project::current) - Project::current->debug_step_into(); - }); - menu.add_action("debug_step_out", []() { - if(Project::current) - Project::current->debug_step_out(); - }); - menu.add_action("debug_backtrace", []() { - if(Project::current) - Project::current->debug_backtrace(); - }); - menu.add_action("debug_show_variables", []() { - if(Project::current) - Project::current->debug_show_variables(); - }); - menu.add_action("debug_run_command", [this]() { - EntryBox::get().clear(); - EntryBox::get().entries.emplace_back(last_run_debug_command, [this](const std::string& content){ - if(content!="") { - if(Project::current) - Project::current->debug_run_command(content); - last_run_debug_command=content; - } - EntryBox::get().hide(); - }, 30); - auto entry_it=EntryBox::get().entries.begin(); - entry_it->set_placeholder_text("Debug Command"); - EntryBox::get().buttons.emplace_back("Run debug command", [entry_it](){ - entry_it->activate(); + EntryBox::get().clear(); + EntryBox::get().labels.emplace_back(); + auto label_it=EntryBox::get().labels.begin(); + label_it->update=[label_it](int state, const std::string& message){ + label_it->set_text("Synopsis: [environment_variable=value]... executable [argument]...\nSet empty to let juCi++ deduce executable."); + }; + label_it->update(0, ""); + EntryBox::get().entries.emplace_back(run_arguments.second, [run_arguments_first=std::move(run_arguments.first)](const std::string& content){ + Project::debug_run_arguments[run_arguments_first].arguments=content; + EntryBox::get().hide(); + }, 50); + auto entry_it=EntryBox::get().entries.begin(); + entry_it->set_placeholder_text("Debug: Set Run Arguments"); + + if(auto options=project->debug_get_options()) { + EntryBox::get().buttons.emplace_back("", [options]() { + options->show_all(); }); - EntryBox::get().show(); + EntryBox::get().buttons.back().set_image_from_icon_name("preferences-system"); + EntryBox::get().buttons.back().set_always_show_image(true); + EntryBox::get().buttons.back().set_tooltip_text("Additional Options"); + options->set_relative_to(EntryBox::get().buttons.back()); + } + + EntryBox::get().buttons.emplace_back("Debug: set run arguments", [entry_it](){ + entry_it->activate(); }); - menu.add_action("debug_toggle_breakpoint", [](){ - if(auto view=Notebook::get().get_current_view()) { - if(view->toggle_breakpoint) - view->toggle_breakpoint(view->get_buffer()->get_insert()->get_iter().get_line()); + EntryBox::get().show(); + }); + menu.add_action("debug_start_continue", [](){ + if(Project::compiling) { + Info::get().print("Compile in progress"); + return; + } + else if(Project::debugging) { + Project::current->debug_continue(); + return; + } + + Project::current=Project::create(); + + if(Config::get().project.save_on_compile_or_run) + Project::save_files(Project::current->build->project_path); + + Project::current->debug_start(); + }); + menu.add_action("debug_stop", []() { + if(Project::current) + Project::current->debug_stop(); + }); + menu.add_action("debug_kill", []() { + if(Project::current) + Project::current->debug_kill(); + }); + menu.add_action("debug_step_over", []() { + if(Project::current) + Project::current->debug_step_over(); + }); + menu.add_action("debug_step_into", []() { + if(Project::current) + Project::current->debug_step_into(); + }); + menu.add_action("debug_step_out", []() { + if(Project::current) + Project::current->debug_step_out(); + }); + menu.add_action("debug_backtrace", []() { + if(Project::current) + Project::current->debug_backtrace(); + }); + menu.add_action("debug_show_variables", []() { + if(Project::current) + Project::current->debug_show_variables(); + }); + menu.add_action("debug_run_command", [this]() { + EntryBox::get().clear(); + EntryBox::get().entries.emplace_back(last_run_debug_command, [this](const std::string& content){ + if(content!="") { + if(Project::current) + Project::current->debug_run_command(content); + last_run_debug_command=content; } + EntryBox::get().hide(); + }, 30); + auto entry_it=EntryBox::get().entries.begin(); + entry_it->set_placeholder_text("Debug Command"); + EntryBox::get().buttons.emplace_back("Run debug command", [entry_it](){ + entry_it->activate(); }); - menu.add_action("debug_goto_stop", [](){ - if(Project::debugging) { - if(!Project::debug_stop.first.empty()) { - Notebook::get().open(Project::debug_stop.first); - if(auto view=Notebook::get().get_current_view()) { - int line=Project::debug_stop.second.first; - int index=Project::debug_stop.second.second; - view->place_cursor_at_line_index(line, index); - view->scroll_to_cursor_delayed(view, true, true); - } + EntryBox::get().show(); + }); + menu.add_action("debug_toggle_breakpoint", [](){ + if(auto view=Notebook::get().get_current_view()) { + if(view->toggle_breakpoint) + view->toggle_breakpoint(view->get_buffer()->get_insert()->get_iter().get_line()); + } + }); + menu.add_action("debug_goto_stop", [](){ + if(Project::debugging) { + if(!Project::debug_stop.first.empty()) { + Notebook::get().open(Project::debug_stop.first); + if(auto view=Notebook::get().get_current_view()) { + int line=Project::debug_stop.second.first; + int index=Project::debug_stop.second.second; + view->place_cursor_at_line_index(line, index); + view->scroll_to_cursor_delayed(view, true, true); } } - }); + } + }); - Project::debug_update_status(""); + Project::debug_update_status(""); #endif - menu.add_action("window_next_tab", []() { - Notebook::get().next(); - }); - menu.add_action("window_previous_tab", []() { - Notebook::get().previous(); - }); - menu.add_action("window_close_tab", []() { - if (Notebook::get().get_current_view()) - Notebook::get().close_current(); - }); - menu.add_action("window_toggle_split", [] { - Notebook::get().toggle_split(); - }); - menu.add_action("window_toggle_full_screen", [this] { - if (this->get_window()->get_state() & Gdk::WindowState::WINDOW_STATE_FULLSCREEN) - unfullscreen(); - else - fullscreen(); - }); - menu.add_action("window_toggle_tabs", [] { - Notebook::get().toggle_tabs(); - }); - menu.add_action("window_clear_terminal", [] { - Terminal::get().clear(); - }); - - menu.toggle_menu_items = [] { - auto &menu = Menu::get(); - auto view = Notebook::get().get_current_view(); - - menu.actions["file_reload_file"]->set_enabled(view); - menu.actions["source_spellcheck"]->set_enabled(view); - menu.actions["source_spellcheck_clear"]->set_enabled(view); - menu.actions["source_spellcheck_next_error"]->set_enabled(view); - menu.actions["source_git_next_diff"]->set_enabled(view); - menu.actions["source_git_show_diff"]->set_enabled(view); - menu.actions["source_indentation_set_buffer_tab"]->set_enabled(view); - menu.actions["source_goto_line"]->set_enabled(view); - menu.actions["source_center_cursor"]->set_enabled(view); - - menu.actions["source_indentation_auto_indent_buffer"]->set_enabled(view && view->format_style); - menu.actions["source_comments_toggle"]->set_enabled(view && view->toggle_comments); - menu.actions["source_comments_add_documentation"]->set_enabled(view && view->get_documentation_template); - menu.actions["source_find_documentation"]->set_enabled(view && view->get_token_data); - menu.actions["source_goto_declaration"]->set_enabled(view && view->get_declaration_location); - menu.actions["source_goto_type_declaration"]->set_enabled(view && view->get_type_declaration_location); - menu.actions["source_goto_implementation"]->set_enabled(view && view->get_implementation_locations); - menu.actions["source_goto_declaration_or_implementation"]->set_enabled( - view && view->get_declaration_or_implementation_locations); - menu.actions["source_goto_usage"]->set_enabled(view && view->get_usages); - menu.actions["source_goto_method"]->set_enabled(view && view->get_methods); - menu.actions["source_rename"]->set_enabled(view && view->rename_similar_tokens); - menu.actions["source_implement_method"]->set_enabled(view && view->get_method); - menu.actions["source_goto_next_diagnostic"]->set_enabled(view && view->goto_next_diagnostic); - menu.actions["source_apply_fix_its"]->set_enabled(view && view->get_fix_its); + menu.add_action("window_next_tab", []() { + Notebook::get().next(); + }); + menu.add_action("window_previous_tab", []() { + Notebook::get().previous(); + }); + menu.add_action("window_close_tab", []() { + if (Notebook::get().get_current_view()) + Notebook::get().close_current(); + }); + menu.add_action("window_toggle_split", [] { + Notebook::get().toggle_split(); + }); + menu.add_action("window_toggle_full_screen", [this] { + if (this->get_window()->get_state() & Gdk::WindowState::WINDOW_STATE_FULLSCREEN) + unfullscreen(); + else + fullscreen(); + }); + menu.add_action("window_toggle_tabs", [] { + Notebook::get().toggle_tabs(); + }); + menu.add_action("window_clear_terminal", [] { + Terminal::get().clear(); + }); + + menu.toggle_menu_items = [] { + auto &menu = Menu::get(); + auto view = Notebook::get().get_current_view(); + + menu.actions["file_reload_file"]->set_enabled(view); + menu.actions["source_spellcheck"]->set_enabled(view); + menu.actions["source_spellcheck_clear"]->set_enabled(view); + menu.actions["source_spellcheck_next_error"]->set_enabled(view); + menu.actions["source_git_next_diff"]->set_enabled(view); + menu.actions["source_git_show_diff"]->set_enabled(view); + menu.actions["source_indentation_set_buffer_tab"]->set_enabled(view); + menu.actions["source_goto_line"]->set_enabled(view); + menu.actions["source_center_cursor"]->set_enabled(view); + + menu.actions["source_indentation_auto_indent_buffer"]->set_enabled(view && view->format_style); + menu.actions["source_comments_toggle"]->set_enabled(view && view->toggle_comments); + menu.actions["source_comments_add_documentation"]->set_enabled(view && view->get_documentation_template); + menu.actions["source_find_documentation"]->set_enabled(view && view->get_token_data); + menu.actions["source_goto_declaration"]->set_enabled(view && view->get_declaration_location); + menu.actions["source_goto_type_declaration"]->set_enabled(view && view->get_type_declaration_location); + menu.actions["source_goto_implementation"]->set_enabled(view && view->get_implementation_locations); + menu.actions["source_goto_declaration_or_implementation"]->set_enabled( + view && view->get_declaration_or_implementation_locations); + menu.actions["source_goto_usage"]->set_enabled(view && view->get_usages); + menu.actions["source_goto_method"]->set_enabled(view && view->get_methods); + menu.actions["source_rename"]->set_enabled(view && view->rename_similar_tokens); + menu.actions["source_implement_method"]->set_enabled(view && view->get_method); + menu.actions["source_goto_next_diagnostic"]->set_enabled(view && view->goto_next_diagnostic); + menu.actions["source_apply_fix_its"]->set_enabled(view && view->get_fix_its); #ifdef JUCI_ENABLE_DEBUG - Project::debug_activate_menu_items(); + Project::debug_activate_menu_items(); #endif - }; + }; } void Window::add_widgets() { - auto directories_scrolled_window = Gtk::manage(new Gtk::ScrolledWindow()); - directories_scrolled_window->add(Directories::get()); - - auto notebook_vbox = Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL)); - notebook_vbox->pack_start(Notebook::get()); - notebook_vbox->pack_end(EntryBox::get(), Gtk::PACK_SHRINK); - - auto terminal_scrolled_window = Gtk::manage(new Gtk::ScrolledWindow()); - terminal_scrolled_window->add(Terminal::get()); - - int width, height; - get_default_size(width, height); - - auto notebook_and_terminal_vpaned = Gtk::manage(new Gtk::Paned(Gtk::Orientation::ORIENTATION_VERTICAL)); - notebook_and_terminal_vpaned->set_position(static_cast<int>(0.75 * height)); - notebook_and_terminal_vpaned->pack1(*notebook_vbox, Gtk::SHRINK); - notebook_and_terminal_vpaned->pack2(*terminal_scrolled_window, Gtk::SHRINK); - - auto hpaned = Gtk::manage(new Gtk::Paned()); - hpaned->set_position(static_cast<int>(0.2 * width)); - hpaned->pack1(*directories_scrolled_window, Gtk::SHRINK); - hpaned->pack2(*notebook_and_terminal_vpaned, Gtk::SHRINK); - - auto status_hbox = Gtk::manage(new Gtk::Box()); - status_hbox->set_homogeneous(true); - status_hbox->pack_start(*Gtk::manage(new Gtk::Box())); - auto status_right_hbox = Gtk::manage(new Gtk::Box()); - status_right_hbox->pack_end(Notebook::get().status_state, Gtk::PACK_SHRINK); - auto status_right_overlay = Gtk::manage(new Gtk::Overlay()); - status_right_overlay->add(*status_right_hbox); - status_right_overlay->add_overlay(Notebook::get().status_diagnostics); - status_hbox->pack_end(*status_right_overlay); - - auto status_overlay = Gtk::manage(new Gtk::Overlay()); - status_overlay->add(*status_hbox); - auto status_file_info_hbox = Gtk::manage(new Gtk::Box); - status_file_info_hbox->pack_start(Notebook::get().status_file_path, Gtk::PACK_SHRINK); - status_file_info_hbox->pack_start(Notebook::get().status_branch, Gtk::PACK_SHRINK); - status_file_info_hbox->pack_start(Notebook::get().status_location, Gtk::PACK_SHRINK); - status_overlay->add_overlay(*status_file_info_hbox); - status_overlay->add_overlay(Project::debug_status_label()); - - auto vbox = Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL)); - vbox->pack_start(*hpaned); - vbox->pack_start(*status_overlay, Gtk::PACK_SHRINK); - - auto overlay_vbox = Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL)); - auto overlay_hbox = Gtk::manage(new Gtk::Box()); - overlay_vbox->set_hexpand(false); - overlay_vbox->set_halign(Gtk::Align::ALIGN_START); - overlay_vbox->pack_start(Info::get(), Gtk::PACK_SHRINK, 20); - overlay_hbox->set_hexpand(false); - overlay_hbox->set_halign(Gtk::Align::ALIGN_END); - overlay_hbox->pack_end(*overlay_vbox, Gtk::PACK_SHRINK, 20); - - auto overlay = Gtk::manage(new Gtk::Overlay()); - overlay->add(*vbox); - overlay->add_overlay(*overlay_hbox); - overlay->set_overlay_pass_through(*overlay_hbox, true); - add(*overlay); - - show_all_children(); - Info::get().hide(); - - //Scroll to end of terminal whenever info is printed - Terminal::get().signal_size_allocate().connect([terminal_scrolled_window](Gtk::Allocation &allocation) { - auto adjustment = terminal_scrolled_window->get_vadjustment(); - adjustment->set_value(adjustment->get_upper() - adjustment->get_page_size()); - Terminal::get().queue_draw(); - }); - - EntryBox::get().signal_show().connect([hpaned, notebook_and_terminal_vpaned, notebook_vbox]() { - hpaned->set_focus_chain({notebook_and_terminal_vpaned}); - notebook_and_terminal_vpaned->set_focus_chain({notebook_vbox}); - notebook_vbox->set_focus_chain({&EntryBox::get()}); - }); - EntryBox::get().signal_hide().connect([hpaned, notebook_and_terminal_vpaned, notebook_vbox]() { - hpaned->unset_focus_chain(); - notebook_and_terminal_vpaned->unset_focus_chain(); - notebook_vbox->unset_focus_chain(); - }); + auto directories_scrolled_window = Gtk::manage(new Gtk::ScrolledWindow()); + directories_scrolled_window->add(Directories::get()); + + auto notebook_vbox = Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL)); + notebook_vbox->pack_start(Notebook::get()); + notebook_vbox->pack_end(EntryBox::get(), Gtk::PACK_SHRINK); + + auto terminal_scrolled_window = Gtk::manage(new Gtk::ScrolledWindow()); + terminal_scrolled_window->add(Terminal::get()); + + int width, height; + get_default_size(width, height); + + auto notebook_and_terminal_vpaned = Gtk::manage(new Gtk::Paned(Gtk::Orientation::ORIENTATION_VERTICAL)); + notebook_and_terminal_vpaned->set_position(static_cast<int>(0.75 * height)); + notebook_and_terminal_vpaned->pack1(*notebook_vbox, Gtk::SHRINK); + notebook_and_terminal_vpaned->pack2(*terminal_scrolled_window, Gtk::SHRINK); + + auto hpaned = Gtk::manage(new Gtk::Paned()); + hpaned->set_position(static_cast<int>(0.2 * width)); + hpaned->pack1(*directories_scrolled_window, Gtk::SHRINK); + hpaned->pack2(*notebook_and_terminal_vpaned, Gtk::SHRINK); + + auto status_hbox = Gtk::manage(new Gtk::Box()); + status_hbox->set_homogeneous(true); + status_hbox->pack_start(*Gtk::manage(new Gtk::Box())); + auto status_right_hbox = Gtk::manage(new Gtk::Box()); + status_right_hbox->pack_end(Notebook::get().status_state, Gtk::PACK_SHRINK); + auto status_right_overlay = Gtk::manage(new Gtk::Overlay()); + status_right_overlay->add(*status_right_hbox); + status_right_overlay->add_overlay(Notebook::get().status_diagnostics); + status_hbox->pack_end(*status_right_overlay); + + auto status_overlay = Gtk::manage(new Gtk::Overlay()); + status_overlay->add(*status_hbox); + auto status_file_info_hbox = Gtk::manage(new Gtk::Box); + status_file_info_hbox->pack_start(Notebook::get().status_file_path, Gtk::PACK_SHRINK); + status_file_info_hbox->pack_start(Notebook::get().status_branch, Gtk::PACK_SHRINK); + status_file_info_hbox->pack_start(Notebook::get().status_location, Gtk::PACK_SHRINK); + status_overlay->add_overlay(*status_file_info_hbox); + status_overlay->add_overlay(Project::debug_status_label()); + + auto vbox = Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL)); + vbox->pack_start(*hpaned); + vbox->pack_start(*status_overlay, Gtk::PACK_SHRINK); + + auto overlay_vbox = Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL)); + auto overlay_hbox = Gtk::manage(new Gtk::Box()); + overlay_vbox->set_hexpand(false); + overlay_vbox->set_halign(Gtk::Align::ALIGN_START); + overlay_vbox->pack_start(Info::get(), Gtk::PACK_SHRINK, 20); + overlay_hbox->set_hexpand(false); + overlay_hbox->set_halign(Gtk::Align::ALIGN_END); + overlay_hbox->pack_end(*overlay_vbox, Gtk::PACK_SHRINK, 20); + + auto overlay = Gtk::manage(new Gtk::Overlay()); + overlay->add(*vbox); + overlay->add_overlay(*overlay_hbox); + overlay->set_overlay_pass_through(*overlay_hbox, true); + add(*overlay); + + show_all_children(); + Info::get().hide(); + + //Scroll to end of terminal whenever info is printed + Terminal::get().signal_size_allocate().connect([terminal_scrolled_window](Gtk::Allocation &allocation) { + auto adjustment = terminal_scrolled_window->get_vadjustment(); + adjustment->set_value(adjustment->get_upper() - adjustment->get_page_size()); + Terminal::get().queue_draw(); + }); + + EntryBox::get().signal_show().connect([hpaned, notebook_and_terminal_vpaned, notebook_vbox]() { + hpaned->set_focus_chain({notebook_and_terminal_vpaned}); + notebook_and_terminal_vpaned->set_focus_chain({notebook_vbox}); + notebook_vbox->set_focus_chain({&EntryBox::get()}); + }); + EntryBox::get().signal_hide().connect([hpaned, notebook_and_terminal_vpaned, notebook_vbox]() { + hpaned->unset_focus_chain(); + notebook_and_terminal_vpaned->unset_focus_chain(); + notebook_vbox->unset_focus_chain(); + }); } bool Window::on_key_press_event(GdkEventKey *event) { - if (event->keyval == GDK_KEY_Escape) { - EntryBox::get().hide(); - } + if (event->keyval == GDK_KEY_Escape) { + EntryBox::get().hide(); + } #ifdef __APPLE__ //For Apple's Command-left, right, up, down keys - else if((event->state & GDK_META_MASK)>0 && (event->state & GDK_MOD1_MASK)==0) { - if(event->keyval==GDK_KEY_Left || event->keyval==GDK_KEY_KP_Left) { - event->keyval=GDK_KEY_Home; - event->state=event->state & GDK_SHIFT_MASK; - event->hardware_keycode=115; - } - else if(event->keyval==GDK_KEY_Right || event->keyval==GDK_KEY_KP_Right) { - event->keyval=GDK_KEY_End; - event->state=event->state & GDK_SHIFT_MASK; - event->hardware_keycode=119; - } - else if(event->keyval==GDK_KEY_Up || event->keyval==GDK_KEY_KP_Up) { - event->keyval=GDK_KEY_Home; - event->state=event->state & GDK_SHIFT_MASK; - event->state+=GDK_CONTROL_MASK; - event->hardware_keycode=115; - } - else if(event->keyval==GDK_KEY_Down || event->keyval==GDK_KEY_KP_Down) { - event->keyval=GDK_KEY_End; - event->state=event->state & GDK_SHIFT_MASK; - event->state+=GDK_CONTROL_MASK; - event->hardware_keycode=119; - } + else if((event->state & GDK_META_MASK)>0 && (event->state & GDK_MOD1_MASK)==0) { + if(event->keyval==GDK_KEY_Left || event->keyval==GDK_KEY_KP_Left) { + event->keyval=GDK_KEY_Home; + event->state=event->state & GDK_SHIFT_MASK; + event->hardware_keycode=115; + } + else if(event->keyval==GDK_KEY_Right || event->keyval==GDK_KEY_KP_Right) { + event->keyval=GDK_KEY_End; + event->state=event->state & GDK_SHIFT_MASK; + event->hardware_keycode=119; + } + else if(event->keyval==GDK_KEY_Up || event->keyval==GDK_KEY_KP_Up) { + event->keyval=GDK_KEY_Home; + event->state=event->state & GDK_SHIFT_MASK; + event->state+=GDK_CONTROL_MASK; + event->hardware_keycode=115; } + else if(event->keyval==GDK_KEY_Down || event->keyval==GDK_KEY_KP_Down) { + event->keyval=GDK_KEY_End; + event->state=event->state & GDK_SHIFT_MASK; + event->state+=GDK_CONTROL_MASK; + event->hardware_keycode=119; + } + } #endif - if (SelectionDialog::get() && SelectionDialog::get()->is_visible()) { - if (SelectionDialog::get()->on_key_press(event)) - return true; - } + if (SelectionDialog::get() && SelectionDialog::get()->is_visible()) { + if (SelectionDialog::get()->on_key_press(event)) + return true; + } - return Gtk::ApplicationWindow::on_key_press_event(event); + return Gtk::ApplicationWindow::on_key_press_event(event); } bool Window::on_delete_event(GdkEventAny *event) { - save_session(); + save_session(); - for (size_t c = Notebook::get().size() - 1; c != static_cast<size_t>(-1); --c) { - if (!Notebook::get().close(c)) - return true; - } - Terminal::get().kill_async_processes(); + for (size_t c = Notebook::get().size() - 1; c != static_cast<size_t>(-1); --c) { + if (!Notebook::get().close(c)) + return true; + } + Terminal::get().kill_async_processes(); #ifdef JUCI_ENABLE_DEBUG - if(Project::current) - Project::current->debug_cancel(); + if(Project::current) + Project::current->debug_cancel(); #endif - return false; + return false; } void Window::search_and_replace_entry() { - EntryBox::get().clear(); - EntryBox::get().labels.emplace_back(); - auto label_it = EntryBox::get().labels.begin(); - label_it->update = [label_it](int state, const std::string &message) { - if (state == 0) { - try { - auto number = stoi(message); - if (number == 0) - label_it->set_text(""); - else if (number == 1) - label_it->set_text("1 result found"); - else if (number > 1) - label_it->set_text(message + " results found"); - } - catch (const std::exception &e) {} - } + EntryBox::get().clear(); + EntryBox::get().labels.emplace_back(); + auto label_it = EntryBox::get().labels.begin(); + label_it->update = [label_it](int state, const std::string &message) { + if (state == 0) { + try { + auto number = stoi(message); + if (number == 0) + label_it->set_text(""); + else if (number == 1) + label_it->set_text("1 result found"); + else if (number > 1) + label_it->set_text(message + " results found"); + } + catch (const std::exception &e) {} + } + }; + EntryBox::get().entries.emplace_back(last_search, [](const std::string &content) { + if (auto view = Notebook::get().get_current_view()) + view->search_forward(); + }); + auto search_entry_it = EntryBox::get().entries.begin(); + search_entry_it->set_placeholder_text("Find"); + if (auto view = Notebook::get().get_current_view()) { + view->update_search_occurrences = [label_it](int number) { + label_it->update(0, std::to_string(number)); }; - EntryBox::get().entries.emplace_back(last_search, [](const std::string &content) { - if (auto view = Notebook::get().get_current_view()) - view->search_forward(); - }); - auto search_entry_it = EntryBox::get().entries.begin(); - search_entry_it->set_placeholder_text("Find"); + view->search_highlight(search_entry_it->get_text(), case_sensitive_search, regex_search); + } + search_entry_it->signal_key_press_event().connect([](GdkEventKey *event) { + if ((event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) && + (event->state & GDK_SHIFT_MASK) > 0) { + if (auto view = Notebook::get().get_current_view()) + view->search_backward(); + } + return false; + }); + search_entry_it->signal_changed().connect([this, search_entry_it]() { + last_search = search_entry_it->get_text(); + if (auto view = Notebook::get().get_current_view()) + view->search_highlight(search_entry_it->get_text(), case_sensitive_search, regex_search); + }); + + EntryBox::get().entries.emplace_back(last_replace, [](const std::string &content) { + if (auto view = Notebook::get().get_current_view()) + view->replace_forward(content); + }); + auto replace_entry_it = EntryBox::get().entries.begin(); + replace_entry_it++; + replace_entry_it->set_placeholder_text("Replace"); + replace_entry_it->signal_key_press_event().connect([replace_entry_it](GdkEventKey *event) { + if ((event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) && + (event->state & GDK_SHIFT_MASK) > 0) { + if (auto view = Notebook::get().get_current_view()) + view->replace_backward(replace_entry_it->get_text()); + } + return false; + }); + replace_entry_it->signal_changed().connect([this, replace_entry_it]() { + last_replace = replace_entry_it->get_text(); + }); + + EntryBox::get().buttons.emplace_back("↑", []() { + if (auto view = Notebook::get().get_current_view()) + view->search_backward(); + }); + EntryBox::get().buttons.back().set_tooltip_text("Find Previous\n\nShortcut: Shift+Enter in the Find entry field"); + EntryBox::get().buttons.emplace_back("⇄", [replace_entry_it]() { if (auto view = Notebook::get().get_current_view()) { - view->update_search_occurrences = [label_it](int number) { - label_it->update(0, std::to_string(number)); - }; - view->search_highlight(search_entry_it->get_text(), case_sensitive_search, regex_search); - } - search_entry_it->signal_key_press_event().connect([](GdkEventKey *event) { - if ((event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) && - (event->state & GDK_SHIFT_MASK) > 0) { - if (auto view = Notebook::get().get_current_view()) - view->search_backward(); + view->replace_forward(replace_entry_it->get_text()); + } + }); + EntryBox::get().buttons.back().set_tooltip_text("Replace Next\n\nShortcut: Enter in the Replace entry field"); + EntryBox::get().buttons.emplace_back("↓", []() { + if (auto view = Notebook::get().get_current_view()) + view->search_forward(); + }); + EntryBox::get().buttons.back().set_tooltip_text("Find Next\n\nShortcut: Enter in the Find entry field"); + EntryBox::get().buttons.emplace_back("Replace All", [replace_entry_it]() { + if (auto view = Notebook::get().get_current_view()) + view->replace_all(replace_entry_it->get_text()); + }); + EntryBox::get().buttons.back().set_tooltip_text("Replace All"); + + EntryBox::get().toggle_buttons.emplace_back("Aa"); + EntryBox::get().toggle_buttons.back().set_tooltip_text("Match Case"); + EntryBox::get().toggle_buttons.back().set_active(case_sensitive_search); + EntryBox::get().toggle_buttons.back().on_activate = [this, search_entry_it]() { + case_sensitive_search = !case_sensitive_search; + if (auto view = Notebook::get().get_current_view()) + view->search_highlight(search_entry_it->get_text(), case_sensitive_search, regex_search); + }; + EntryBox::get().toggle_buttons.emplace_back(".*"); + EntryBox::get().toggle_buttons.back().set_tooltip_text("Use Regex"); + EntryBox::get().toggle_buttons.back().set_active(regex_search); + EntryBox::get().toggle_buttons.back().on_activate = [this, search_entry_it]() { + regex_search = !regex_search; + if (auto view = Notebook::get().get_current_view()) + view->search_highlight(search_entry_it->get_text(), case_sensitive_search, regex_search); + }; + EntryBox::get().signal_hide().connect([this]() { + for (size_t c = 0; c < Notebook::get().size(); c++) { + Notebook::get().get_view(c)->update_search_occurrences = nullptr; + Notebook::get().get_view(c)->search_highlight("", case_sensitive_search, regex_search); + } + search_entry_shown = false; + }); + search_entry_shown = true; + EntryBox::get().show(); +} + +void Window::set_tab_entry() { + EntryBox::get().clear(); + if (auto view = Notebook::get().get_current_view()) { + auto tab_char_and_size = view->get_tab_char_and_size(); + + EntryBox::get().labels.emplace_back(); + auto label_it = EntryBox::get().labels.begin(); + + EntryBox::get().entries.emplace_back(std::to_string(tab_char_and_size.second)); + auto entry_tab_size_it = EntryBox::get().entries.begin(); + entry_tab_size_it->set_placeholder_text("Tab size"); + + char tab_char = tab_char_and_size.first; + std::string tab_char_string; + if (tab_char == ' ') + tab_char_string = "space"; + else if (tab_char == '\t') + tab_char_string = "tab"; + + EntryBox::get().entries.emplace_back(tab_char_string); + auto entry_tab_char_it = EntryBox::get().entries.rbegin(); + entry_tab_char_it->set_placeholder_text("Tab char"); + + const auto activate_function = [entry_tab_char_it, entry_tab_size_it, label_it](const std::string &content) { + if (auto view = Notebook::get().get_current_view()) { + char tab_char = 0; + unsigned tab_size = 0; + try { + tab_size = static_cast<unsigned>(std::stoul(entry_tab_size_it->get_text())); + std::string tab_char_string = entry_tab_char_it->get_text(); + std::transform(tab_char_string.begin(), tab_char_string.end(), tab_char_string.begin(), ::tolower); + if (tab_char_string == "space") + tab_char = ' '; + else if (tab_char_string == "tab") + tab_char = '\t'; } - return false; - }); - search_entry_it->signal_changed().connect([this, search_entry_it]() { - last_search = search_entry_it->get_text(); - if (auto view = Notebook::get().get_current_view()) - view->search_highlight(search_entry_it->get_text(), case_sensitive_search, regex_search); - }); + catch (const std::exception &e) {} - EntryBox::get().entries.emplace_back(last_replace, [](const std::string &content) { - if (auto view = Notebook::get().get_current_view()) - view->replace_forward(content); - }); - auto replace_entry_it = EntryBox::get().entries.begin(); - replace_entry_it++; - replace_entry_it->set_placeholder_text("Replace"); - replace_entry_it->signal_key_press_event().connect([replace_entry_it](GdkEventKey *event) { - if ((event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) && - (event->state & GDK_SHIFT_MASK) > 0) { - if (auto view = Notebook::get().get_current_view()) - view->replace_backward(replace_entry_it->get_text()); + if (tab_char != 0 && tab_size > 0) { + view->set_tab_char_and_size(tab_char, tab_size); + EntryBox::get().hide(); + } else { + label_it->set_text("Tab size must be >0 and tab char set to either 'space' or 'tab'"); } - return false; - }); - replace_entry_it->signal_changed().connect([this, replace_entry_it]() { - last_replace = replace_entry_it->get_text(); - }); + } + }; - EntryBox::get().buttons.emplace_back("↑", []() { - if (auto view = Notebook::get().get_current_view()) - view->search_backward(); + entry_tab_char_it->on_activate = activate_function; + entry_tab_size_it->on_activate = activate_function; + + EntryBox::get().buttons.emplace_back("Set tab in current buffer", [entry_tab_char_it]() { + entry_tab_char_it->activate(); }); - EntryBox::get().buttons.back().set_tooltip_text("Find Previous\n\nShortcut: Shift+Enter in the Find entry field"); - EntryBox::get().buttons.emplace_back("⇄", [replace_entry_it]() { - if (auto view = Notebook::get().get_current_view()) { - view->replace_forward(replace_entry_it->get_text()); + + EntryBox::get().show(); + } +} + +void Window::goto_line_entry() { + EntryBox::get().clear(); + if (Notebook::get().get_current_view()) { + EntryBox::get().entries.emplace_back("", [](const std::string &content) { + if (auto view = Notebook::get().get_current_view()) { + try { + view->place_cursor_at_line_index(stoi(content) - 1, 0); + view->scroll_to_cursor_delayed(view, true, false); } + catch (const std::exception &e) {} + EntryBox::get().hide(); + } }); - EntryBox::get().buttons.back().set_tooltip_text("Replace Next\n\nShortcut: Enter in the Replace entry field"); - EntryBox::get().buttons.emplace_back("↓", []() { - if (auto view = Notebook::get().get_current_view()) - view->search_forward(); - }); - EntryBox::get().buttons.back().set_tooltip_text("Find Next\n\nShortcut: Enter in the Find entry field"); - EntryBox::get().buttons.emplace_back("Replace All", [replace_entry_it]() { - if (auto view = Notebook::get().get_current_view()) - view->replace_all(replace_entry_it->get_text()); - }); - EntryBox::get().buttons.back().set_tooltip_text("Replace All"); - - EntryBox::get().toggle_buttons.emplace_back("Aa"); - EntryBox::get().toggle_buttons.back().set_tooltip_text("Match Case"); - EntryBox::get().toggle_buttons.back().set_active(case_sensitive_search); - EntryBox::get().toggle_buttons.back().on_activate = [this, search_entry_it]() { - case_sensitive_search = !case_sensitive_search; - if (auto view = Notebook::get().get_current_view()) - view->search_highlight(search_entry_it->get_text(), case_sensitive_search, regex_search); - }; - EntryBox::get().toggle_buttons.emplace_back(".*"); - EntryBox::get().toggle_buttons.back().set_tooltip_text("Use Regex"); - EntryBox::get().toggle_buttons.back().set_active(regex_search); - EntryBox::get().toggle_buttons.back().on_activate = [this, search_entry_it]() { - regex_search = !regex_search; - if (auto view = Notebook::get().get_current_view()) - view->search_highlight(search_entry_it->get_text(), case_sensitive_search, regex_search); - }; - EntryBox::get().signal_hide().connect([this]() { - for (size_t c = 0; c < Notebook::get().size(); c++) { - Notebook::get().get_view(c)->update_search_occurrences = nullptr; - Notebook::get().get_view(c)->search_highlight("", case_sensitive_search, regex_search); - } - search_entry_shown = false; + auto entry_it = EntryBox::get().entries.begin(); + entry_it->set_placeholder_text("Line number"); + EntryBox::get().buttons.emplace_back("Go to line", [entry_it]() { + entry_it->activate(); }); - search_entry_shown = true; EntryBox::get().show(); + } } -void Window::set_tab_entry() { - EntryBox::get().clear(); - if (auto view = Notebook::get().get_current_view()) { - auto tab_char_and_size = view->get_tab_char_and_size(); - - EntryBox::get().labels.emplace_back(); - auto label_it = EntryBox::get().labels.begin(); - - EntryBox::get().entries.emplace_back(std::to_string(tab_char_and_size.second)); - auto entry_tab_size_it = EntryBox::get().entries.begin(); - entry_tab_size_it->set_placeholder_text("Tab size"); - - char tab_char = tab_char_and_size.first; - std::string tab_char_string; - if (tab_char == ' ') - tab_char_string = "space"; - else if (tab_char == '\t') - tab_char_string = "tab"; - - EntryBox::get().entries.emplace_back(tab_char_string); - auto entry_tab_char_it = EntryBox::get().entries.rbegin(); - entry_tab_char_it->set_placeholder_text("Tab char"); - - const auto activate_function = [entry_tab_char_it, entry_tab_size_it, label_it](const std::string &content) { - if (auto view = Notebook::get().get_current_view()) { - char tab_char = 0; - unsigned tab_size = 0; - try { - tab_size = static_cast<unsigned>(std::stoul(entry_tab_size_it->get_text())); - std::string tab_char_string = entry_tab_char_it->get_text(); - std::transform(tab_char_string.begin(), tab_char_string.end(), tab_char_string.begin(), ::tolower); - if (tab_char_string == "space") - tab_char = ' '; - else if (tab_char_string == "tab") - tab_char = '\t'; - } - catch (const std::exception &e) {} - - if (tab_char != 0 && tab_size > 0) { - view->set_tab_char_and_size(tab_char, tab_size); - EntryBox::get().hide(); - } else { - label_it->set_text("Tab size must be >0 and tab char set to either 'space' or 'tab'"); - } - } - }; - - entry_tab_char_it->on_activate = activate_function; - entry_tab_size_it->on_activate = activate_function; - - EntryBox::get().buttons.emplace_back("Set tab in current buffer", [entry_tab_char_it]() { - entry_tab_char_it->activate(); +void Window::rename_token_entry() { + EntryBox::get().clear(); + if (auto view = Notebook::get().get_current_view()) { + if (view->get_token_spelling && view->rename_similar_tokens) { + auto spelling = std::make_shared<std::string>(view->get_token_spelling()); + if (!spelling->empty()) { + EntryBox::get().entries.emplace_back(*spelling, + [view, spelling, iter = view->get_buffer()->get_insert()->get_iter()]( + const std::string &content) { + //TODO: gtk needs a way to check if iter is valid without dumping g_error message + //iter->get_buffer() will print such a message, but no segfault will occur + if (Notebook::get().get_current_view() == view && + content != *spelling && iter.get_buffer() && + view->get_buffer()->get_insert()->get_iter() == iter) + view->rename_similar_tokens(content); + else + Info::get().print("Operation canceled"); + EntryBox::get().hide(); + }); + auto entry_it = EntryBox::get().entries.begin(); + entry_it->set_placeholder_text("New name"); + EntryBox::get().buttons.emplace_back("Rename", [entry_it]() { + entry_it->activate(); }); - EntryBox::get().show(); + } } + } } -void Window::goto_line_entry() { - EntryBox::get().clear(); - if (Notebook::get().get_current_view()) { - EntryBox::get().entries.emplace_back("", [](const std::string &content) { - if (auto view = Notebook::get().get_current_view()) { - try { - view->place_cursor_at_line_index(stoi(content) - 1, 0); - view->scroll_to_cursor_delayed(view, true, false); - } - catch (const std::exception &e) {} - EntryBox::get().hide(); - } - }); - auto entry_it = EntryBox::get().entries.begin(); - entry_it->set_placeholder_text("Line number"); - EntryBox::get().buttons.emplace_back("Go to line", [entry_it]() { - entry_it->activate(); - }); - EntryBox::get().show(); +void Window::save_session() { + try { + boost::property_tree::ptree root_pt; + root_pt.put("folder", Directories::get().path.string()); + + boost::property_tree::ptree files_pt; + for (auto ¬ebook_view: Notebook::get().get_notebook_views()) { + boost::property_tree::ptree file_pt; + file_pt.put("path", notebook_view.second->file_path.string()); + file_pt.put("notebook", notebook_view.first); + auto iter = notebook_view.second->get_buffer()->get_insert()->get_iter(); + file_pt.put("line", iter.get_line()); + file_pt.put("line_offset", iter.get_line_offset()); + files_pt.push_back(std::make_pair("", file_pt)); } -} + root_pt.add_child("files", files_pt); -void Window::rename_token_entry() { - EntryBox::get().clear(); + boost::property_tree::ptree current_file_pt; if (auto view = Notebook::get().get_current_view()) { - if (view->get_token_spelling && view->rename_similar_tokens) { - auto spelling = std::make_shared<std::string>(view->get_token_spelling()); - if (!spelling->empty()) { - EntryBox::get().entries.emplace_back(*spelling, - [view, spelling, iter = view->get_buffer()->get_insert()->get_iter()]( - const std::string &content) { - //TODO: gtk needs a way to check if iter is valid without dumping g_error message - //iter->get_buffer() will print such a message, but no segfault will occur - if (Notebook::get().get_current_view() == view && - content != *spelling && iter.get_buffer() && - view->get_buffer()->get_insert()->get_iter() == iter) - view->rename_similar_tokens(content); - else - Info::get().print("Operation canceled"); - EntryBox::get().hide(); - }); - auto entry_it = EntryBox::get().entries.begin(); - entry_it->set_placeholder_text("New name"); - EntryBox::get().buttons.emplace_back("Rename", [entry_it]() { - entry_it->activate(); - }); - EntryBox::get().show(); - } - } + current_file_pt.put("path", view->file_path.string()); + auto iter = view->get_buffer()->get_insert()->get_iter(); + current_file_pt.put("line", iter.get_line()); + current_file_pt.put("line_offset", iter.get_line_offset()); } -} - -void Window::save_session() { - try { - boost::property_tree::ptree root_pt; - root_pt.put("folder", Directories::get().path.string()); - - boost::property_tree::ptree files_pt; - for (auto ¬ebook_view: Notebook::get().get_notebook_views()) { - boost::property_tree::ptree file_pt; - file_pt.put("path", notebook_view.second->file_path.string()); - file_pt.put("notebook", notebook_view.first); - auto iter = notebook_view.second->get_buffer()->get_insert()->get_iter(); - file_pt.put("line", iter.get_line()); - file_pt.put("line_offset", iter.get_line_offset()); - files_pt.push_back(std::make_pair("", file_pt)); - } - root_pt.add_child("files", files_pt); - - boost::property_tree::ptree current_file_pt; - if (auto view = Notebook::get().get_current_view()) { - current_file_pt.put("path", view->file_path.string()); - auto iter = view->get_buffer()->get_insert()->get_iter(); - current_file_pt.put("line", iter.get_line()); - current_file_pt.put("line_offset", iter.get_line_offset()); - } - std::string current_path; - if (auto view = Notebook::get().get_current_view()) - current_path = view->file_path.string(); - root_pt.put("current_file", current_path); - - boost::property_tree::ptree run_arguments_pt; - for (auto &run_argument: Project::run_arguments) { - if (run_argument.second.empty()) - continue; - boost::system::error_code ec; - if (boost::filesystem::exists(run_argument.first, ec) && - boost::filesystem::is_directory(run_argument.first, ec)) { - boost::property_tree::ptree run_argument_pt; - run_argument_pt.put("path", run_argument.first); - run_argument_pt.put("arguments", run_argument.second); - run_arguments_pt.push_back(std::make_pair("", run_argument_pt)); - } - } - root_pt.add_child("run_arguments", run_arguments_pt); - - boost::property_tree::ptree debug_run_arguments_pt; - for (auto &debug_run_argument: Project::debug_run_arguments) { - if (debug_run_argument.second.arguments.empty() && !debug_run_argument.second.remote_enabled && - debug_run_argument.second.remote_host_port.empty()) - continue; - boost::system::error_code ec; - if (boost::filesystem::exists(debug_run_argument.first, ec) && - boost::filesystem::is_directory(debug_run_argument.first, ec)) { - boost::property_tree::ptree debug_run_argument_pt; - debug_run_argument_pt.put("path", debug_run_argument.first); - debug_run_argument_pt.put("arguments", debug_run_argument.second.arguments); - debug_run_argument_pt.put("remote_enabled", debug_run_argument.second.remote_enabled); - debug_run_argument_pt.put("remote_host_port", debug_run_argument.second.remote_host_port); - debug_run_arguments_pt.push_back(std::make_pair("", debug_run_argument_pt)); - } - } - root_pt.add_child("debug_run_arguments", debug_run_arguments_pt); - - int width, height; - get_size(width, height); - boost::property_tree::ptree window_pt; - window_pt.put("width", width); - window_pt.put("height", height); - root_pt.add_child("window", window_pt); - - boost::property_tree::write_json((Config::get().home_juci_path / "last_session.json").string(), root_pt); + std::string current_path; + if (auto view = Notebook::get().get_current_view()) + current_path = view->file_path.string(); + root_pt.put("current_file", current_path); + + boost::property_tree::ptree run_arguments_pt; + for (auto &run_argument: Project::run_arguments) { + if (run_argument.second.empty()) + continue; + boost::system::error_code ec; + if (boost::filesystem::exists(run_argument.first, ec) && + boost::filesystem::is_directory(run_argument.first, ec)) { + boost::property_tree::ptree run_argument_pt; + run_argument_pt.put("path", run_argument.first); + run_argument_pt.put("arguments", run_argument.second); + run_arguments_pt.push_back(std::make_pair("", run_argument_pt)); + } + } + root_pt.add_child("run_arguments", run_arguments_pt); + + boost::property_tree::ptree debug_run_arguments_pt; + for (auto &debug_run_argument: Project::debug_run_arguments) { + if (debug_run_argument.second.arguments.empty() && !debug_run_argument.second.remote_enabled && + debug_run_argument.second.remote_host_port.empty()) + continue; + boost::system::error_code ec; + if (boost::filesystem::exists(debug_run_argument.first, ec) && + boost::filesystem::is_directory(debug_run_argument.first, ec)) { + boost::property_tree::ptree debug_run_argument_pt; + debug_run_argument_pt.put("path", debug_run_argument.first); + debug_run_argument_pt.put("arguments", debug_run_argument.second.arguments); + debug_run_argument_pt.put("remote_enabled", debug_run_argument.second.remote_enabled); + debug_run_argument_pt.put("remote_host_port", debug_run_argument.second.remote_host_port); + debug_run_arguments_pt.push_back(std::make_pair("", debug_run_argument_pt)); + } } - catch (...) {} + root_pt.add_child("debug_run_arguments", debug_run_arguments_pt); + + int width, height; + get_size(width, height); + boost::property_tree::ptree window_pt; + window_pt.put("width", width); + window_pt.put("height", height); + root_pt.add_child("window", window_pt); + + boost::property_tree::write_json((Config::get().home_juci_path / "last_session.json").string(), root_pt); + } + catch (...) {} } void Window::load_session(std::vector<boost::filesystem::path> &directories, std::vector<std::pair<boost::filesystem::path, size_t> > &files, std::vector<std::pair<int, int> > &file_offsets, std::string ¤t_file, bool read_directories_and_files) { - try { - boost::property_tree::ptree root_pt; - boost::property_tree::read_json((Config::get().home_juci_path / "last_session.json").string(), root_pt); - if (read_directories_and_files) { - auto folder = root_pt.get<std::string>("folder"); - if (!folder.empty() && boost::filesystem::exists(folder) && boost::filesystem::is_directory(folder)) - directories.emplace_back(folder); - - for (auto &file_pt: root_pt.get_child("files")) { - auto file = file_pt.second.get<std::string>("path"); - auto notebook = file_pt.second.get<size_t>("notebook"); - auto line = file_pt.second.get<int>("line"); - auto line_offset = file_pt.second.get<int>("line_offset"); - if (!file.empty() && boost::filesystem::exists(file) && !boost::filesystem::is_directory(file)) { - files.emplace_back(file, notebook); - file_offsets.emplace_back(line, line_offset); - } - } - - current_file = root_pt.get<std::string>("current_file"); - } - - for (auto &run_argument: root_pt.get_child(("run_arguments"))) { - auto path = run_argument.second.get<std::string>("path"); - boost::system::error_code ec; - if (boost::filesystem::exists(path, ec) && boost::filesystem::is_directory(path, ec)) - Project::run_arguments.emplace(path, run_argument.second.get<std::string>("arguments")); + try { + boost::property_tree::ptree root_pt; + boost::property_tree::read_json((Config::get().home_juci_path / "last_session.json").string(), root_pt); + if (read_directories_and_files) { + auto folder = root_pt.get<std::string>("folder"); + if (!folder.empty() && boost::filesystem::exists(folder) && boost::filesystem::is_directory(folder)) + directories.emplace_back(folder); + + for (auto &file_pt: root_pt.get_child("files")) { + auto file = file_pt.second.get<std::string>("path"); + auto notebook = file_pt.second.get<size_t>("notebook"); + auto line = file_pt.second.get<int>("line"); + auto line_offset = file_pt.second.get<int>("line_offset"); + if (!file.empty() && boost::filesystem::exists(file) && !boost::filesystem::is_directory(file)) { + files.emplace_back(file, notebook); + file_offsets.emplace_back(line, line_offset); } + } - for (auto &debug_run_argument: root_pt.get_child(("debug_run_arguments"))) { - auto path = debug_run_argument.second.get<std::string>("path"); - boost::system::error_code ec; - if (boost::filesystem::exists(path, ec) && boost::filesystem::is_directory(path, ec)) - Project::debug_run_arguments.emplace(path, Project::DebugRunArguments{ - debug_run_argument.second.get<std::string>("arguments"), - debug_run_argument.second.get<bool>("remote_enabled"), - debug_run_argument.second.get<std::string>("remote_host_port")}); - } + current_file = root_pt.get<std::string>("current_file"); + } - auto window_pt = root_pt.get_child("window"); - set_default_size(window_pt.get<int>("width"), window_pt.get<int>("height")); + for (auto &run_argument: root_pt.get_child(("run_arguments"))) { + auto path = run_argument.second.get<std::string>("path"); + boost::system::error_code ec; + if (boost::filesystem::exists(path, ec) && boost::filesystem::is_directory(path, ec)) + Project::run_arguments.emplace(path, run_argument.second.get<std::string>("arguments")); } - catch (...) { - set_default_size(800, 600); + + for (auto &debug_run_argument: root_pt.get_child(("debug_run_arguments"))) { + auto path = debug_run_argument.second.get<std::string>("path"); + boost::system::error_code ec; + if (boost::filesystem::exists(path, ec) && boost::filesystem::is_directory(path, ec)) + Project::debug_run_arguments.emplace(path, Project::DebugRunArguments{ + debug_run_argument.second.get<std::string>("arguments"), + debug_run_argument.second.get<bool>("remote_enabled"), + debug_run_argument.second.get<std::string>("remote_host_port")}); } + + auto window_pt = root_pt.get_child("window"); + set_default_size(window_pt.get<int>("width"), window_pt.get<int>("height")); + } + catch (...) { + set_default_size(800, 600); + } } diff --git a/src/window.h b/src/window.h index 7f9b062e..1aa7e30d 100644 --- a/src/window.h +++ b/src/window.h @@ -5,51 +5,51 @@ #include <boost/filesystem.hpp> class Window : public Gtk::ApplicationWindow { - Window(); + Window(); public: - static Window &get() { - static Window singleton; - return singleton; - } + static Window &get() { + static Window singleton; + return singleton; + } - void add_widgets(); + void add_widgets(); - void save_session(); + void save_session(); - void load_session(std::vector<boost::filesystem::path> &directories, - std::vector<std::pair<boost::filesystem::path, size_t>> &files, - std::vector<std::pair<int, int>> &file_offsets, std::string ¤t_file, - bool read_directories_and_files); + void load_session(std::vector<boost::filesystem::path> &directories, + std::vector<std::pair<boost::filesystem::path, size_t>> &files, + std::vector<std::pair<int, int>> &file_offsets, std::string ¤t_file, + bool read_directories_and_files); protected: - bool on_key_press_event(GdkEventKey *event) override; + bool on_key_press_event(GdkEventKey *event) override; - bool on_delete_event(GdkEventAny *event) override; + bool on_delete_event(GdkEventAny *event) override; private: - Gtk::AboutDialog about; + Gtk::AboutDialog about; - Glib::RefPtr<Gtk::CssProvider> css_provider_theme; - Glib::RefPtr<Gtk::CssProvider> css_provider_tooltips; + Glib::RefPtr<Gtk::CssProvider> css_provider_theme; + Glib::RefPtr<Gtk::CssProvider> css_provider_tooltips; - void configure(); + void configure(); - void set_menu_actions(); + void set_menu_actions(); - void search_and_replace_entry(); + void search_and_replace_entry(); - void set_tab_entry(); + void set_tab_entry(); - void goto_line_entry(); + void goto_line_entry(); - void rename_token_entry(); + void rename_token_entry(); - std::string last_search; - std::string last_replace; - std::string last_run_command; - std::string last_run_debug_command; - bool case_sensitive_search = true; - bool regex_search = false; - bool search_entry_shown = false; + std::string last_search; + std::string last_replace; + std::string last_run_command; + std::string last_run_debug_command; + bool case_sensitive_search = true; + bool regex_search = false; + bool search_entry_shown = false; }; From dcaf1c42026e4f3da947a009c3bb9f9313a9951d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E4=BA=88=E9=A1=BA?= <ys_37677@126.com> Date: Mon, 21 May 2018 17:14:07 +0800 Subject: [PATCH 06/12] Idea --- .gitignore | 1 + .idea/.name | 1 + .idea/jucipp.iml | 2 ++ .idea/misc.xml | 4 ++++ .idea/modules.xml | 8 ++++++++ .idea/vcs.xml | 8 ++++++++ 6 files changed, 24 insertions(+) create mode 100644 .idea/.name create mode 100644 .idea/jucipp.iml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml diff --git a/.gitignore b/.gitignore index 8448ec26..58f0e817 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ !config.json !*.py !debian/* +!.idea/* build .usages_clang diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 00000000..c2470e11 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +juci \ No newline at end of file diff --git a/.idea/jucipp.iml b/.idea/jucipp.iml new file mode 100644 index 00000000..f08604bb --- /dev/null +++ b/.idea/jucipp.iml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module classpath="CMake" type="CPP_MODULE" version="4" /> \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..79b3c948 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" /> +</project> \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..b168f94e --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectModuleManager"> + <modules> + <module fileurl="file://$PROJECT_DIR$/.idea/jucipp.iml" filepath="$PROJECT_DIR$/.idea/jucipp.iml" /> + </modules> + </component> +</project> \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..0388d1c5 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="VcsDirectoryMappings"> + <mapping directory="$PROJECT_DIR$" vcs="Git" /> + <mapping directory="$PROJECT_DIR$/libclangmm" vcs="Git" /> + <mapping directory="$PROJECT_DIR$/tiny-process-library" vcs="Git" /> + </component> +</project> \ No newline at end of file From 3c23846067021d01263eee4183800fec6d7ae02d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=88=E9=A1=BA=20=E5=88=98?= <yshliu0321@icloud.com> Date: Wed, 23 May 2018 11:39:51 +0800 Subject: [PATCH 07/12] Simplfy Code --- src/CMakeLists.txt | 2 +- src/cmake.cc | 43 +++++++------------- src/cmake.h | 6 +-- src/ctags.cc | 2 +- src/{buildsystem.cc => makefile_base.cc} | 6 +-- src/{buildsystem.h => makefile_base.h} | 6 +-- src/meson.cc | 18 +++----- src/meson.h | 4 +- src/project.cc | 47 ++++++++++----------- src/project_build.cc | 46 +++++++-------------- src/project_build.h | 52 ++++++++++++++++-------- src/source_clang.cc | 12 +++--- src/source_language_protocol.cc | 12 +++--- src/window.cc | 10 ++--- 14 files changed, 123 insertions(+), 143 deletions(-) rename src/{buildsystem.cc => makefile_base.cc} (85%) rename src/{buildsystem.h => makefile_base.h} (80%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b988f114..b84b87e9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,6 @@ # Files used both in ../src and ../tests -file(GLOB JUCI_SRC_BUILDSYSTEMS buildsystem/*.*) +file(GLOB JUCI_SRC_BUILDSYSTEMS cmake.h cmake.cc meson.h meson.cc makefile_base.h makefile_base.cc) set(JUCI_SHARED_FILES ${JUCI_SRC_BUILDSYSTEMS} diff --git a/src/cmake.cc b/src/cmake.cc index 5e9b13e5..cfcc2479 100644 --- a/src/cmake.cc +++ b/src/cmake.cc @@ -6,30 +6,24 @@ #include <regex> #include "compile_commands.h" -CMake::CMake(const boost::filesystem::path &path) : BuildSystemBase(path, "CMakeLists.txt") {} +CMake::CMake(const boost::filesystem::path &path) : MakefileBase(path, "CMakeLists.txt") {} bool CMake::update_default_build(const boost::filesystem::path &default_build_path, bool force) { - if(project_path.empty() || !boost::filesystem::exists(project_path/"CMakeLists.txt") || default_build_path.empty()) + if(get_project_path().empty() || !boost::filesystem::exists(get_project_path()/"CMakeLists.txt") || default_build_path.empty()) return false; - - if(!boost::filesystem::exists(default_build_path)) { - boost::system::error_code ec; - boost::filesystem::create_directories(default_build_path, ec); - if(ec) { - Terminal::get().print("Error: could not create "+default_build_path.string()+": "+ec.message()+"\n", true); - return false; - } - } - + + if (!create_build_directory(default_build_path)) return false; + if(!force && boost::filesystem::exists(default_build_path/"compile_commands.json")) return true; 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().project.cmake.command+' '+ - filesystem::escape_argument(project_path.string())+" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON", default_build_path); + filesystem::escape_argument(get_project_path().string())+" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON", default_build_path); message.hide(); if(exit_status==EXIT_SUCCESS) { + #ifdef _WIN32 //Temporary fix to MSYS2's libclang auto compile_commands_file=filesystem::read(compile_commands_path); auto replace_drive = [&compile_commands_file](const std::string& param) { @@ -52,28 +46,19 @@ bool CMake::update_default_build(const boost::filesystem::path &default_build_pa } bool CMake::update_debug_build(const boost::filesystem::path &debug_build_path, bool force) { - if(project_path.empty() || !boost::filesystem::exists(project_path/"CMakeLists.txt") || debug_build_path.empty()) + if(get_project_path().empty() || !boost::filesystem::exists(get_project_path()/"CMakeLists.txt") || debug_build_path.empty()) return false; - - if(!boost::filesystem::exists(debug_build_path)) { - boost::system::error_code ec; - boost::filesystem::create_directories(debug_build_path, ec); - if(ec) { - Terminal::get().print("Error: could not create "+debug_build_path.string()+": "+ec.message()+"\n", true); - return false; - } - } - + + if (!create_build_directory(debug_build_path)) return false; + if(!force && boost::filesystem::exists(debug_build_path/"CMakeCache.txt")) return true; Dialog::Message message("Creating/updating debug build"); auto exit_status=Terminal::get().process(Config::get().project.cmake.command+' '+ - filesystem::escape_argument(project_path.string())+" -DCMAKE_BUILD_TYPE=Debug", debug_build_path); + filesystem::escape_argument(get_project_path().string())+" -DCMAKE_BUILD_TYPE=Debug", debug_build_path); message.hide(); - if(exit_status==EXIT_SUCCESS) - return true; - return false; + return exit_status == EXIT_SUCCESS; } boost::filesystem::path CMake::get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) { @@ -87,7 +72,7 @@ boost::filesystem::path CMake::get_executable(const boost::filesystem::path &bui for(auto ¶meter: parameters) { if(parameter.second.size()>1 && parameter.second[0].size()>0 && parameter.second[0].compare(0, 2, "${")!=0) { auto executable=(parameter.first.parent_path()/parameter.second[0]).string(); - auto project_path_str=project_path.string(); + auto project_path_str=get_project_path().string(); size_t pos=executable.find(project_path_str); if(pos!=std::string::npos) executable.replace(pos, project_path_str.size(), build_path.string()); diff --git a/src/cmake.h b/src/cmake.h index 7783ae28..ebb68671 100644 --- a/src/cmake.h +++ b/src/cmake.h @@ -1,12 +1,12 @@ #pragma once -#include "buildsystem.h" +#include "makefile_base.h" #include <unordered_map> #include <unordered_set> -class CMake : public BuildSystemBase { +class CMake : public MakefileBase { public: - CMake(const boost::filesystem::path &path); + explicit CMake(const boost::filesystem::path &path); bool update_default_build(const boost::filesystem::path &default_build_path, bool force) override; diff --git a/src/ctags.cc b/src/ctags.cc index 55b9fb43..1f34f6dc 100644 --- a/src/ctags.cc +++ b/src/ctags.cc @@ -10,7 +10,7 @@ std::pair<boost::filesystem::path, std::unique_ptr<std::stringstream> > Ctags::get_result(const boost::filesystem::path &path) { auto build=Project::Build::create(path); - auto run_path=build->project_path; + auto run_path=build->get_project_path(); std::string exclude; if(!run_path.empty()) { auto relative_default_path=filesystem::get_relative_path(build->get_default_path(), run_path); diff --git a/src/buildsystem.cc b/src/makefile_base.cc similarity index 85% rename from src/buildsystem.cc rename to src/makefile_base.cc index e6ddefae..a73a76b4 100644 --- a/src/buildsystem.cc +++ b/src/makefile_base.cc @@ -1,10 +1,10 @@ #include <regex> #include "meson.h" -#include "buildsystem.h" +#include "makefile_base.h" #include "terminal.h" #include "filesystem.h" -bool BuildSystemBase::create_build_directory(const boost::filesystem::path &build_path) { +bool MakefileBase::create_build_directory(const boost::filesystem::path &build_path) { if (!boost::filesystem::exists(build_path)) { boost::system::error_code ec; boost::filesystem::create_directories(build_path, ec); @@ -45,5 +45,5 @@ namespace { } } -BuildSystemBase::BuildSystemBase(const boost::filesystem::path &path, const std::string &filename) : project_path( +MakefileBase::MakefileBase(const boost::filesystem::path &path, const std::string &filename) : project_path( find_project(path, filename)) {} diff --git a/src/buildsystem.h b/src/makefile_base.h similarity index 80% rename from src/buildsystem.h rename to src/makefile_base.h index 202a5674..cd080804 100644 --- a/src/buildsystem.h +++ b/src/makefile_base.h @@ -3,11 +3,11 @@ #include <boost/filesystem.hpp> #include <vector> -class BuildSystemBase { +class MakefileBase { public: - BuildSystemBase(const boost::filesystem::path &path, const std::string &filename); + MakefileBase(const boost::filesystem::path &path, const std::string &filename); - virtual ~BuildSystemBase() = default; + virtual ~MakefileBase() = default; auto &get_project_path() const { return project_path; } diff --git a/src/meson.cc b/src/meson.cc index ac1f6cb1..04b30de6 100644 --- a/src/meson.cc +++ b/src/meson.cc @@ -6,7 +6,7 @@ #include "dialogs.h" #include "config.h" -Meson::Meson(const boost::filesystem::path &path) : BuildSystemBase(path, "meson.build") {} +Meson::Meson(const boost::filesystem::path &path) : MakefileBase(path, "meson.build") {} bool Meson::update_default_build(const boost::filesystem::path &default_build_path, bool force) { if (get_project_path().empty() || !boost::filesystem::exists(get_project_path() / "meson.build") || @@ -31,17 +31,11 @@ bool Meson::update_default_build(const boost::filesystem::path &default_build_pa } bool Meson::update_debug_build(const boost::filesystem::path &debug_build_path, bool force) { - if(project_path.empty() || !boost::filesystem::exists(project_path/"meson.build") || debug_build_path.empty()) + if (get_project_path().empty() || !boost::filesystem::exists(get_project_path() / "meson.build") || + debug_build_path.empty()) return false; - - if(!boost::filesystem::exists(debug_build_path)) { - boost::system::error_code ec; - boost::filesystem::create_directories(debug_build_path, ec); - if(ec) { - Terminal::get().print("Error: could not create "+debug_build_path.string()+": "+ec.message()+"\n", true); - return false; - } - } + + if (!create_build_directory(debug_build_path)) return false; bool compile_commands_exists=boost::filesystem::exists(debug_build_path/"compile_commands.json"); if(!force && compile_commands_exists) @@ -49,7 +43,7 @@ bool Meson::update_debug_build(const boost::filesystem::path &debug_build_path, Dialog::Message message("Creating/updating debug build"); auto exit_status=Terminal::get().process(Config::get().project.meson.command+' '+(compile_commands_exists?"--internal regenerate ":"")+ - "--buildtype debug "+filesystem::escape_argument(project_path.string()), debug_build_path); + "--buildtype debug "+filesystem::escape_argument(get_project_path().string()), debug_build_path); message.hide(); if(exit_status==EXIT_SUCCESS) return true; diff --git a/src/meson.h b/src/meson.h index cd31dbfa..43c5b1c4 100644 --- a/src/meson.h +++ b/src/meson.h @@ -1,8 +1,8 @@ #pragma once -#include "buildsystem.h" +#include "makefile_base.h" -class Meson : public BuildSystemBase { +class Meson : public MakefileBase { public: Meson(const boost::filesystem::path &path); diff --git a/src/project.cc b/src/project.cc index eea330f2..2f32f8e9 100644 --- a/src/project.cc +++ b/src/project.cc @@ -16,6 +16,7 @@ #include "usages_clang.h" #include "ctags.h" #include <future> +#include <memory> boost::filesystem::path Project::debug_last_stop_file_path; std::unordered_map<std::string, std::string> Project::run_arguments; @@ -64,7 +65,7 @@ void Project::on_save(size_t index) { auto build=Build::create(build_path); if(dynamic_cast<CMakeBuild*>(build.get()) || dynamic_cast<MesonBuild*>(build.get())) { build->update_default(true); - Usages::Clang::erase_all_caches_for_project(build->project_path, build->get_default_path()); + Usages::Clang::erase_all_caches_for_project(build->get_project_path(), build->get_default_path()); boost::system::error_code ec; if(boost::filesystem::exists(build->get_debug_path()), ec) build->update_debug(true); @@ -72,7 +73,7 @@ void Project::on_save(size_t index) { for(size_t c=0;c<Notebook::get().size();c++) { auto source_view=Notebook::get().get_view(c); if(auto source_clang_view=dynamic_cast<Source::ClangView*>(source_view)) { - if(filesystem::file_in_path(source_clang_view->file_path, build->project_path)) + if(filesystem::file_in_path(source_clang_view->file_path, build->get_project_path())) source_clang_view->full_reparse_needed=true; } } @@ -159,7 +160,7 @@ std::shared_ptr<Project::Base> Project::create() { else if(dynamic_cast<NpmBuild*>(build.get())) return std::shared_ptr<Project::Base>(new Project::JavaScript(std::move(build))); else - return std::shared_ptr<Project::Base>(new Project::Base(std::move(build))); + return std::make_shared<Project::Base>(std::move(build)); } std::pair<std::string, std::string> Project::Base::get_run_arguments() { @@ -260,7 +261,7 @@ std::pair<std::string, std::string> Project::LLDB::debug_get_run_arguments() { if(debug_build_path.empty() || default_build_path.empty()) return {"", ""}; - auto project_path=build->project_path.string(); + auto project_path=build->get_project_path().string(); auto run_arguments_it=debug_run_arguments.find(project_path); std::string arguments; if(run_arguments_it!=debug_run_arguments.end()) @@ -284,12 +285,12 @@ std::pair<std::string, std::string> Project::LLDB::debug_get_run_arguments() { } Project::DebugOptions *Project::LLDB::debug_get_options() { - if(build->project_path.empty()) + if(build->get_project_path().empty()) return nullptr; debug_options=std::make_unique<DebugOptions>(); - auto &arguments=Project::debug_run_arguments[build->project_path.string()]; + auto &arguments=Project::debug_run_arguments[build->get_project_path().string()]; auto remote_enabled=Gtk::manage(new Gtk::CheckButton()); auto remote_host_port=Gtk::manage(new Gtk::Entry()); @@ -308,7 +309,7 @@ Project::DebugOptions *Project::LLDB::debug_get_options() { auto self=this->shared_from_this(); debug_options->signal_hide().connect([self, remote_enabled, remote_host_port] { - auto &arguments=Project::debug_run_arguments[self->build->project_path.string()]; + auto &arguments=Project::debug_run_arguments[self->build->get_project_path().string()]; arguments.remote_enabled=remote_enabled->get_active(); arguments.remote_host_port=remote_host_port->get_text(); }); @@ -332,7 +333,7 @@ void Project::LLDB::debug_start() { if(debug_build_path.empty() || !build->update_debug() || default_build_path.empty()) return; - auto project_path=std::make_shared<boost::filesystem::path>(build->project_path); + auto project_path=std::make_shared<boost::filesystem::path>(build->get_project_path()); auto run_arguments_it=debug_run_arguments.find(project_path->string()); auto run_arguments=std::make_shared<std::string>(); @@ -667,7 +668,7 @@ void Project::LLDB::debug_cancel() { #endif void Project::LanguageProtocol::show_symbols() { - if(build->project_path.empty()) { + if(build->get_project_path().empty()) { Info::get().print("Could not find project folder"); return; } @@ -679,7 +680,7 @@ void Project::LanguageProtocol::show_symbols() { return; } - auto project_path=std::make_shared<boost::filesystem::path>(build->project_path); + auto project_path=std::make_shared<boost::filesystem::path>(build->get_project_path()); auto client=::LanguageProtocol::Client::get(*project_path, language_id); auto capabilities=client->initialize(nullptr); @@ -767,7 +768,7 @@ std::pair<std::string, std::string> Project::Clang::get_run_arguments() { if(build_path.empty()) return {"", ""}; - auto project_path=build->project_path.string(); + auto project_path=build->get_project_path().string(); auto run_arguments_it=run_arguments.find(project_path); std::string arguments; if(run_arguments_it!=run_arguments.end()) @@ -796,7 +797,7 @@ void Project::Clang::compile() { if(Config::get().project.clear_terminal_on_compile) Terminal::get().clear(); - Terminal::get().print("Compiling project "+filesystem::get_short_path(build->project_path).string()+"\n"); + Terminal::get().print("Compiling project "+filesystem::get_short_path(build->get_project_path()).string()+"\n"); Terminal::get().async_process(build->get_compile_command(), default_build_path, [](int exit_status) { compiling=false; }); @@ -807,7 +808,7 @@ void Project::Clang::compile_and_run() { if(default_build_path.empty() || !build->update_default()) return; - auto project_path=build->project_path; + auto project_path=build->get_project_path(); auto run_arguments_it=run_arguments.find(project_path.string()); std::string arguments; @@ -842,7 +843,7 @@ void Project::Clang::compile_and_run() { } void Project::Clang::recreate_build() { - if(build->project_path.empty()) + if(build->get_project_path().empty()) return; auto default_build_path=build->get_default_path(); if(default_build_path.empty()) @@ -866,7 +867,7 @@ void Project::Clang::recreate_build() { dialog.set_secondary_text(message+"?"); if(dialog.run()!=Gtk::RESPONSE_YES) return; - Usages::Clang::erase_all_caches_for_project(build->project_path, default_build_path); + Usages::Clang::erase_all_caches_for_project(build->get_project_path(), default_build_path); try { if(has_default_build) boost::filesystem::remove_all(default_build_path); @@ -886,7 +887,7 @@ void Project::Clang::recreate_build() { for(size_t c=0;c<Notebook::get().size();c++) { auto source_view=Notebook::get().get_view(c); if(auto source_clang_view=dynamic_cast<Source::ClangView*>(source_view)) { - if(filesystem::file_in_path(source_clang_view->file_path, build->project_path)) + if(filesystem::file_in_path(source_clang_view->file_path, build->get_project_path())) source_clang_view->full_reparse_needed=true; } } @@ -932,9 +933,9 @@ void Project::Python::compile_and_run() { void Project::JavaScript::compile_and_run() { std::string command; boost::filesystem::path path; - if(!build->project_path.empty()) { + if(!build->get_project_path().empty()) { command="npm start"; - path=build->project_path; + path=build->get_project_path(); } else { auto view=Notebook::get().get_current_view(); @@ -970,7 +971,7 @@ void Project::HTML::compile_and_run() { } std::pair<std::string, std::string> Project::Rust::get_run_arguments() { - auto project_path=build->project_path.string(); + auto project_path=build->get_project_path().string(); auto run_arguments_it=run_arguments.find(project_path); std::string arguments; if(run_arguments_it!=run_arguments.end()) @@ -988,10 +989,10 @@ void Project::Rust::compile() { if(Config::get().project.clear_terminal_on_compile) Terminal::get().clear(); - Terminal::get().print("Compiling project "+filesystem::get_short_path(build->project_path).string()+"\n"); + Terminal::get().print("Compiling project "+filesystem::get_short_path(build->get_project_path()).string()+"\n"); auto command=build->get_compile_command(); - Terminal::get().async_process(command, build->project_path, [](int exit_status) { + Terminal::get().async_process(command, build->get_project_path(), [](int exit_status) { compiling=false; }); } @@ -1006,10 +1007,10 @@ void Project::Rust::compile_and_run() { Terminal::get().print("Compiling and running "+arguments+"\n"); auto self=this->shared_from_this(); - Terminal::get().async_process(build->get_compile_command(), build->project_path, [self, arguments=std::move(arguments)](int exit_status) { + Terminal::get().async_process(build->get_compile_command(), build->get_project_path(), [self, arguments=std::move(arguments)](int exit_status) { compiling=false; if(exit_status==EXIT_SUCCESS) { - Terminal::get().async_process(arguments, self->build->project_path, [arguments](int exit_status) { + Terminal::get().async_process(arguments, self->build->get_project_path(), [arguments](int exit_status) { Terminal::get().async_print(arguments+" returned: "+std::to_string(exit_status)+'\n'); }); } diff --git a/src/project_build.cc b/src/project_build.cc index a1f6f003..3398086a 100644 --- a/src/project_build.cc +++ b/src/project_build.cc @@ -40,22 +40,25 @@ std::unique_ptr<Project::Build> Project::Build::create(const boost::filesystem:: return std::make_unique<Project::Build>(); } +namespace { + void replace_variable_in_path(boost::filesystem::path& path, const std::string& var, const std::string& with) { + size_t pos = 0; + auto path_string = path.string(); + while ((pos = path_string.find(var, pos)) != std::string::npos) { + path_string.replace(pos, var.size(), with); + pos += with.size(); + } + if (pos != 0) + path = path_string; + } +} boost::filesystem::path Project::Build::get_default_path() { if (project_path.empty()) return boost::filesystem::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; - auto default_build_path_string = default_build_path.string(); - auto path_filename_string = project_path.filename().string(); - while ((pos = default_build_path_string.find(path_variable_project_directory_name, pos)) != std::string::npos) { - default_build_path_string.replace(pos, path_variable_project_directory_name.size(), path_filename_string); - pos += path_filename_string.size(); - } - if (pos != 0) - default_build_path = default_build_path_string; + replace_variable_in_path(default_build_path, "<project_directory_name>", project_path.filename().string()); if (default_build_path.is_relative()) default_build_path = project_path / default_build_path; @@ -69,27 +72,8 @@ boost::filesystem::path Project::Build::get_debug_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; - auto debug_build_path_string = debug_build_path.string(); - auto path_filename_string = project_path.filename().string(); - while ((pos = debug_build_path_string.find(path_variable_project_directory_name, pos)) != std::string::npos) { - debug_build_path_string.replace(pos, path_variable_project_directory_name.size(), path_filename_string); - pos += path_filename_string.size(); - } - if (pos != 0) - debug_build_path = debug_build_path_string; - - const std::string path_variable_default_build_path = "<default_build_path>"; - pos = 0; - debug_build_path_string = debug_build_path.string(); - auto default_build_path = Config::get().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(); - } - if (pos != 0) - debug_build_path = debug_build_path_string; + replace_variable_in_path(debug_build_path, "<project_directory_name>", project_path.filename().string()); + replace_variable_in_path(debug_build_path, "<default_build_path>", Config::get().project.default_build_path); if (debug_build_path.is_relative()) debug_build_path = project_path / debug_build_path; diff --git a/src/project_build.h b/src/project_build.h index 0198c88b..058ccd6e 100644 --- a/src/project_build.h +++ b/src/project_build.h @@ -8,8 +8,6 @@ namespace Project { public: virtual ~Build() {} - boost::filesystem::path project_path; - virtual boost::filesystem::path get_default_path(); virtual bool update_default(bool force=false) {return false;} virtual boost::filesystem::path get_debug_path(); @@ -17,44 +15,62 @@ namespace Project { virtual std::string get_compile_command() { return std::string(); } virtual boost::filesystem::path get_executable(const boost::filesystem::path &path) {return boost::filesystem::path();} + + const boost::filesystem::path& get_project_path() const noexcept { return project_path; } static std::unique_ptr<Build> create(const boost::filesystem::path &path); + + protected: + boost::filesystem::path project_path; }; - + + class CMakeBuild : public Build { ::CMake cmake; public: CMakeBuild(const boost::filesystem::path &path); - - bool update_default(bool force=false) override; - bool update_debug(bool force=false) override; - + + bool update_default(bool force) override; + + bool update_debug(bool force) override; + std::string get_compile_command() override; + boost::filesystem::path get_executable(const boost::filesystem::path &path) override; }; - + class MesonBuild : public Build { Meson meson; public: MesonBuild(const boost::filesystem::path &path); - - bool update_default(bool force=false) override; - bool update_debug(bool force=false) override; - + + bool update_default(bool force) override; + + bool update_debug(bool force) override; + std::string get_compile_command() override; + boost::filesystem::path get_executable(const boost::filesystem::path &path) override; }; class CargoBuild : public Build { public: - boost::filesystem::path get_default_path() override { return project_path/"target"/"debug"; } - bool update_default(bool force=false) override { return true; } + boost::filesystem::path get_default_path() override { return project_path / "target" / "debug"; } + + bool update_default(bool force) override { return true; } + boost::filesystem::path get_debug_path() override { return get_default_path(); } - bool update_debug(bool force=false) override { return true; } - + + bool update_debug(bool force) override { return true; } + std::string get_compile_command() override { return "cargo build"; } - boost::filesystem::path get_executable(const boost::filesystem::path &path) override { return get_debug_path()/project_path.filename(); } + + boost::filesystem::path get_executable(const boost::filesystem::path &path) override { + return get_debug_path() / project_path.filename(); + } + }; + + class NpmBuild : public Build { }; - class NpmBuild : public Build {}; } diff --git a/src/source_clang.cc b/src/source_clang.cc index 81c5357d..b3e14388 100644 --- a/src/source_clang.cc +++ b/src/source_clang.cc @@ -109,7 +109,7 @@ void Source::ClangViewParse::parse_initialize() { clangmm::remove_include_guard(buffer_raw); auto build=Project::Build::create(file_path); - if(build->project_path.empty()) + if(build->get_project_path().empty()) Info::get().print(file_path.filename().string()+": could not find a supported build system"); build->update_default(); auto arguments=CompileCommands::get_arguments(build->get_default_path(), file_path); @@ -868,7 +868,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file } auto build=Project::Build::create(this->file_path); - auto usages=Usages::Clang::get_usages(build->project_path, build->get_default_path(), build->get_debug_path(), identifier.spelling, identifier.cursor, translation_units); + auto usages=Usages::Clang::get_usages(build->get_project_path(), build->get_default_path(), build->get_debug_path(), identifier.spelling, identifier.cursor, translation_units); std::vector<Source::View*> renamed_views; std::vector<Usages::Clang::Usages*> usages_renamed; @@ -1347,7 +1347,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file } auto build=Project::Build::create(this->file_path); - auto usages_clang=Usages::Clang::get_usages(build->project_path, build->get_default_path(), build->get_debug_path(), {identifier.spelling}, {identifier.cursor}, translation_units); + auto usages_clang=Usages::Clang::get_usages(build->get_project_path(), build->get_default_path(), build->get_debug_path(), {identifier.spelling}, {identifier.cursor}, translation_units); for(auto &usage: usages_clang) { for(size_t c=0;c<usage.offsets.size();++c) { std::string line=Glib::Markup::escape_text(usage.lines[c]); @@ -1808,8 +1808,8 @@ void Source::ClangView::async_delete() { for(auto &view: views) { if(dynamic_cast<ClangView*>(view)) { auto build=Project::Build::create(view->file_path); - if(!build->project_path.empty()) - project_paths_in_use.emplace(build->project_path); + if(!build->get_project_path().empty()) + project_paths_in_use.emplace(build->get_project_path()); } } Usages::Clang::erase_unused_caches(project_paths_in_use); @@ -1847,7 +1847,7 @@ void Source::ClangView::async_delete() { if(clang_tokens) { auto build=Project::Build::create(file_path); - Usages::Clang::cache(build->project_path, build->get_default_path(), file_path, before_parse_time, project_paths_in_use, clang_tu.get(), clang_tokens.get()); + Usages::Clang::cache(build->get_project_path(), build->get_default_path(), file_path, before_parse_time, project_paths_in_use, clang_tu.get(), clang_tokens.get()); } if(full_reparse_thread.joinable()) diff --git a/src/source_language_protocol.cc b/src/source_language_protocol.cc index 3057c51d..f6f5de98 100644 --- a/src/source_language_protocol.cc +++ b/src/source_language_protocol.cc @@ -27,8 +27,8 @@ LanguageProtocol::Client::Client(std::string root_uri_, std::string language_id_ std::shared_ptr<LanguageProtocol::Client> LanguageProtocol::Client::get(const boost::filesystem::path &file_path, const std::string &language_id) { std::string root_uri; auto build=Project::Build::create(file_path); - if(!build->project_path.empty()) - root_uri=build->project_path.string(); + if(!build->get_project_path().empty()) + root_uri=build->get_project_path().string(); else root_uri=file_path.parent_path().string(); @@ -335,8 +335,8 @@ Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path auto build=Project::Build::create(file_path); if(auto npm_build=dynamic_cast<Project::NpmBuild*>(build.get())) { boost::system::error_code ec; - if(!npm_build->project_path.empty() && boost::filesystem::exists(npm_build->project_path/".flowconfig", ec)) { - auto executable=npm_build->project_path/"node_modules"/".bin"/"flow"; // It is recommended to use Flow binary installed in project, despite the security risk of doing so... + if(!npm_build->get_project_path().empty() && boost::filesystem::exists(npm_build->get_project_path()/".flowconfig", ec)) { + auto executable=npm_build->get_project_path()/"node_modules"/".bin"/"flow"; // It is recommended to use Flow binary installed in project, despite the security risk of doing so... if(boost::filesystem::exists(executable, ec)) flow_coverage_executable=executable; else @@ -701,8 +701,8 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { if(!error) { boost::filesystem::path project_path; auto build=Project::Build::create(file_path); - if(!build->project_path.empty()) - project_path=build->project_path; + if(!build->get_project_path().empty()) + project_path=build->get_project_path(); else project_path=file_path.parent_path(); try { diff --git a/src/window.cc b/src/window.cc index 62a6dc83..83f4a889 100644 --- a/src/window.cc +++ b/src/window.cc @@ -628,7 +628,7 @@ void Window::set_menu_actions() { } } auto build=Project::Build::create(search_path); - auto project_path=build->project_path; + auto project_path=build->get_project_path(); boost::filesystem::path default_path, debug_path; if(!project_path.empty()) { search_path=project_path; @@ -800,7 +800,7 @@ void Window::set_menu_actions() { auto dialog_iter=view->get_iter_for_dialog(); SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); std::vector<Source::Offset> rows; - auto project_path=Project::Build::create(view->file_path)->project_path; + auto project_path=Project::Build::create(view->file_path)->get_project_path(); if(project_path.empty()) { if(!Directories::get().path.empty()) project_path=Directories::get().path; @@ -1044,7 +1044,7 @@ void Window::set_menu_actions() { Project::current=Project::create(); if(Config::get().project.save_on_compile_or_run) - Project::save_files(Project::current->build->project_path); + Project::save_files(Project::current->build->get_project_path()); Project::current->compile_and_run(); }); @@ -1057,7 +1057,7 @@ void Window::set_menu_actions() { Project::current=Project::create(); if(Config::get().project.save_on_compile_or_run) - Project::save_files(Project::current->build->project_path); + Project::save_files(Project::current->build->get_project_path()); Project::current->compile(); }); @@ -1156,7 +1156,7 @@ void Window::set_menu_actions() { Project::current=Project::create(); if(Config::get().project.save_on_compile_or_run) - Project::save_files(Project::current->build->project_path); + Project::save_files(Project::current->build->get_project_path()); Project::current->debug_start(); }); From 6df4473e7c777635e50d81c3eabb4fae6a140b9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=88=E9=A1=BA=20=E5=88=98?= <yshliu0321@icloud.com> Date: Thu, 24 May 2018 13:59:05 +0800 Subject: [PATCH 08/12] Reimplement CMake support with CBP --- src/cmake.cc | 434 ++++++++++++++------------------------------ src/cmake.h | 26 --- src/files.h | 4 +- src/makefile_base.h | 1 - 4 files changed, 135 insertions(+), 330 deletions(-) diff --git a/src/cmake.cc b/src/cmake.cc index cfcc2479..b3f72b9d 100644 --- a/src/cmake.cc +++ b/src/cmake.cc @@ -3,336 +3,168 @@ #include "dialogs.h" #include "config.h" #include "terminal.h" -#include <regex> #include "compile_commands.h" - -CMake::CMake(const boost::filesystem::path &path) : MakefileBase(path, "CMakeLists.txt") {} - -bool CMake::update_default_build(const boost::filesystem::path &default_build_path, bool force) { - if(get_project_path().empty() || !boost::filesystem::exists(get_project_path()/"CMakeLists.txt") || default_build_path.empty()) - return false; - - if (!create_build_directory(default_build_path)) return false; - - if(!force && boost::filesystem::exists(default_build_path/"compile_commands.json")) - return true; - - 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().project.cmake.command+' '+ - filesystem::escape_argument(get_project_path().string())+" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON", default_build_path); - message.hide(); - if(exit_status==EXIT_SUCCESS) { - -#ifdef _WIN32 //Temporary fix to MSYS2's libclang - auto compile_commands_file=filesystem::read(compile_commands_path); - auto replace_drive = [&compile_commands_file](const std::string& param) { - size_t pos=0; - auto param_size = param.length(); - while((pos=compile_commands_file.find(param+"/", pos))!=std::string::npos) { - if(pos+param_size+1<compile_commands_file.size()) - compile_commands_file.replace(pos, param_size+2, param+compile_commands_file[pos+param_size+1]+":"); - else - break; - } - }; - replace_drive("-I"); - replace_drive("-isystem "); - filesystem::write(compile_commands_path, compile_commands_file); +#include "rapidxml/rapidxml.hpp" + +namespace { + enum class Configuration { + Debug, Release + }; + + class CMakeProjectUpdater { + public: + CMakeProjectUpdater(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path) : + m_ref_project_path(project_path), m_ref_build_path(build_path) {} + + CMakeProjectUpdater &set_configuration(Configuration conf) noexcept { + m_configuration = conf; + return *this; + } + + void do_update(bool force) { + check_arguments(); + boost::filesystem::create_directory(m_ref_build_path); + if(!force && boost::filesystem::exists(m_ref_build_path / "CMakeCache.txt")) return; + auto compile_commands_path = m_ref_build_path / "compile_commands.json"; + Dialog::Message message("Creating/updating build"); + auto exit_status = Terminal::get().process(Config::get().project.cmake.command+' '+ + filesystem::escape_argument(m_ref_project_path.string())+ + " -DCMAKE_EXPORT_COMPILE_COMMANDS=ON"+get_extra_arguments(), + m_ref_build_path); + if(exit_status == EXIT_SUCCESS) +#ifdef _WIN32 + applyMsys2Patch(compile_commands_path); #endif - return true; - } - return false; -} - -bool CMake::update_debug_build(const boost::filesystem::path &debug_build_path, bool force) { - if(get_project_path().empty() || !boost::filesystem::exists(get_project_path()/"CMakeLists.txt") || debug_build_path.empty()) - return false; - - if (!create_build_directory(debug_build_path)) return false; - - if(!force && boost::filesystem::exists(debug_build_path/"CMakeCache.txt")) - return true; - - Dialog::Message message("Creating/updating debug build"); - auto exit_status=Terminal::get().process(Config::get().project.cmake.command+' '+ - filesystem::escape_argument(get_project_path().string())+" -DCMAKE_BUILD_TYPE=Debug", debug_build_path); - message.hide(); - return exit_status == EXIT_SUCCESS; -} + message.hide(); + } -boost::filesystem::path CMake::get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) { - // CMake does not store in compile_commands.json if an object is part of an executable or not. - // Therefore, executables are first attempted found in the cmake files. These executables - // are then used to identify if a file in compile_commands.json is part of an executable or not - - auto parameters = get_functions_parameters("add_executable"); - - std::vector<boost::filesystem::path> cmake_executables; - for(auto ¶meter: parameters) { - if(parameter.second.size()>1 && parameter.second[0].size()>0 && parameter.second[0].compare(0, 2, "${")!=0) { - auto executable=(parameter.first.parent_path()/parameter.second[0]).string(); - auto project_path_str=get_project_path().string(); - size_t pos=executable.find(project_path_str); - if(pos!=std::string::npos) - executable.replace(pos, project_path_str.size(), build_path.string()); - cmake_executables.emplace_back(executable); + private: + void check_arguments() { + if(m_ref_project_path.empty() || m_ref_build_path.empty()) + throw std::runtime_error("Arguments Incomplete"); + if(!boost::filesystem::exists(m_ref_project_path / "CMakeLists.txt")) + throw std::runtime_error("CMake Project Not Found"); } - } - - - CompileCommands compile_commands(build_path); - std::vector<std::pair<boost::filesystem::path, boost::filesystem::path>> command_files_and_maybe_executables; - for(auto &command: compile_commands.commands) { - auto command_file=filesystem::get_normal_path(command.file); - auto values=command.parameter_values("-o"); - if(!values.empty()) { - size_t pos; - values[0].erase(0, 11); - if((pos=values[0].find(".dir"))!=std::string::npos) { - auto executable=command.directory/values[0].substr(0, pos); - command_files_and_maybe_executables.emplace_back(command_file, executable); - } + + std::string get_extra_arguments() const { + return m_configuration == Configuration::Debug ? " -DCMAKE_BUILD_TYPE=Debug" : " -DCMAKE_BUILD_TYPE=Release"; } - } - - size_t best_match_size=-1; - boost::filesystem::path best_match_executable; - - for(auto &cmake_executable: cmake_executables) { - for(auto &command_file_and_maybe_executable: command_files_and_maybe_executables) { - auto &command_file=command_file_and_maybe_executable.first; - auto &maybe_executable=command_file_and_maybe_executable.second; - if(cmake_executable==maybe_executable) { - if(command_file==file_path) - return maybe_executable; - auto command_file_directory=command_file.parent_path(); - if(filesystem::file_in_path(file_path, command_file_directory)) { - auto size=static_cast<size_t>(std::distance(command_file_directory.begin(), command_file_directory.end())); - if(best_match_size==static_cast<size_t>(-1) || best_match_size<size) { - best_match_size=size; - best_match_executable=maybe_executable; - } + + //Temporary fix to MSYS2's libclang + void applyMsys2Patch(const boost::filesystem::path &compile_commands_path) const { + auto compile_commands_file = filesystem::read(compile_commands_path); + const std::array<const std::string, 2> drives = {"-I", "-isystem"}; + for(const auto &target: drives) { + size_t pos = 0; + auto param_size = target.length(); + while((pos = compile_commands_file.find(target+"/", pos)) != std::string::npos) { + if(pos+param_size+1 < compile_commands_file.size()) + compile_commands_file.replace(pos, param_size+2, target+compile_commands_file[pos+param_size+1]+":"); + else + break; } } + filesystem::write(compile_commands_path, compile_commands_file); } - } - if(!best_match_executable.empty()) - return best_match_executable; - - for(auto &command_file_and_maybe_executable: command_files_and_maybe_executables) { - auto &command_file=command_file_and_maybe_executable.first; - auto &maybe_executable=command_file_and_maybe_executable.second; - if(command_file==file_path) - return maybe_executable; - auto command_file_directory=command_file.parent_path(); - if(filesystem::file_in_path(file_path, command_file_directory)) { - auto size=static_cast<size_t>(std::distance(command_file_directory.begin(), command_file_directory.end())); - if(best_match_size==static_cast<size_t>(-1) || best_match_size<size) { - best_match_size=size; - best_match_executable=maybe_executable; - } - } - } - return best_match_executable; -} -void CMake::read_files() { - for(auto &path: paths) - files.emplace_back(filesystem::read(path)); + Configuration m_configuration; + const boost::filesystem::path &m_ref_project_path; + const boost::filesystem::path &m_ref_build_path; + }; } -void CMake::remove_tabs() { - for(auto &file: files) { - for(auto &chr: file) { - if(chr=='\t') - chr=' '; - } +CMake::CMake(const boost::filesystem::path &path) : MakefileBase(path, "CMakeLists.txt") {} + +bool CMake::update_default_build(const boost::filesystem::path &default_build_path, bool force) { + try { + CMakeProjectUpdater(get_project_path(), default_build_path).set_configuration(Configuration::Release).do_update( + force); } + catch(std::exception &e) { + Terminal::get().print("Configuration For: "+get_project_path().string()+" Failed With: "+e.what()+'\n'); + return false; + } + return true; } -void CMake::remove_comments() { - for(auto &file: files) { - size_t pos=0; - size_t comment_start; - bool inside_comment=false; - while(pos<file.size()) { - if(!inside_comment && file[pos]=='#') { - comment_start=pos; - inside_comment=true; - } - if(inside_comment && file[pos]=='\n') { - file.erase(comment_start, pos-comment_start); - pos-=pos-comment_start; - inside_comment=false; - } - pos++; - } - if(inside_comment) - file.erase(comment_start); +bool CMake::update_debug_build(const boost::filesystem::path &debug_build_path, bool force) { + try { + CMakeProjectUpdater(get_project_path(), debug_build_path).set_configuration(Configuration::Debug).do_update(force); + } + catch(std::exception &e) { + Terminal::get().print("Configuration For: "+get_project_path().string()+" Failed With: "+e.what()+'\n'); + return false; } + return true; } -void CMake::remove_newlines_inside_parentheses() { - for(auto &file: files) { - size_t pos=0; - bool inside_para=false; - bool inside_quote=false; - char last_char=0; - while(pos<file.size()) { - if(!inside_quote && file[pos]=='"' && last_char!='\\') - inside_quote=true; - else if(inside_quote && file[pos]=='"' && last_char!='\\') - inside_quote=false; +namespace { + class GetExecutableHelper { + public: + explicit GetExecutableHelper(const boost::filesystem::path &build_path); - else if(!inside_quote && file[pos]=='(') - inside_para=true; - else if(!inside_quote && file[pos]==')') - inside_para=false; + auto &get_executables() const noexcept { return executables; } - else if(inside_para && file[pos]=='\n') - file.replace(pos, 1, 1, ' '); - last_char=file[pos]; - pos++; + private: + static boost::filesystem::path find_project_file(const boost::filesystem::path &build_path) { + for(boost::filesystem::directory_iterator it(build_path), end; it != end; ++it) { + if(it->path().extension().compare(std::string(".cbp")) == 0) + return boost::filesystem::absolute(it->path()); + } + return ""; } - } -} -void CMake::parse_variable_parameters(std::string &data) { - size_t pos=0; - bool inside_quote=false; - char last_char=0; - while(pos<data.size()) { - if(!inside_quote && data[pos]=='"' && last_char!='\\') { - inside_quote=true; - data.erase(pos, 1); //TODO: instead remove quote-mark if pasted into a quote, for instance: "test${test}test"<-remove quotes from ${test} - pos--; - } - else if(inside_quote && data[pos]=='"' && last_char!='\\') { - inside_quote=false; - data.erase(pos, 1); //TODO: instead remove quote-mark if pasted into a quote, for instance: "test${test}test"<-remove quotes from ${test} - pos--; + static std::string file_reader(const boost::filesystem::path &build_path) { + std::stringstream ss; + std::ifstream inf(build_path.string()); + ss << inf.rdbuf(); + return ss.str(); } - else if(!inside_quote && data[pos]==' ' && pos+1<data.size() && data[pos+1]==' ') { - data.erase(pos, 1); - pos--; + + void parse_project(const boost::filesystem::path &build_path) { + const auto project_file = file_reader(find_project_file(build_path)); + document.parse<0>(const_cast<char *>(project_file.c_str())); } - - if(pos!=static_cast<size_t>(-1)) - last_char=data[pos]; - pos++; - } - for(auto &var: variables) { - auto pos=data.find("${"+var.first+'}'); - while(pos!=std::string::npos) { - data.replace(pos, var.first.size()+3, var.second); - pos=data.find("${"+var.first+'}'); + + static bool is_fast_target(const std::string &target) { + if(target.size() > 5) + return target.substr(target.size()-5, 5) == "/fast"; + return false; } - } - - //Remove variables we do not know: - pos=data.find("${"); - auto pos_end=data.find('}', pos+2); - while(pos!=std::string::npos && pos_end!=std::string::npos) { - data.erase(pos, pos_end-pos+1); - pos=data.find("${"); - pos_end=data.find('}', pos+2); - } -} -void CMake::parse() { - read_files(); - remove_tabs(); - remove_comments(); - remove_newlines_inside_parentheses(); - parsed=true; -} + static bool is_executable(int type) noexcept { return type == 0 || type == 1 || type == 5; } -std::vector<std::string> CMake::get_function_parameters(std::string &data) { - std::vector<std::string> parameters; - size_t pos=0; - size_t parameter_pos=0; - bool inside_quote=false; - char last_char=0; - while(pos<data.size()) { - if(!inside_quote && data[pos]=='"' && last_char!='\\') { - inside_quote=true; - data.erase(pos, 1); - pos--; - } - else if(inside_quote && data[pos]=='"' && last_char!='\\') { - inside_quote=false; - data.erase(pos, 1); - pos--; - } - else if(!inside_quote && pos+1<data.size() && data[pos]==' ' && data[pos+1]==' ') { - data.erase(pos, 1); - pos--; - } - else if(!inside_quote && data[pos]==' ') { - parameters.emplace_back(data.substr(parameter_pos, pos-parameter_pos)); - if(pos+1<data.size()) - parameter_pos=pos+1; - } - - if(pos!=static_cast<size_t>(-1)) - last_char=data[pos]; - pos++; - } - parameters.emplace_back(data.substr(parameter_pos)); - for(auto &var: variables) { - for(auto ¶meter: parameters) { - auto pos=parameter.find("${"+var.first+'}'); - while(pos!=std::string::npos) { - parameter.replace(pos, var.first.size()+3, var.second); - pos=parameter.find("${"+var.first+'}'); + static auto get_options(const rapidxml::xml_node<> *target) { + std::map<std::string, std::string> options; + for(auto option = target->first_node("Option"); option != nullptr; option = option->next_sibling("Option")) { + auto attr = option->first_attribute(); + options[std::string(attr->name(), attr->name_size())] = std::string(attr->value(), attr->value_size()); + } + return options; + } + + rapidxml::xml_document<> document; + std::vector<boost::filesystem::path> executables; + }; + + GetExecutableHelper::GetExecutableHelper(const boost::filesystem::path &build_path) { + parse_project(build_path); + auto build = document.first_node("CodeBlocks_project_file")->first_node("Project")->first_node("Build"); + for(auto target = build->first_node("Target"); target != nullptr; target = target->next_sibling("Target")) { + auto title_attr = target->first_attribute("title"); + if(!is_fast_target(std::string(title_attr->value(), title_attr->value_size()))) { + auto options = get_options(target); + if(is_executable(options["type"][0]-'0')) + executables.emplace_back(options["output"]); } } } - return parameters; + } -std::vector<std::pair<boost::filesystem::path, std::vector<std::string> > > CMake::get_functions_parameters(const std::string &name) { - const std::regex function_regex("^ *"+name+R"( *\( *(.*)\) *\r?$)", std::regex::icase); - variables.clear(); - if(!parsed) - parse(); - std::vector<std::pair<boost::filesystem::path, std::vector<std::string> > > functions; - for(size_t c=0;c<files.size();++c) { - size_t pos=0; - while(pos<files[c].size()) { - auto start_line=pos; - auto end_line=files[c].find('\n', start_line); - if(end_line==std::string::npos) - end_line=files[c].size(); - if(end_line>start_line) { - auto line=files[c].substr(start_line, end_line-start_line); - std::smatch sm; - const static std::regex set_regex(R"(^ *set *\( *([A-Za-z_][A-Za-z_0-9]*) +(.*)\) *\r?$)", std::regex::icase); - const static std::regex project_regex(R"(^ *project *\( *([^ ]+).*\) *\r?$)", std::regex::icase); - if(std::regex_match(line, sm, set_regex)) { - auto data=sm[2].str(); - while(data.size()>0 && data.back()==' ') - data.pop_back(); - parse_variable_parameters(data); - variables[sm[1].str()]=data; - } - else if(std::regex_match(line, sm, project_regex)) { - auto data=sm[1].str(); - parse_variable_parameters(data); - variables["CMAKE_PROJECT_NAME"]=data; //TODO: is this variable deprecated/non-standard? - variables["PROJECT_NAME"]=data; - } - if(std::regex_match(line, sm, function_regex)) { - auto data=sm[1].str(); - while(data.size()>0 && data.back()==' ') - data.pop_back(); - auto parameters=get_function_parameters(data); - functions.emplace_back(paths[c], parameters); - } - } - pos=end_line+1; - } - } - return functions; +boost::filesystem::path +CMake::get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &) { + GetExecutableHelper helper(build_path); + auto &exec = helper.get_executables(); + return exec.empty() ? "" : exec[0]; } diff --git a/src/cmake.h b/src/cmake.h index ebb68671..1fa22ce9 100644 --- a/src/cmake.h +++ b/src/cmake.h @@ -1,8 +1,6 @@ #pragma once #include "makefile_base.h" -#include <unordered_map> -#include <unordered_set> class CMake : public MakefileBase { public: @@ -14,28 +12,4 @@ class CMake : public MakefileBase { boost::filesystem::path get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) override; - -private: - std::vector<boost::filesystem::path> paths; - std::vector<std::string> files; - std::unordered_map<std::string, std::string> variables; - - void read_files(); - - void remove_tabs(); - - void remove_comments(); - - void remove_newlines_inside_parentheses(); - - void parse_variable_parameters(std::string &data); - - void parse(); - - std::vector<std::string> get_function_parameters(std::string &data); - - std::vector<std::pair<boost::filesystem::path, std::vector<std::string> > > - get_functions_parameters(const std::string &name); - - bool parsed = false; }; diff --git a/src/files.h b/src/files.h index d72bbcf8..7e10dbe3 100644 --- a/src/files.h +++ b/src/files.h @@ -164,10 +164,10 @@ const std::string default_config_file = R"RAW({ "cmake": {)RAW" #ifdef _WIN32 R"RAW( - "command": "cmake -G\"MSYS Makefiles\"",)RAW" + "command": "cmake -G\"CodeBlocks - MinGW Makefiles\"",)RAW" #else R"RAW( - "command": "cmake",)RAW" + "command": "cmake -G\"CodeBlocks - Unix Makefiles\"",)RAW" #endif R"RAW( "compile_command": "cmake --build ." diff --git a/src/makefile_base.h b/src/makefile_base.h index cd080804..d2493ab6 100644 --- a/src/makefile_base.h +++ b/src/makefile_base.h @@ -1,7 +1,6 @@ #pragma once #include <boost/filesystem.hpp> -#include <vector> class MakefileBase { public: From 42c54fcc0796f2d503839cf5c803232abffd822e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=88=E9=A1=BA=20=E5=88=98?= <yshliu0321@icloud.com> Date: Thu, 24 May 2018 14:11:04 +0800 Subject: [PATCH 09/12] Add rapidxml --- src/cmake.cc | 2 +- src/rapidxml/rapidxml.h | 1639 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 1640 insertions(+), 1 deletion(-) create mode 100644 src/rapidxml/rapidxml.h diff --git a/src/cmake.cc b/src/cmake.cc index b3f72b9d..118c3488 100644 --- a/src/cmake.cc +++ b/src/cmake.cc @@ -4,7 +4,7 @@ #include "config.h" #include "terminal.h" #include "compile_commands.h" -#include "rapidxml/rapidxml.hpp" +#include "rapidxml/rapidxml.h" namespace { enum class Configuration { diff --git a/src/rapidxml/rapidxml.h b/src/rapidxml/rapidxml.h new file mode 100644 index 00000000..49e1b2aa --- /dev/null +++ b/src/rapidxml/rapidxml.h @@ -0,0 +1,1639 @@ +#pragma once + +#include <cstdlib> +#include <cassert> +#include <new> + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4127) +#endif + +#include <exception> + +#define RAPIDXML_PARSE_ERROR(what, where) throw parse_error(what, where) + +namespace rapidxml { + + class parse_error : public std::exception { + public: + + parse_error(const char *what, void *where) + : m_what(what), m_where(where) { + } + + virtual const char *what() const throw() { + return m_what; + } + + template<class Ch> + Ch *where() const { + return reinterpret_cast<Ch *>(m_where); + } + + private: + const char *m_what; + void *m_where; + }; +} + +#ifndef RAPIDXML_STATIC_POOL_SIZE + +#define RAPIDXML_STATIC_POOL_SIZE (64 * 1024) +#endif +#ifndef RAPIDXML_DYNAMIC_POOL_SIZE + +#define RAPIDXML_DYNAMIC_POOL_SIZE (64 * 1024) +#endif +#ifndef RAPIDXML_ALIGNMENT + +#define RAPIDXML_ALIGNMENT sizeof(void *) +#endif +namespace rapidxml { + template<class Ch> + class xml_node; + + template<class Ch> + class xml_attribute; + + template<class Ch> + class xml_document; + + enum node_type { + node_document, + node_element, + node_data, + node_cdata, + node_comment, + node_declaration, + node_doctype, + node_pi + }; + + const int parse_no_data_nodes = 0x1; + + const int parse_no_element_values = 0x2; + + const int parse_no_string_terminators = 0x4; + + const int parse_no_entity_translation = 0x8; + + const int parse_no_utf8 = 0x10; + + const int parse_declaration_node = 0x20; + + const int parse_comment_nodes = 0x40; + + const int parse_doctype_node = 0x80; + + const int parse_pi_nodes = 0x100; + + const int parse_validate_closing_tags = 0x200; + + const int parse_trim_whitespace = 0x400; + + const int parse_normalize_whitespace = 0x800; + + const int parse_default = 0; + + const int parse_non_destructive = parse_no_string_terminators | parse_no_entity_translation; + + const int parse_fastest = parse_non_destructive | parse_no_data_nodes; + + const int parse_full = + parse_declaration_node | parse_comment_nodes | parse_doctype_node | parse_pi_nodes | parse_validate_closing_tags; + + namespace internal { + + template<int Dummy> + struct lookup_tables { + static const unsigned char lookup_whitespace[256]; + static const unsigned char lookup_node_name[256]; + static const unsigned char lookup_text[256]; + static const unsigned char lookup_text_pure_no_ws[256]; + static const unsigned char lookup_text_pure_with_ws[256]; + static const unsigned char lookup_attribute_name[256]; + static const unsigned char lookup_attribute_data_1[256]; + static const unsigned char lookup_attribute_data_1_pure[256]; + static const unsigned char lookup_attribute_data_2[256]; + static const unsigned char lookup_attribute_data_2_pure[256]; + static const unsigned char lookup_digits[256]; + static const unsigned char lookup_upcase[256]; + }; + + template<class Ch> + inline std::size_t measure(const Ch *p) { + const Ch *tmp = p; + while(*tmp) + ++tmp; + return tmp-p; + } + + template<class Ch> + inline bool compare(const Ch *p1, std::size_t size1, const Ch *p2, std::size_t size2, bool case_sensitive) { + if(size1 != size2) + return false; + if(case_sensitive) { + for(const Ch *end = p1+size1; p1 < end; ++p1, ++p2) + if(*p1 != *p2) + return false; + } else { + for(const Ch *end = p1+size1; p1 < end; ++p1, ++p2) + if(lookup_tables<0>::lookup_upcase[static_cast<unsigned char>(*p1)] != + lookup_tables<0>::lookup_upcase[static_cast<unsigned char>(*p2)]) + return false; + } + return true; + } + } + + template<class Ch = char> + class memory_pool { + public: + + typedef void *(alloc_func)(std::size_t); + + typedef void (free_func)(void *); + + memory_pool() + : m_alloc_func(0), m_free_func(0) { + init(); + } + + ~memory_pool() { + clear(); + } + + xml_node<Ch> *allocate_node(node_type type, + const Ch *name = 0, const Ch *value = 0, + std::size_t name_size = 0, std::size_t value_size = 0) { + void *memory = allocate_aligned(sizeof(xml_node<Ch>)); + xml_node<Ch> *node = new(memory) xml_node<Ch>(type); + if(name) { + if(name_size > 0) + node->name(name, name_size); + else + node->name(name); + } + if(value) { + if(value_size > 0) + node->value(value, value_size); + else + node->value(value); + } + return node; + } + + xml_attribute<Ch> *allocate_attribute(const Ch *name = 0, const Ch *value = 0, + std::size_t name_size = 0, std::size_t value_size = 0) { + void *memory = allocate_aligned(sizeof(xml_attribute<Ch>)); + xml_attribute<Ch> *attribute = new(memory) xml_attribute<Ch>; + if(name) { + if(name_size > 0) + attribute->name(name, name_size); + else + attribute->name(name); + } + if(value) { + if(value_size > 0) + attribute->value(value, value_size); + else + attribute->value(value); + } + return attribute; + } + + Ch *allocate_string(const Ch *source = 0, std::size_t size = 0) { + assert(source || size); + if(size == 0) + size = internal::measure(source)+1; + Ch *result = static_cast<Ch *>(allocate_aligned(size * sizeof(Ch))); + if(source) + for(std::size_t i = 0; i < size; ++i) + result[i] = source[i]; + return result; + } + + xml_node<Ch> *clone_node(const xml_node<Ch> *source, xml_node<Ch> *result = 0) { + if(result) { + result->remove_all_attributes(); + result->remove_all_nodes(); + result->type(source->type()); + } else + result = allocate_node(source->type()); + + result->name(source->name(), source->name_size()); + result->value(source->value(), source->value_size()); + + for(xml_node<Ch> *child = source->first_node(); child; child = child->next_sibling()) + result->append_node(clone_node(child)); + for(xml_attribute<Ch> *attr = source->first_attribute(); attr; attr = attr->next_attribute()) + result->append_attribute( + allocate_attribute(attr->name(), attr->value(), attr->name_size(), attr->value_size())); + return result; + } + + void clear() { + while(m_begin != m_static_memory) { + char *previous_begin = reinterpret_cast<header *>(align(m_begin))->previous_begin; + if(m_free_func) + m_free_func(m_begin); + else + delete[] m_begin; + m_begin = previous_begin; + } + init(); + } + + void set_allocator(alloc_func *af, free_func *ff) { + assert(m_begin == m_static_memory && m_ptr == align(m_begin)); + m_alloc_func = af; + m_free_func = ff; + } + + private: + struct header { + char *previous_begin; + }; + + void init() { + m_begin = m_static_memory; + m_ptr = align(m_begin); + m_end = m_static_memory+sizeof(m_static_memory); + } + + char *align(char *ptr) { + std::size_t alignment = ((RAPIDXML_ALIGNMENT-(std::size_t(ptr) & (RAPIDXML_ALIGNMENT-1))) & + (RAPIDXML_ALIGNMENT-1)); + return ptr+alignment; + } + + char *allocate_raw(std::size_t size) { + void *memory; + if(m_alloc_func) { + memory = m_alloc_func(size); + assert( + memory); + } else { + memory = new char[size]; +#ifdef RAPIDXML_NO_EXCEPTIONS + if (!memory) + RAPIDXML_PARSE_ERROR("out of memory", 0); +#endif + } + return static_cast<char *>(memory); + } + + void *allocate_aligned(std::size_t size) { + char *result = align(m_ptr); + + if(result+size > m_end) { + std::size_t pool_size = RAPIDXML_DYNAMIC_POOL_SIZE; + if(pool_size < size) + pool_size = size; + + std::size_t alloc_size = sizeof(header)+(2 * RAPIDXML_ALIGNMENT-2)+ + pool_size; + char *raw_memory = allocate_raw(alloc_size); + + char *pool = align(raw_memory); + header *new_header = reinterpret_cast<header *>(pool); + new_header->previous_begin = m_begin; + m_begin = raw_memory; + m_ptr = pool+sizeof(header); + m_end = raw_memory+alloc_size; + + result = align(m_ptr); + } + + m_ptr = result+size; + return result; + } + + char *m_begin; + char *m_ptr; + char *m_end; + char m_static_memory[RAPIDXML_STATIC_POOL_SIZE]; + alloc_func *m_alloc_func; + free_func *m_free_func; + }; + + template<class Ch = char> + class xml_base { + public: + + xml_base() + : m_name(0), m_value(0), m_parent(0) { + } + + Ch *name() const { + return m_name ? m_name : nullstr(); + } + + std::size_t name_size() const { + return m_name ? m_name_size : 0; + } + + Ch *value() const { + return m_value ? m_value : nullstr(); + } + + std::size_t value_size() const { + return m_value ? m_value_size : 0; + } + + void name(const Ch *name, std::size_t size) { + m_name = const_cast<Ch *>(name); + m_name_size = size; + } + + void name(const Ch *name) { + this->name(name, internal::measure(name)); + } + + void value(const Ch *value, std::size_t size) { + m_value = const_cast<Ch *>(value); + m_value_size = size; + } + + void value(const Ch *value) { + this->value(value, internal::measure(value)); + } + + xml_node<Ch> *parent() const { + return m_parent; + } + + protected: + + static Ch *nullstr() { + static Ch zero = Ch('\0'); + return &zero; + } + + Ch *m_name; + Ch *m_value; + std::size_t m_name_size; + std::size_t m_value_size; + xml_node<Ch> *m_parent; + }; + + template<class Ch = char> + class xml_attribute : public xml_base<Ch> { + friend class xml_node<Ch>; + + public: + + xml_attribute() { + } + + xml_document<Ch> *document() const { + if(xml_node<Ch> *node = this->parent()) { + while(node->parent()) + node = node->parent(); + return node->type() == node_document ? static_cast<xml_document<Ch> *>(node) : 0; + } else + return 0; + } + + xml_attribute<Ch> * + previous_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const { + if(name) { + if(name_size == 0) + name_size = internal::measure(name); + for(xml_attribute<Ch> *attribute = m_prev_attribute; attribute; attribute = attribute->m_prev_attribute) + if(internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive)) + return attribute; + return 0; + } else + return this->m_parent ? m_prev_attribute : 0; + } + + xml_attribute<Ch> *next_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const { + if(name) { + if(name_size == 0) + name_size = internal::measure(name); + for(xml_attribute<Ch> *attribute = m_next_attribute; attribute; attribute = attribute->m_next_attribute) + if(internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive)) + return attribute; + return 0; + } else + return this->m_parent ? m_next_attribute : 0; + } + + private: + xml_attribute<Ch> *m_prev_attribute; + xml_attribute<Ch> *m_next_attribute; + }; + + template<class Ch = char> + class xml_node : public xml_base<Ch> { + public: + + xml_node(node_type type) + : m_type(type), m_first_node(0), m_first_attribute(0) { + } + + node_type type() const { + return m_type; + } + + xml_document<Ch> *document() const { + xml_node<Ch> *node = const_cast<xml_node<Ch> *>(this); + while(node->parent()) + node = node->parent(); + return node->type() == node_document ? static_cast<xml_document<Ch> *>(node) : 0; + } + + xml_node<Ch> *first_node(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const { + if(name) { + if(name_size == 0) + name_size = internal::measure(name); + for(xml_node<Ch> *child = m_first_node; child; child = child->next_sibling()) + if(internal::compare(child->name(), child->name_size(), name, name_size, case_sensitive)) + return child; + return 0; + } else + return m_first_node; + } + + xml_node<Ch> *last_node(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const { + assert(m_first_node); + if(name) { + if(name_size == 0) + name_size = internal::measure(name); + for(xml_node<Ch> *child = m_last_node; child; child = child->previous_sibling()) + if(internal::compare(child->name(), child->name_size(), name, name_size, case_sensitive)) + return child; + return 0; + } else + return m_last_node; + } + + xml_node<Ch> *previous_sibling(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const { + assert(this->m_parent); + if(name) { + if(name_size == 0) + name_size = internal::measure(name); + for(xml_node<Ch> *sibling = m_prev_sibling; sibling; sibling = sibling->m_prev_sibling) + if(internal::compare(sibling->name(), sibling->name_size(), name, name_size, case_sensitive)) + return sibling; + return 0; + } else + return m_prev_sibling; + } + + xml_node<Ch> *next_sibling(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const { + assert(this->m_parent); + if(name) { + if(name_size == 0) + name_size = internal::measure(name); + for(xml_node<Ch> *sibling = m_next_sibling; sibling; sibling = sibling->m_next_sibling) + if(internal::compare(sibling->name(), sibling->name_size(), name, name_size, case_sensitive)) + return sibling; + return 0; + } else + return m_next_sibling; + } + + xml_attribute<Ch> * + first_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const { + if(name) { + if(name_size == 0) + name_size = internal::measure(name); + for(xml_attribute<Ch> *attribute = m_first_attribute; attribute; attribute = attribute->m_next_attribute) + if(internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive)) + return attribute; + return 0; + } else + return m_first_attribute; + } + + xml_attribute<Ch> *last_attribute(const Ch *name = 0, std::size_t name_size = 0, bool case_sensitive = true) const { + if(name) { + if(name_size == 0) + name_size = internal::measure(name); + for(xml_attribute<Ch> *attribute = m_last_attribute; attribute; attribute = attribute->m_prev_attribute) + if(internal::compare(attribute->name(), attribute->name_size(), name, name_size, case_sensitive)) + return attribute; + return 0; + } else + return m_first_attribute ? m_last_attribute : 0; + } + + void type(node_type type) { + m_type = type; + } + + void prepend_node(xml_node<Ch> *child) { + assert(child && !child->parent() && child->type() != node_document); + if(first_node()) { + child->m_next_sibling = m_first_node; + m_first_node->m_prev_sibling = child; + } else { + child->m_next_sibling = 0; + m_last_node = child; + } + m_first_node = child; + child->m_parent = this; + child->m_prev_sibling = 0; + } + + void append_node(xml_node<Ch> *child) { + assert(child && !child->parent() && child->type() != node_document); + if(first_node()) { + child->m_prev_sibling = m_last_node; + m_last_node->m_next_sibling = child; + } else { + child->m_prev_sibling = 0; + m_first_node = child; + } + m_last_node = child; + child->m_parent = this; + child->m_next_sibling = 0; + } + + void insert_node(xml_node<Ch> *where, xml_node<Ch> *child) { + assert(!where || where->parent() == this); + assert(child && !child->parent() && child->type() != node_document); + if(where == m_first_node) + prepend_node(child); + else if(where == 0) + append_node(child); + else { + child->m_prev_sibling = where->m_prev_sibling; + child->m_next_sibling = where; + where->m_prev_sibling->m_next_sibling = child; + where->m_prev_sibling = child; + child->m_parent = this; + } + } + + void remove_first_node() { + assert(first_node()); + xml_node<Ch> *child = m_first_node; + m_first_node = child->m_next_sibling; + if(child->m_next_sibling) + child->m_next_sibling->m_prev_sibling = 0; + else + m_last_node = 0; + child->m_parent = 0; + } + + void remove_last_node() { + assert(first_node()); + xml_node<Ch> *child = m_last_node; + if(child->m_prev_sibling) { + m_last_node = child->m_prev_sibling; + child->m_prev_sibling->m_next_sibling = 0; + } else + m_first_node = 0; + child->m_parent = 0; + } + + void remove_node(xml_node<Ch> *where) { + assert(where && where->parent() == this); + assert(first_node()); + if(where == m_first_node) + remove_first_node(); + else if(where == m_last_node) + remove_last_node(); + else { + where->m_prev_sibling->m_next_sibling = where->m_next_sibling; + where->m_next_sibling->m_prev_sibling = where->m_prev_sibling; + where->m_parent = 0; + } + } + + void remove_all_nodes() { + for(xml_node<Ch> *node = first_node(); node; node = node->m_next_sibling) + node->m_parent = 0; + m_first_node = 0; + } + + void prepend_attribute(xml_attribute<Ch> *attribute) { + assert(attribute && !attribute->parent()); + if(first_attribute()) { + attribute->m_next_attribute = m_first_attribute; + m_first_attribute->m_prev_attribute = attribute; + } else { + attribute->m_next_attribute = 0; + m_last_attribute = attribute; + } + m_first_attribute = attribute; + attribute->m_parent = this; + attribute->m_prev_attribute = 0; + } + + void append_attribute(xml_attribute<Ch> *attribute) { + assert(attribute && !attribute->parent()); + if(first_attribute()) { + attribute->m_prev_attribute = m_last_attribute; + m_last_attribute->m_next_attribute = attribute; + } else { + attribute->m_prev_attribute = 0; + m_first_attribute = attribute; + } + m_last_attribute = attribute; + attribute->m_parent = this; + attribute->m_next_attribute = 0; + } + + void insert_attribute(xml_attribute<Ch> *where, xml_attribute<Ch> *attribute) { + assert(!where || where->parent() == this); + assert(attribute && !attribute->parent()); + if(where == m_first_attribute) + prepend_attribute(attribute); + else if(where == 0) + append_attribute(attribute); + else { + attribute->m_prev_attribute = where->m_prev_attribute; + attribute->m_next_attribute = where; + where->m_prev_attribute->m_next_attribute = attribute; + where->m_prev_attribute = attribute; + attribute->m_parent = this; + } + } + + void remove_first_attribute() { + assert(first_attribute()); + xml_attribute<Ch> *attribute = m_first_attribute; + if(attribute->m_next_attribute) { + attribute->m_next_attribute->m_prev_attribute = 0; + } else + m_last_attribute = 0; + attribute->m_parent = 0; + m_first_attribute = attribute->m_next_attribute; + } + + void remove_last_attribute() { + assert(first_attribute()); + xml_attribute<Ch> *attribute = m_last_attribute; + if(attribute->m_prev_attribute) { + attribute->m_prev_attribute->m_next_attribute = 0; + m_last_attribute = attribute->m_prev_attribute; + } else + m_first_attribute = 0; + attribute->m_parent = 0; + } + + void remove_attribute(xml_attribute<Ch> *where) { + assert(first_attribute() && where->parent() == this); + if(where == m_first_attribute) + remove_first_attribute(); + else if(where == m_last_attribute) + remove_last_attribute(); + else { + where->m_prev_attribute->m_next_attribute = where->m_next_attribute; + where->m_next_attribute->m_prev_attribute = where->m_prev_attribute; + where->m_parent = 0; + } + } + + void remove_all_attributes() { + for(xml_attribute<Ch> *attribute = first_attribute(); attribute; attribute = attribute->m_next_attribute) + attribute->m_parent = 0; + m_first_attribute = 0; + } + + private: + + xml_node(const xml_node &); + + void operator=(const xml_node &); + + + + // + + + node_type m_type; + xml_node<Ch> *m_first_node; + xml_node<Ch> *m_last_node; + xml_attribute<Ch> *m_first_attribute; + xml_attribute<Ch> *m_last_attribute; + xml_node<Ch> *m_prev_sibling; + xml_node<Ch> *m_next_sibling; + }; + + template<class Ch = char> + class xml_document : public xml_node<Ch>, public memory_pool<Ch> { + public: + + xml_document() + : xml_node<Ch>(node_document) { + } + + template<int Flags> + void parse(Ch *text) { + assert(text); + + this->remove_all_nodes(); + this->remove_all_attributes(); + + parse_bom<Flags>(text); + + while(1) { + skip<whitespace_pred, Flags>(text); + if(*text == 0) + break; + + if(*text == Ch('<')) { + ++text; + if(xml_node<Ch> *node = parse_node<Flags>(text)) + this->append_node(node); + } else + RAPIDXML_PARSE_ERROR("expected <", text); + } + } + + void clear() { + this->remove_all_nodes(); + this->remove_all_attributes(); + memory_pool<Ch>::clear(); + } + + private: + + struct whitespace_pred { + static unsigned char test(Ch ch) { + return internal::lookup_tables<0>::lookup_whitespace[static_cast<unsigned char>(ch)]; + } + }; + + struct node_name_pred { + static unsigned char test(Ch ch) { + return internal::lookup_tables<0>::lookup_node_name[static_cast<unsigned char>(ch)]; + } + }; + + struct attribute_name_pred { + static unsigned char test(Ch ch) { + return internal::lookup_tables<0>::lookup_attribute_name[static_cast<unsigned char>(ch)]; + } + }; + + struct text_pred { + static unsigned char test(Ch ch) { + return internal::lookup_tables<0>::lookup_text[static_cast<unsigned char>(ch)]; + } + }; + + struct text_pure_no_ws_pred { + static unsigned char test(Ch ch) { + return internal::lookup_tables<0>::lookup_text_pure_no_ws[static_cast<unsigned char>(ch)]; + } + }; + + struct text_pure_with_ws_pred { + static unsigned char test(Ch ch) { + return internal::lookup_tables<0>::lookup_text_pure_with_ws[static_cast<unsigned char>(ch)]; + } + }; + + template<Ch Quote> + struct attribute_value_pred { + static unsigned char test(Ch ch) { + if(Quote == Ch('\'')) + return internal::lookup_tables<0>::lookup_attribute_data_1[static_cast<unsigned char>(ch)]; + if(Quote == Ch('\"')) + return internal::lookup_tables<0>::lookup_attribute_data_2[static_cast<unsigned char>(ch)]; + return 0; + } + }; + + template<Ch Quote> + struct attribute_value_pure_pred { + static unsigned char test(Ch ch) { + if(Quote == Ch('\'')) + return internal::lookup_tables<0>::lookup_attribute_data_1_pure[static_cast<unsigned char>(ch)]; + if(Quote == Ch('\"')) + return internal::lookup_tables<0>::lookup_attribute_data_2_pure[static_cast<unsigned char>(ch)]; + return 0; + } + }; + + template<int Flags> + static void insert_coded_character(Ch *&text, unsigned long code) { + if(Flags & parse_no_utf8) { + + text[0] = static_cast<unsigned char>(code); + text += 1; + } else { + if(code < 0x80) { + text[0] = static_cast<unsigned char>(code); + text += 1; + } else if(code < 0x800) { + text[1] = static_cast<unsigned char>((code | 0x80) & 0xBF); + code >>= 6; + text[0] = static_cast<unsigned char>(code | 0xC0); + text += 2; + } else if(code < 0x10000) { + text[2] = static_cast<unsigned char>((code | 0x80) & 0xBF); + code >>= 6; + text[1] = static_cast<unsigned char>((code | 0x80) & 0xBF); + code >>= 6; + text[0] = static_cast<unsigned char>(code | 0xE0); + text += 3; + } else if(code < 0x110000) { + text[3] = static_cast<unsigned char>((code | 0x80) & 0xBF); + code >>= 6; + text[2] = static_cast<unsigned char>((code | 0x80) & 0xBF); + code >>= 6; + text[1] = static_cast<unsigned char>((code | 0x80) & 0xBF); + code >>= 6; + text[0] = static_cast<unsigned char>(code | 0xF0); + text += 4; + } else { + RAPIDXML_PARSE_ERROR("invalid numeric character entity", text); + } + } + } + + template<class StopPred, int Flags> + static void skip(Ch *&text) { + Ch *tmp = text; + while(StopPred::test(*tmp)) + ++tmp; + text = tmp; + } + + template<class StopPred, class StopPredPure, int Flags> + static Ch *skip_and_expand_character_refs(Ch *&text) { + if(Flags & parse_no_entity_translation && + !(Flags & parse_normalize_whitespace) && + !(Flags & parse_trim_whitespace)) { + skip<StopPred, Flags>(text); + return text; + } + + skip<StopPredPure, Flags>(text); + + Ch *src = text; + Ch *dest = src; + while(StopPred::test(*src)) { + if(!(Flags & parse_no_entity_translation)) { + if(src[0] == Ch('&')) { + switch(src[1]) { + + case Ch('a'): + if(src[2] == Ch('m') && src[3] == Ch('p') && src[4] == Ch(';')) { + *dest = Ch('&'); + ++dest; + src += 5; + continue; + } + if(src[2] == Ch('p') && src[3] == Ch('o') && src[4] == Ch('s') && src[5] == Ch(';')) { + *dest = Ch('\''); + ++dest; + src += 6; + continue; + } + break; + + case Ch('q'): + if(src[2] == Ch('u') && src[3] == Ch('o') && src[4] == Ch('t') && src[5] == Ch(';')) { + *dest = Ch('"'); + ++dest; + src += 6; + continue; + } + break; + + case Ch('g'): + if(src[2] == Ch('t') && src[3] == Ch(';')) { + *dest = Ch('>'); + ++dest; + src += 4; + continue; + } + break; + + case Ch('l'): + if(src[2] == Ch('t') && src[3] == Ch(';')) { + *dest = Ch('<'); + ++dest; + src += 4; + continue; + } + break; + + case Ch('#'): + if(src[2] == Ch('x')) { + unsigned long code = 0; + src += 3; + while(1) { + unsigned char digit = internal::lookup_tables<0>::lookup_digits[static_cast<unsigned char>(*src)]; + if(digit == 0xFF) + break; + code = code * 16+digit; + ++src; + } + insert_coded_character<Flags>(dest, code); + } else { + unsigned long code = 0; + src += 2; + while(1) { + unsigned char digit = internal::lookup_tables<0>::lookup_digits[static_cast<unsigned char>(*src)]; + if(digit == 0xFF) + break; + code = code * 10+digit; + ++src; + } + insert_coded_character<Flags>(dest, code); + } + if(*src == Ch(';')) + ++src; + else + RAPIDXML_PARSE_ERROR("expected ;", src); + continue; + + default: + break; + } + } + } + + if(Flags & parse_normalize_whitespace) { + if(whitespace_pred::test(*src)) { + *dest = Ch(' '); + ++dest; + ++src; + while(whitespace_pred::test(*src)) + ++src; + continue; + } + } + + *dest++ = *src++; + } + + text = src; + return dest; + } + + template<int Flags> + void parse_bom(Ch *&text) { + if(static_cast<unsigned char>(text[0]) == 0xEF && + static_cast<unsigned char>(text[1]) == 0xBB && + static_cast<unsigned char>(text[2]) == 0xBF) { + text += 3; + } + } + + template<int Flags> + xml_node<Ch> *parse_xml_declaration(Ch *&text) { + if(!(Flags & parse_declaration_node)) { + while(text[0] != Ch('?') || text[1] != Ch('>')) { + if(!text[0]) + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + ++text; + } + text += 2; + return 0; + } + + xml_node<Ch> *declaration = this->allocate_node(node_declaration); + + skip<whitespace_pred, Flags>(text); + + parse_node_attributes<Flags>(text, declaration); + + if(text[0] != Ch('?') || text[1] != Ch('>')) + RAPIDXML_PARSE_ERROR("expected ?>", text); + text += 2; + return declaration; + } + + template<int Flags> + xml_node<Ch> *parse_comment(Ch *&text) { + if(!(Flags & parse_comment_nodes)) { + while(text[0] != Ch('-') || text[1] != Ch('-') || text[2] != Ch('>')) { + if(!text[0]) + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + ++text; + } + text += 3; + return 0; + } + + Ch *value = text; + + while(text[0] != Ch('-') || text[1] != Ch('-') || text[2] != Ch('>')) { + if(!text[0]) + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + ++text; + } + + xml_node<Ch> *comment = this->allocate_node(node_comment); + comment->value(value, text-value); + + if(!(Flags & parse_no_string_terminators)) + *text = Ch('\0'); + text += 3; + return comment; + } + + template<int Flags> + xml_node<Ch> *parse_doctype(Ch *&text) { + Ch *value = text; + + while(*text != Ch('>')) { + switch(*text) { + + case Ch('['): { + ++text; + int depth = 1; + while(depth > 0) { + switch(*text) { + case Ch('['): + ++depth; + break; + case Ch(']'): + --depth; + break; + case 0: + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + } + ++text; + } + break; + } + + case Ch('\0'): + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + + default: + ++text; + } + } + + if(Flags & parse_doctype_node) { + xml_node<Ch> *doctype = this->allocate_node(node_doctype); + doctype->value(value, text-value); + + if(!(Flags & parse_no_string_terminators)) + *text = Ch('\0'); + text += 1; + return doctype; + } else { + text += 1; + return 0; + } + } + + template<int Flags> + xml_node<Ch> *parse_pi(Ch *&text) { + if(Flags & parse_pi_nodes) { + xml_node<Ch> *pi = this->allocate_node(node_pi); + + Ch *name = text; + skip<node_name_pred, Flags>(text); + if(text == name) + RAPIDXML_PARSE_ERROR("expected PI target", text); + pi->name(name, text-name); + + skip<whitespace_pred, Flags>(text); + + Ch *value = text; + + while(text[0] != Ch('?') || text[1] != Ch('>')) { + if(*text == Ch('\0')) + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + ++text; + } + + pi->value(value, text-value); + + if(!(Flags & parse_no_string_terminators)) { + pi->name()[pi->name_size()] = Ch('\0'); + pi->value()[pi->value_size()] = Ch('\0'); + } + text += 2; + return pi; + } else { + while(text[0] != Ch('?') || text[1] != Ch('>')) { + if(*text == Ch('\0')) + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + ++text; + } + text += 2; + return 0; + } + } + + template<int Flags> + Ch parse_and_append_data(xml_node<Ch> *node, Ch *&text, Ch *contents_start) { + if(!(Flags & parse_trim_whitespace)) + text = contents_start; + + Ch *value = text, *end; + if(Flags & parse_normalize_whitespace) + end = skip_and_expand_character_refs<text_pred, text_pure_with_ws_pred, Flags>(text); + else + end = skip_and_expand_character_refs<text_pred, text_pure_no_ws_pred, Flags>(text); + + if(Flags & parse_trim_whitespace) { + if(Flags & parse_normalize_whitespace) { + if(*(end-1) == Ch(' ')) + --end; + } else { + while(whitespace_pred::test(*(end-1))) + --end; + } + } + + if(!(Flags & parse_no_data_nodes)) { + xml_node<Ch> *data = this->allocate_node(node_data); + data->value(value, end-value); + node->append_node(data); + } + + if(!(Flags & parse_no_element_values)) + if(*node->value() == Ch('\0')) + node->value(value, end-value); + + if(!(Flags & parse_no_string_terminators)) { + Ch ch = *text; + *end = Ch('\0'); + return ch; + } + + return *text; + } + + template<int Flags> + xml_node<Ch> *parse_cdata(Ch *&text) { + if(Flags & parse_no_data_nodes) { + while(text[0] != Ch(']') || text[1] != Ch(']') || text[2] != Ch('>')) { + if(!text[0]) + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + ++text; + } + text += 3; + return 0; + } + + Ch *value = text; + while(text[0] != Ch(']') || text[1] != Ch(']') || text[2] != Ch('>')) { + if(!text[0]) + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + ++text; + } + + xml_node<Ch> *cdata = this->allocate_node(node_cdata); + cdata->value(value, text-value); + + if(!(Flags & parse_no_string_terminators)) + *text = Ch('\0'); + text += 3; + return cdata; + } + + template<int Flags> + xml_node<Ch> *parse_element(Ch *&text) { + xml_node<Ch> *element = this->allocate_node(node_element); + + Ch *name = text; + skip<node_name_pred, Flags>(text); + if(text == name) + RAPIDXML_PARSE_ERROR("expected element name", text); + element->name(name, text-name); + + skip<whitespace_pred, Flags>(text); + + parse_node_attributes<Flags>(text, element); + + if(*text == Ch('>')) { + ++text; + parse_node_contents<Flags>(text, element); + } else if(*text == Ch('/')) { + ++text; + if(*text != Ch('>')) + RAPIDXML_PARSE_ERROR("expected >", text); + ++text; + } else + RAPIDXML_PARSE_ERROR("expected >", text); + + if(!(Flags & parse_no_string_terminators)) + element->name()[element->name_size()] = Ch('\0'); + + return element; + } + + template<int Flags> + xml_node<Ch> *parse_node(Ch *&text) { + switch(text[0]) { + + default: + return parse_element<Flags>(text); + + case Ch('?'): + ++text; + if((text[0] == Ch('x') || text[0] == Ch('X')) && + (text[1] == Ch('m') || text[1] == Ch('M')) && + (text[2] == Ch('l') || text[2] == Ch('L')) && + whitespace_pred::test(text[3])) { + text += 4; + return parse_xml_declaration<Flags>(text); + } else { + return parse_pi<Flags>(text); + } + + case Ch('!'): + + switch(text[1]) { + + case Ch('-'): + if(text[2] == Ch('-')) { + text += 3; + return parse_comment<Flags>(text); + } + break; + + case Ch('['): + if(text[2] == Ch('C') && text[3] == Ch('D') && text[4] == Ch('A') && + text[5] == Ch('T') && text[6] == Ch('A') && text[7] == Ch('[')) { + text += 8; + return parse_cdata<Flags>(text); + } + break; + + case Ch('D'): + if(text[2] == Ch('O') && text[3] == Ch('C') && text[4] == Ch('T') && + text[5] == Ch('Y') && text[6] == Ch('P') && text[7] == Ch('E') && + whitespace_pred::test(text[8])) { + text += 9; + return parse_doctype<Flags>(text); + } + } + + ++text; + while(*text != Ch('>')) { + if(*text == 0) + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + ++text; + } + ++text; + return 0; + } + } + + template<int Flags> + void parse_node_contents(Ch *&text, xml_node<Ch> *node) { + while(1) { + Ch *contents_start = text; + skip<whitespace_pred, Flags>(text); + Ch next_char = *text; + + after_data_node: + + switch(next_char) { + + case Ch('<'): + if(text[1] == Ch('/')) { + text += 2; + if(Flags & parse_validate_closing_tags) { + Ch *closing_name = text; + skip<node_name_pred, Flags>(text); + if(!internal::compare(node->name(), node->name_size(), closing_name, text-closing_name, true)) + RAPIDXML_PARSE_ERROR("invalid closing tag name", text); + } else { + skip<node_name_pred, Flags>(text); + } + skip<whitespace_pred, Flags>(text); + if(*text != Ch('>')) + RAPIDXML_PARSE_ERROR("expected >", text); + ++text; + return; + } else { + ++text; + if(xml_node<Ch> *child = parse_node<Flags>(text)) + node->append_node(child); + } + break; + + case Ch('\0'): + RAPIDXML_PARSE_ERROR("unexpected end of data", text); + + default: + next_char = parse_and_append_data<Flags>(node, text, contents_start); + goto after_data_node; + } + } + } + + template<int Flags> + void parse_node_attributes(Ch *&text, xml_node<Ch> *node) { + while(attribute_name_pred::test(*text)) { + Ch *name = text; + ++text; + skip<attribute_name_pred, Flags>(text); + if(text == name) + RAPIDXML_PARSE_ERROR("expected attribute name", name); + + xml_attribute<Ch> *attribute = this->allocate_attribute(); + attribute->name(name, text-name); + node->append_attribute(attribute); + + skip<whitespace_pred, Flags>(text); + + if(*text != Ch('=')) + RAPIDXML_PARSE_ERROR("expected =", text); + ++text; + + if(!(Flags & parse_no_string_terminators)) + attribute->name()[attribute->name_size()] = 0; + + skip<whitespace_pred, Flags>(text); + + Ch quote = *text; + if(quote != Ch('\'') && quote != Ch('"')) + RAPIDXML_PARSE_ERROR("expected ' or \"", text); + ++text; + + Ch *value = text, *end; + const int AttFlags = Flags & ~parse_normalize_whitespace; + if(quote == Ch('\'')) + end = skip_and_expand_character_refs<attribute_value_pred<Ch('\'')>, attribute_value_pure_pred<Ch( + '\'')>, AttFlags>(text); + else + end = skip_and_expand_character_refs<attribute_value_pred<Ch('"')>, attribute_value_pure_pred<Ch( + '"')>, AttFlags>(text); + + attribute->value(value, end-value); + + if(*text != quote) + RAPIDXML_PARSE_ERROR("expected ' or \"", text); + ++text; + + if(!(Flags & parse_no_string_terminators)) + attribute->value()[attribute->value_size()] = 0; + + skip<whitespace_pred, Flags>(text); + } + } + }; + + namespace internal { + + template<int Dummy> + const unsigned char lookup_tables<Dummy>::lookup_whitespace[256] = + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + template<int Dummy> + const unsigned char lookup_tables<Dummy>::lookup_node_name[256] = + { + 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + + template<int Dummy> + const unsigned char lookup_tables<Dummy>::lookup_text[256] = + { + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + + template<int Dummy> + const unsigned char lookup_tables<Dummy>::lookup_text_pure_no_ws[256] = + { + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + + template<int Dummy> + const unsigned char lookup_tables<Dummy>::lookup_text_pure_with_ws[256] = + { + 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + + template<int Dummy> + const unsigned char lookup_tables<Dummy>::lookup_attribute_name[256] = + { + 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + + template<int Dummy> + const unsigned char lookup_tables<Dummy>::lookup_attribute_data_1[256] = + { + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + + template<int Dummy> + const unsigned char lookup_tables<Dummy>::lookup_attribute_data_1_pure[256] = + { + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + + template<int Dummy> + const unsigned char lookup_tables<Dummy>::lookup_attribute_data_2[256] = + { + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + + template<int Dummy> + const unsigned char lookup_tables<Dummy>::lookup_attribute_data_2_pure[256] = + { + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + + template<int Dummy> + const unsigned char lookup_tables<Dummy>::lookup_digits[256] = + { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 + }; + + template<int Dummy> + const unsigned char lookup_tables<Dummy>::lookup_upcase[256] = + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 123, 124, 125, 126, 127, + 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, + 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, + 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, + 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, + 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, + 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 + }; + } + +} + +#undef RAPIDXML_PARSE_ERROR + +#ifdef _MSC_VER +#pragma warning(pop) +#endif From fb87b8e0d69b09cbf7cdf41f2934e3f54918d0bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=88=E9=A1=BA=20=E5=88=98?= <yshliu0321@icloud.com> Date: Thu, 24 May 2018 14:28:08 +0800 Subject: [PATCH 10/12] Remove Trailing Whitespace --- src/Doxyfile.in | 136 +++++++------- src/compile_commands.cc | 26 +-- src/ctags.cc | 28 +-- src/debug_lldb.cc | 54 +++--- src/directories.cc | 146 +++++++-------- src/directories.h | 26 +-- src/documentation_cppreference.cc | 4 +- src/entrybox.h | 6 +- src/filesystem.cc | 16 +- src/git.cc | 18 +- src/git.h | 30 +-- src/menu.h | 6 +- src/meson.cc | 8 +- src/notebook.cc | 70 +++---- src/notebook.h | 22 +-- src/project.cc | 176 +++++++++--------- src/project.h | 58 +++--- src/project_build.h | 6 +- src/rapidxml/rapidxml.h | 2 +- src/selection_dialog.cc | 48 ++--- src/selection_dialog.h | 20 +- src/source.cc | 300 +++++++++++++++--------------- src/source.h | 58 +++--- src/source_base.cc | 50 ++--- src/source_base.h | 30 +-- src/source_clang.cc | 272 +++++++++++++-------------- src/source_clang.h | 30 +-- src/source_diff.cc | 30 +-- src/source_diff.h | 22 +-- src/source_language_protocol.cc | 190 +++++++++---------- src/source_language_protocol.h | 18 +- src/source_spellcheck.cc | 62 +++--- src/source_spellcheck.h | 14 +- src/terminal.cc | 48 ++--- src/terminal.h | 10 +- src/tooltips.cc | 32 ++-- src/tooltips.h | 18 +- src/usages_clang.cc | 4 +- src/window.cc | 242 ++++++++++++------------ 39 files changed, 1168 insertions(+), 1168 deletions(-) diff --git a/src/Doxyfile.in b/src/Doxyfile.in index 500232b6..056b1357 100644 --- a/src/Doxyfile.in +++ b/src/Doxyfile.in @@ -38,27 +38,27 @@ PROJECT_NAME = "juCi++" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = +PROJECT_NUMBER = # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = +PROJECT_BRIEF = # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. -PROJECT_LOGO = +PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. -OUTPUT_DIRECTORY = +OUTPUT_DIRECTORY = # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and @@ -118,7 +118,7 @@ REPEAT_BRIEF = YES # the entity):The $name class, The $name widget, The $name file, is, provides, # specifies, contains, represents, a, an and the. -ABBREVIATE_BRIEF = +ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # doxygen will generate a detailed section even if there is only a brief @@ -152,7 +152,7 @@ FULL_PATH_NAMES = YES # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. -STRIP_FROM_PATH = +STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which @@ -161,7 +161,7 @@ STRIP_FROM_PATH = # specify the list of include paths that are normally passed to the compiler # using the -I flag. -STRIP_FROM_INC_PATH = +STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't @@ -228,13 +228,13 @@ TAB_SIZE = 4 # "Side Effects:". You can put \n's in the value part of an alias to insert # newlines. -ALIASES = +ALIASES = # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding "class=itcl::class" # will allow you to use the command class in the itcl::class meaning. -TCL_SUBST = +TCL_SUBST = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For @@ -281,7 +281,7 @@ OPTIMIZE_OUTPUT_VHDL = NO # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by doxygen. -EXTENSION_MAPPING = +EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable @@ -629,7 +629,7 @@ GENERATE_DEPRECATEDLIST= YES # sections, marked by \if <section_label> ... \endif and \cond <section_label> # ... \endcond blocks. -ENABLED_SECTIONS = +ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the @@ -671,7 +671,7 @@ SHOW_NAMESPACES = YES # by doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. -FILE_VERSION_FILTER = +FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated @@ -684,7 +684,7 @@ FILE_VERSION_FILTER = # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. -LAYOUT_FILE = +LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib @@ -694,7 +694,7 @@ LAYOUT_FILE = # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. -CITE_BIB_FILES = +CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages @@ -753,7 +753,7 @@ WARN_FORMAT = "$file:$line: $text" # messages should be written. If left blank the output is written to standard # error (stderr). -WARN_LOGFILE = +WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files @@ -790,7 +790,7 @@ INPUT_ENCODING = UTF-8 # *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, # *.vhdl, *.ucf, *.qsf, *.as and *.js. -FILE_PATTERNS = +FILE_PATTERNS = # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. @@ -805,7 +805,7 @@ RECURSIVE = YES # Note that relative paths are relative to the directory from which doxygen is # run. -EXCLUDE = +EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -821,7 +821,7 @@ EXCLUDE_SYMLINKS = NO # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = +EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the @@ -832,20 +832,20 @@ EXCLUDE_PATTERNS = # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* -EXCLUDE_SYMBOLS = +EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). -EXAMPLE_PATH = +EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. -EXAMPLE_PATTERNS = +EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands @@ -858,7 +858,7 @@ EXAMPLE_RECURSIVE = NO # that contain images that are to be included in the documentation (see the # \image command). -IMAGE_PATH = +IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program @@ -875,7 +875,7 @@ IMAGE_PATH = # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. -INPUT_FILTER = +INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the @@ -884,7 +884,7 @@ INPUT_FILTER = # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. -FILTER_PATTERNS = +FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will also be used to filter the input files that are used for @@ -899,14 +899,14 @@ FILTER_SOURCE_FILES = NO # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. -FILTER_SOURCE_PATTERNS = +FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. -USE_MDFILE_AS_MAINPAGE = +USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # Configuration options related to source browsing @@ -1018,7 +1018,7 @@ COLS_IN_ALPHA_INDEX = 5 # while generating the index headers. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. -IGNORE_PREFIX = +IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output @@ -1062,7 +1062,7 @@ HTML_FILE_EXTENSION = .html # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_HEADER = +HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard @@ -1072,7 +1072,7 @@ HTML_HEADER = # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_FOOTER = +HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of @@ -1084,7 +1084,7 @@ HTML_FOOTER = # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_STYLESHEET = +HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets @@ -1097,7 +1097,7 @@ HTML_STYLESHEET = # list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_STYLESHEET = +HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note @@ -1107,7 +1107,7 @@ HTML_EXTRA_STYLESHEET = # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_FILES = +HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to @@ -1236,7 +1236,7 @@ GENERATE_HTMLHELP = NO # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. -CHM_FILE = +CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler (hhc.exe). If non-empty, @@ -1244,7 +1244,7 @@ CHM_FILE = # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. -HHC_LOCATION = +HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated # (YES) or that it should be included in the master .chm file (NO). @@ -1257,7 +1257,7 @@ GENERATE_CHI = NO # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. -CHM_INDEX_ENCODING = +CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated # (YES) or a normal table of contents (NO) in the .chm file. Furthermore it @@ -1288,7 +1288,7 @@ GENERATE_QHP = NO # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. -QCH_FILE = +QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace @@ -1313,7 +1313,7 @@ QHP_VIRTUAL_FOLDER = doc # filters). # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom @@ -1321,21 +1321,21 @@ QHP_CUST_FILTER_NAME = # filters). # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_CUST_FILTER_ATTRS = +QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_SECT_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location of Qt's # qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the # generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. -QHG_LOCATION = +QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To @@ -1468,7 +1468,7 @@ MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # This tag requires that the tag USE_MATHJAX is set to YES. -MATHJAX_EXTENSIONS = +MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site @@ -1476,7 +1476,7 @@ MATHJAX_EXTENSIONS = # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. -MATHJAX_CODEFILE = +MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and @@ -1536,7 +1536,7 @@ EXTERNAL_SEARCH = NO # Searching" for details. # This tag requires that the tag SEARCHENGINE is set to YES. -SEARCHENGINE_URL = +SEARCHENGINE_URL = # When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed # search data is written to a file for indexing by an external tool. With the @@ -1552,7 +1552,7 @@ SEARCHDATA_FILE = searchdata.xml # projects and redirect the results back to the right project. # This tag requires that the tag SEARCHENGINE is set to YES. -EXTERNAL_SEARCH_ID = +EXTERNAL_SEARCH_ID = # The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen # projects other than the one defined by this configuration file, but that are @@ -1562,7 +1562,7 @@ EXTERNAL_SEARCH_ID = # EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... # This tag requires that the tag SEARCHENGINE is set to YES. -EXTRA_SEARCH_MAPPINGS = +EXTRA_SEARCH_MAPPINGS = #--------------------------------------------------------------------------- # Configuration options related to the LaTeX output @@ -1626,7 +1626,7 @@ PAPER_TYPE = a4 # If left blank no extra packages will be included. # This tag requires that the tag GENERATE_LATEX is set to YES. -EXTRA_PACKAGES = +EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for the # generated LaTeX document. The header should contain everything until the first @@ -1642,7 +1642,7 @@ EXTRA_PACKAGES = # to HTML_HEADER. # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_HEADER = +LATEX_HEADER = # The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the # generated LaTeX document. The footer should contain everything after the last @@ -1653,7 +1653,7 @@ LATEX_HEADER = # Note: Only use a user-defined footer if you know what you are doing! # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_FOOTER = +LATEX_FOOTER = # The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined # LaTeX style sheets that are included after the standard style sheets created @@ -1664,7 +1664,7 @@ LATEX_FOOTER = # list). # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_EXTRA_STYLESHEET = +LATEX_EXTRA_STYLESHEET = # The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the LATEX_OUTPUT output @@ -1672,7 +1672,7 @@ LATEX_EXTRA_STYLESHEET = # markers available. # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_EXTRA_FILES = +LATEX_EXTRA_FILES = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is # prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will @@ -1772,14 +1772,14 @@ RTF_HYPERLINKS = NO # default style sheet that doxygen normally uses. # This tag requires that the tag GENERATE_RTF is set to YES. -RTF_STYLESHEET_FILE = +RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an RTF document. Syntax is # similar to doxygen's config file. A template extensions file can be generated # using doxygen -e rtf extensionFile. # This tag requires that the tag GENERATE_RTF is set to YES. -RTF_EXTENSIONS_FILE = +RTF_EXTENSIONS_FILE = # If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code # with syntax highlighting in the RTF output. @@ -1824,7 +1824,7 @@ MAN_EXTENSION = .3 # MAN_EXTENSION with the initial . removed. # This tag requires that the tag GENERATE_MAN is set to YES. -MAN_SUBDIR = +MAN_SUBDIR = # If the MAN_LINKS tag is set to YES and doxygen generates man output, then it # will generate one additional man file for each entity documented in the real @@ -1937,7 +1937,7 @@ PERLMOD_PRETTY = YES # overwrite each other's variables. # This tag requires that the tag GENERATE_PERLMOD is set to YES. -PERLMOD_MAKEVAR_PREFIX = +PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor @@ -1978,7 +1978,7 @@ SEARCH_INCLUDES = YES # preprocessor. # This tag requires that the tag SEARCH_INCLUDES is set to YES. -INCLUDE_PATH = +INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the @@ -1986,7 +1986,7 @@ INCLUDE_PATH = # used. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -INCLUDE_FILE_PATTERNS = +INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that are # defined before the preprocessor is started (similar to the -D option of e.g. @@ -1996,7 +1996,7 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = +PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The @@ -2005,7 +2005,7 @@ PREDEFINED = # definition found in the source code. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -EXPAND_AS_DEFINED = +EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will # remove all references to function-like macros that are alone on a line, have @@ -2034,13 +2034,13 @@ SKIP_FUNCTION_MACROS = YES # the path). If a tag file is not located in the directory in which doxygen is # run, you must also specify the path to the tagfile here. -TAGFILES = +TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create a # tag file that is based on the input files it reads. See section "Linking to # external documentation" for more information about the usage of tag files. -GENERATE_TAGFILE = +GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES, all external class will be listed in # the class index. If set to NO, only the inherited external classes will be @@ -2089,14 +2089,14 @@ CLASS_DIAGRAMS = YES # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. -MSCGEN_PATH = +MSCGEN_PATH = # You can include diagrams made with dia in doxygen documentation. Doxygen will # then run dia to produce the diagram and insert it in the documentation. The # DIA_PATH tag allows you to specify the directory where the dia binary resides. # If left empty dia is assumed to be found in the default search path. -DIA_PATH = +DIA_PATH = # If set to YES the inheritance and collaboration graphs will hide inheritance # and usage relations if the target is undocumented or is not a class. @@ -2145,7 +2145,7 @@ DOT_FONTSIZE = 10 # the path where dot can find it using this tag. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTPATH = +DOT_FONTPATH = # If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for # each documented class showing the direct and indirect inheritance relations. @@ -2289,26 +2289,26 @@ INTERACTIVE_SVG = NO # found. If left blank, it is assumed the dot tool can be found in the path. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_PATH = +DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the \dotfile # command). # This tag requires that the tag HAVE_DOT is set to YES. -DOTFILE_DIRS = +DOTFILE_DIRS = # The MSCFILE_DIRS tag can be used to specify one or more directories that # contain msc files that are included in the documentation (see the \mscfile # command). -MSCFILE_DIRS = +MSCFILE_DIRS = # The DIAFILE_DIRS tag can be used to specify one or more directories that # contain dia files that are included in the documentation (see the \diafile # command). -DIAFILE_DIRS = +DIAFILE_DIRS = # When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the # path where java can find the plantuml.jar file. If left blank, it is assumed @@ -2321,7 +2321,7 @@ PLANTUML_JAR_PATH = @PLANTUML_JARFILE@ # When using plantuml, the specified paths are searched for files specified by # the !include statement in a plantuml block. -PLANTUML_INCLUDE_PATH = +PLANTUML_INCLUDE_PATH = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes # that will be shown in the graph. If the number of nodes in a graph becomes diff --git a/src/compile_commands.cc b/src/compile_commands.cc index 27f946bd..07c3d221 100644 --- a/src/compile_commands.cc +++ b/src/compile_commands.cc @@ -5,7 +5,7 @@ std::vector<std::string> CompileCommands::Command::parameter_values(const std::string ¶meter_name) const { std::vector<std::string> parameter_values; - + bool found_argument=false; for(auto ¶meter: parameters) { if(found_argument) { @@ -15,7 +15,7 @@ std::vector<std::string> CompileCommands::Command::parameter_values(const std::s else if(parameter==parameter_name) found_argument=true; } - + return parameter_values; } @@ -23,13 +23,13 @@ CompileCommands::CompileCommands(const boost::filesystem::path &build_path) { try { boost::property_tree::ptree root_pt; boost::property_tree::json_parser::read_json((build_path/"compile_commands.json").string(), root_pt); - + auto commands_pt=root_pt.get_child(""); for(auto &command: commands_pt) { boost::filesystem::path directory=command.second.get<std::string>("directory"); auto parameters_str=command.second.get<std::string>("command"); boost::filesystem::path file=command.second.get<std::string>("file"); - + std::vector<std::string> parameters; bool backslash=false; bool single_quote=false; @@ -66,14 +66,14 @@ CompileCommands::CompileCommands(const boost::filesystem::path &build_path) { double_quote=!double_quote; continue; } - + if(parameter_start_pos==std::string::npos) parameter_start_pos=c; ++parameter_size; } if(parameter_start_pos!=std::string::npos) add_parameter(); - + commands.emplace_back(Command{directory, parameters, boost::filesystem::absolute(file, build_path)}); } } @@ -82,7 +82,7 @@ CompileCommands::CompileCommands(const boost::filesystem::path &build_path) { std::vector<std::string> CompileCommands::get_arguments(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) { std::string default_std_argument="-std=c++1y"; - + std::vector<std::string> arguments; if(!build_path.empty()) { clangmm::CompilationDatabase db(build_path.string()); @@ -110,7 +110,7 @@ std::vector<std::string> CompileCommands::get_arguments(const boost::filesystem: } else arguments.emplace_back(default_std_argument); - + auto clang_version_string=clangmm::to_string(clang_getClangVersion()); const static std::regex clang_version_regex(R"(^[A-Za-z ]+([0-9.]+).*$)"); std::smatch sm; @@ -130,23 +130,23 @@ std::vector<std::string> CompileCommands::get_arguments(const boost::filesystem: #endif } arguments.emplace_back("-fretain-comments-from-system-headers"); - + auto extension=file_path.extension().string(); if(extension==".h" || //TODO: temporary fix for .h-files (parse as c++) extension!=".c") arguments.emplace_back("-xc++"); - + if(extension.empty() || (1<extension.size() && extension[1]=='h') || extension==".tcc" || extension==".cuh") { arguments.emplace_back("-Wno-pragma-once-outside-header"); arguments.emplace_back("-Wno-pragma-system-header-outside-header"); arguments.emplace_back("-Wno-include-next-outside-header"); } - + if(extension==".cu" || extension==".cuh") { arguments.emplace_back("-include"); arguments.emplace_back("cuda_runtime.h"); } - + if(extension==".cl") { arguments.emplace_back("-xcl"); arguments.emplace_back("-cl-std=CL2.0"); @@ -154,7 +154,7 @@ std::vector<std::string> CompileCommands::get_arguments(const boost::filesystem: arguments.emplace_back("-finclude-default-header"); arguments.emplace_back("-Wno-gcc-compat"); } - + if(!build_path.empty()) { arguments.emplace_back("-working-directory"); arguments.emplace_back(build_path.string()); diff --git a/src/ctags.cc b/src/ctags.cc index 1f34f6dc..72bcda59 100644 --- a/src/ctags.cc +++ b/src/ctags.cc @@ -16,7 +16,7 @@ std::pair<boost::filesystem::path, std::unique_ptr<std::stringstream> > Ctags::g auto relative_default_path=filesystem::get_relative_path(build->get_default_path(), run_path); if(!relative_default_path.empty()) exclude+=" --exclude="+relative_default_path.string(); - + auto relative_debug_path=filesystem::get_relative_path(build->get_debug_path(), run_path); if(!relative_debug_path.empty()) exclude+=" --exclude="+relative_debug_path.string(); @@ -28,7 +28,7 @@ std::pair<boost::filesystem::path, std::unique_ptr<std::stringstream> > Ctags::g else run_path=path.parent_path(); } - + std::stringstream stdin_stream; //TODO: when debian stable gets newer g++ version that supports move on streams, remove unique_ptr below auto stdout_stream=std::make_unique<std::stringstream>(); @@ -58,7 +58,7 @@ Ctags::Location Ctags::get_location(const std::string &line, bool markup) { if(!((chr>='a' && chr<='z') || (chr>='A' && chr<='Z') || (chr>='0' && chr<='9') || chr=='_')) location.symbol.erase(8, 1); } - + location.file_path=sm[2].str(); location.source=sm[4].str(); try { @@ -70,11 +70,11 @@ Ctags::Location Ctags::get_location(const std::string &line, bool markup) { location.scope=sm[7].str(); if(!sm[5].str().empty()) { location.index=sm[3].str().size(); - + size_t pos=location.source.find(location.symbol); if(pos!=std::string::npos) location.index+=pos; - + if(markup) { location.source=Glib::Markup::escape_text(location.source); auto symbol=Glib::Markup::escape_text(location.symbol); @@ -95,7 +95,7 @@ Ctags::Location Ctags::get_location(const std::string &line, bool markup) { } else std::cerr << "Warning (ctags): please report to the juCi++ project that the following line was not parsed:\n" << line << std::endl; - + return location; } @@ -127,7 +127,7 @@ std::vector<Ctags::Location> Ctags::get_locations(const boost::filesystem::path if(result.second->tellg()==0) return std::vector<Location>(); result.second->seekg(0, std::ios::beg); - + //insert name into type size_t c=0; size_t bracket_count=0; @@ -141,9 +141,9 @@ std::vector<Ctags::Location> Ctags::get_locations(const boost::filesystem::path } auto full_type=type; full_type.insert(c, name); - + auto parts=get_type_parts(full_type); - + std::string line; long best_score=LONG_MIN; std::vector<Location> best_locations; @@ -157,11 +157,11 @@ std::vector<Ctags::Location> Ctags::get_locations(const boost::filesystem::path } else if(location.symbol!=name) continue; - + location.file_path=result.first/location.file_path; - + auto source_parts=get_type_parts(location.source); - + //Find match score long score=0; size_t source_index=0; @@ -192,7 +192,7 @@ std::vector<Ctags::Location> Ctags::get_locations(const boost::filesystem::path if(!found) --score; } - + if(score>best_score) { best_score=score; best_locations.clear(); @@ -201,6 +201,6 @@ std::vector<Ctags::Location> Ctags::get_locations(const boost::filesystem::path else if(score==best_score) best_locations.emplace_back(location); } - + return best_locations; } diff --git a/src/debug_lldb.cc b/src/debug_lldb.cc index 71b1d3c3..1058aa5b 100644 --- a/src/debug_lldb.cc +++ b/src/debug_lldb.cc @@ -34,12 +34,12 @@ std::tuple<std::vector<std::string>, std::string, std::vector<std::string> > Deb std::vector<std::string> environment; std::string executable; std::vector<std::string> arguments; - + size_t start_pos=std::string::npos; bool quote=false; bool double_quote=false; size_t backslash_count=0; - for(size_t c=0;c<=command.size();c++) { + for(size_t c=0;c<=command.size();c++) { if(c==command.size() || (!quote && !double_quote && backslash_count%2==0 && command[c]==' ')) { if(c>0 && start_pos!=std::string::npos) { auto argument=command.substr(start_pos, c-start_pos); @@ -58,7 +58,7 @@ std::tuple<std::vector<std::string>, std::string, std::vector<std::string> > Deb else break; } - + if(!env_arg) { executable=filesystem::unescape_argument(argument); #ifdef _WIN32 @@ -83,7 +83,7 @@ std::tuple<std::vector<std::string>, std::string, std::vector<std::string> > Deb if(c<command.size() && start_pos==std::string::npos && command[c]!=' ') start_pos=c; } - + return std::make_tuple(environment, executable, arguments); } @@ -95,19 +95,19 @@ void Debug::LLDB::start(const std::string &command, const boost::filesystem::pat debugger=std::make_unique<lldb::SBDebugger>(lldb::SBDebugger::Create(true, log, nullptr)); listener=std::make_unique<lldb::SBListener>("juCi++ lldb listener"); } - + //Create executable string and argument array auto parsed_run_arguments=parse_run_arguments(command); auto &environment_from_arguments=std::get<0>(parsed_run_arguments); auto &executable=std::get<1>(parsed_run_arguments); auto &arguments=std::get<2>(parsed_run_arguments); - + std::vector<const char*> argv; argv.reserve(arguments.size()); for(auto &argument : arguments) argv.emplace_back(argument.c_str()); argv.emplace_back(nullptr); - + auto target=debugger->CreateTarget(executable.c_str()); if(!target.IsValid()) { Terminal::get().async_print("Error (debug): Could not create debug target to: "+executable+'\n', true); @@ -115,7 +115,7 @@ void Debug::LLDB::start(const std::string &command, const boost::filesystem::pat handler(-1); return; } - + //Set breakpoints for(auto &breakpoint: breakpoints) { if(!(target.BreakpointCreateByLocation(breakpoint.first.string().c_str(), breakpoint.second)).IsValid()) { @@ -125,7 +125,7 @@ void Debug::LLDB::start(const std::string &command, const boost::filesystem::pat return; } } - + lldb::SBError error; if(!remote_host.empty()) { auto connect_string="connect://"+remote_host; @@ -147,14 +147,14 @@ void Debug::LLDB::start(const std::string &command, const boost::filesystem::pat } } } - + // Create environment array std::vector<const char*> environment; environment.reserve(environment_from_arguments.size()); for(auto &e: environment_from_arguments) environment.emplace_back(e.c_str()); environment.emplace_back(nullptr); - + process->RemoteLaunch(argv.data(), environment.data(), nullptr, nullptr, nullptr, nullptr, lldb::eLaunchFlagNone, false, error); if(!error.Fail()) process->Continue(); @@ -171,7 +171,7 @@ void Debug::LLDB::start(const std::string &command, const boost::filesystem::pat for(size_t c=0;c<environ_size;++c) environment.emplace_back(environ[c]); environment.emplace_back(nullptr); - + process = std::make_unique<lldb::SBProcess>(target.Launch(*listener, argv.data(), environment.data(), nullptr, nullptr, nullptr, path.string().c_str(), lldb::eLaunchFlagNone, false, error)); } if(error.Fail()) { @@ -184,12 +184,12 @@ void Debug::LLDB::start(const std::string &command, const boost::filesystem::pat debug_thread.join(); for(auto &handler: on_start) handler(*process); - + for(auto &command: startup_commands) { lldb::SBCommandReturnObject command_return_object; debugger->GetCommandInterpreter().HandleCommand(command.c_str(), command_return_object, false); } - + debug_thread=std::thread([this]() { lldb::SBEvent event; while(true) { @@ -198,7 +198,7 @@ void Debug::LLDB::start(const std::string &command, const boost::filesystem::pat if((event.GetType() & lldb::SBProcess::eBroadcastBitStateChanged)>0) { auto state=process->GetStateFromEvent(event); this->state=state; - + if(state==lldb::StateType::eStateStopped) { for(uint32_t c=0;c<process->GetNumThreads();c++) { auto thread=process->GetThreadAtIndex(c); @@ -208,12 +208,12 @@ void Debug::LLDB::start(const std::string &command, const boost::filesystem::pat } } } - + lock.unlock(); for(auto &handler: on_event) handler(event); lock.lock(); - + if(state==lldb::StateType::eStateExited || state==lldb::StateType::eStateCrashed) { auto exit_status=state==lldb::StateType::eStateCrashed?-1:process->GetExitStatus(); lock.unlock(); @@ -314,17 +314,17 @@ std::vector<Debug::LLDB::Frame> Debug::LLDB::get_backtrace() { for(uint32_t c_f=0;c_f<thread.GetNumFrames();c_f++) { Frame backtrace_frame; auto frame=thread.GetFrameAtIndex(c_f); - + backtrace_frame.index=c_f; - + if(frame.GetFunctionName()!=nullptr) backtrace_frame.function_name=frame.GetFunctionName(); - + auto module_filename=frame.GetModule().GetFileSpec().GetFilename(); if(module_filename!=nullptr) { backtrace_frame.module_filename=module_filename; } - + auto line_entry=frame.GetLineEntry(); if(line_entry.IsValid()) { lldb::SBStream stream; @@ -354,7 +354,7 @@ std::vector<Debug::LLDB::Variable> Debug::LLDB::get_variables() { for(uint32_t value_index=0;value_index<values.GetSize();value_index++) { lldb::SBStream stream; auto value=values.GetValueAtIndex(value_index); - + Debug::LLDB::Variable variable; variable.thread_index_id=thread.GetIndexID(); variable.frame_index=c_f; @@ -362,7 +362,7 @@ std::vector<Debug::LLDB::Variable> Debug::LLDB::get_variables() { variable.name=value.GetName(); value.GetDescription(stream); variable.value=stream.GetData(); - + auto declaration=value.GetDeclaration(); if(declaration.IsValid()) { variable.declaration_found=true; @@ -370,7 +370,7 @@ std::vector<Debug::LLDB::Variable> Debug::LLDB::get_variables() { variable.line_index=declaration.GetColumn(); if(variable.line_index==0) variable.line_index=1; - + auto file_spec=declaration.GetFileSpec(); variable.file_path=filesystem::get_normal_path(file_spec.GetDirectory()); variable.file_path/=file_spec.GetFilename(); @@ -383,7 +383,7 @@ std::vector<Debug::LLDB::Variable> Debug::LLDB::get_variables() { variable.line_index=line_entry.GetColumn(); if(variable.line_index==0) variable.line_index=1; - + auto file_spec=line_entry.GetFileSpec(); variable.file_path=filesystem::get_normal_path(file_spec.GetDirectory()); variable.file_path/=file_spec.GetFilename(); @@ -417,14 +417,14 @@ std::string Debug::LLDB::get_value(const std::string &variable, const boost::fil std::unique_lock<std::mutex> lock(mutex); if(state==lldb::StateType::eStateStopped) { auto frame=process->GetSelectedThread().GetSelectedFrame(); - + auto values=frame.GetVariables(true, true, true, false); //First try to find variable based on name, file and line number if(!file_path.empty()) { for(uint32_t value_index=0;value_index<values.GetSize();value_index++) { lldb::SBStream stream; auto value=values.GetValueAtIndex(value_index); - + if(value.GetName()!=nullptr && value.GetName()==variable) { auto declaration=value.GetDeclaration(); if(declaration.IsValid()) { diff --git a/src/directories.cc b/src/directories.cc index f8449493..3c14337a 100644 --- a/src/directories.cc +++ b/src/directories.cc @@ -12,7 +12,7 @@ bool Directories::TreeStore::row_drop_possible_vfunc(const Gtk::TreeModel::Path bool Directories::TreeStore::drag_data_received_vfunc(const TreeModel::Path &path, const Gtk::SelectionData &selection_data) { auto &directories=Directories::get(); - + auto get_target_folder=[this, &directories](const TreeModel::Path &path) { if(path.size()==1) return directories.path; @@ -40,36 +40,36 @@ bool Directories::TreeStore::drag_data_received_vfunc(const TreeModel::Path &pat } return boost::filesystem::path(); }; - + auto it=directories.get_selection()->get_selected(); if(it) { auto source_path=it->get_value(directories.column_record.path); if(source_path.empty()) return false; - + auto target_path=get_target_folder(path); target_path/=source_path.filename(); - + if(source_path==target_path) return false; - + if(boost::filesystem::exists(target_path)) { Terminal::get().print("Error: could not move file: "+target_path.string()+" already exists\n", true); return false; } - + bool is_directory=boost::filesystem::is_directory(source_path); - + if(is_directory) Directories::get().remove_path(source_path); - + boost::system::error_code ec; boost::filesystem::rename(source_path, target_path, ec); if(ec) { Terminal::get().print("Error: could not move file: "+ec.message()+'\n', true); return false; } - + for(size_t c=0;c<Notebook::get().size();c++) { auto view=Notebook::get().get_view(c); if(is_directory) { @@ -88,12 +88,12 @@ bool Directories::TreeStore::drag_data_received_vfunc(const TreeModel::Path &pat break; } } - + Directories::get().update(); Directories::get().on_save_file(target_path); directories.select(target_path); } - + EntryBox::get().hide(); return false; } @@ -104,25 +104,25 @@ bool Directories::TreeStore::drag_data_delete_vfunc (const Gtk::TreeModel::Path Directories::Directories() : Gtk::ListViewText(1) { set_enable_tree_lines(true); - + tree_store = TreeStore::create(); tree_store->set_column_types(column_record); set_model(tree_store); - + get_column(0)->set_title(""); - + auto renderer=dynamic_cast<Gtk::CellRendererText*>(get_column(0)->get_first_cell()); get_column(0)->set_cell_data_func(*renderer, [this] (Gtk::CellRenderer *renderer, const Gtk::TreeModel::iterator &iter) { if(auto renderer_text=dynamic_cast<Gtk::CellRendererText*>(renderer)) renderer_text->property_markup()=iter->get_value(column_record.markup); }); - + get_style_context()->add_class("juci_directories"); - + tree_store->set_sort_column(column_record.id, Gtk::SortType::SORT_ASCENDING); set_enable_search(true); //TODO: why does this not work in OS X? set_search_column(column_record.name); - + signal_row_activated().connect([this](const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *column){ auto iter = tree_store->get_iter(path); if (iter) { @@ -135,7 +135,7 @@ Directories::Directories() : Gtk::ListViewText(1) { } } }); - + signal_test_expand_row().connect([this](const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path){ if(iter->children().begin()->get_value(column_record.path)=="") add_or_update_path(iter->get_value(column_record.path), *iter, true); @@ -144,10 +144,10 @@ Directories::Directories() : Gtk::ListViewText(1) { signal_row_collapsed().connect([this](const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path){ this->remove_path(iter->get_value(column_record.path)); }); - + enable_model_drag_source(); enable_model_drag_dest(); - + auto new_file_label = "New File"; auto new_file_function = [this] { if(menu_popup_row_path.empty()) @@ -171,7 +171,7 @@ Directories::Directories() : Gtk::ListViewText(1) { Terminal::get().print("Error: could not create "+target_path.string()+": already exists\n", true); return; } - + EntryBox::get().hide(); }); auto entry_it=EntryBox::get().entries.begin(); @@ -181,15 +181,15 @@ Directories::Directories() : Gtk::ListViewText(1) { }); EntryBox::get().show(); }; - + menu_item_new_file.set_label(new_file_label); menu_item_new_file.signal_activate().connect(new_file_function); menu.append(menu_item_new_file); - + menu_root_item_new_file.set_label(new_file_label); menu_root_item_new_file.signal_activate().connect(new_file_function); menu_root.append(menu_root_item_new_file); - + auto new_folder_label = "New Folder"; auto new_folder_function = [this] { if(menu_popup_row_path.empty()) @@ -214,7 +214,7 @@ Directories::Directories() : Gtk::ListViewText(1) { Terminal::get().print("Error: could not create "+target_path.string()+": already exists\n", true); return; } - + EntryBox::get().hide(); }); auto entry_it=EntryBox::get().entries.begin(); @@ -224,17 +224,17 @@ Directories::Directories() : Gtk::ListViewText(1) { }); EntryBox::get().show(); }; - + menu_item_new_folder.set_label(new_folder_label); menu_item_new_folder.signal_activate().connect(new_folder_function); menu.append(menu_item_new_folder); - + menu_root_item_new_folder.set_label(new_folder_label); menu_root_item_new_folder.signal_activate().connect(new_folder_function); menu_root.append(menu_root_item_new_folder); - + menu.append(menu_item_separator); - + menu_item_rename.set_label("Rename"); menu_item_rename.signal_activate().connect([this] { if(menu_popup_row_path.empty()) @@ -242,17 +242,17 @@ Directories::Directories() : Gtk::ListViewText(1) { EntryBox::get().clear(); EntryBox::get().entries.emplace_back(menu_popup_row_path.filename().string(), [this, source_path=menu_popup_row_path](const std::string &content){ bool is_directory=boost::filesystem::is_directory(source_path); - + auto target_path=source_path.parent_path()/content; - + if(boost::filesystem::exists(target_path)) { Terminal::get().print("Error: could not rename to "+target_path.string()+": already exists\n", true); return; } - + if(is_directory) this->remove_path(source_path); - + boost::system::error_code ec; boost::filesystem::rename(source_path, target_path, ec); if(ec) { @@ -262,7 +262,7 @@ Directories::Directories() : Gtk::ListViewText(1) { update(); on_save_file(target_path); select(target_path); - + for(size_t c=0;c<Notebook::get().size();c++) { auto view=Notebook::get().get_view(c); if(is_directory) { @@ -278,7 +278,7 @@ Directories::Directories() : Gtk::ListViewText(1) { } else if(view->file_path==source_path) { view->rename(target_path); - + std::string old_language_id; if(view->language) old_language_id=view->language->get_id(); @@ -290,7 +290,7 @@ Directories::Directories() : Gtk::ListViewText(1) { Terminal::get().print("Warning: language for "+target_path.string()+" has changed. Please reopen the file\n"); } } - + EntryBox::get().hide(); }); auto entry_it=EntryBox::get().entries.begin(); @@ -301,7 +301,7 @@ Directories::Directories() : Gtk::ListViewText(1) { EntryBox::get().show(); }); menu.append(menu_item_rename); - + menu_item_delete.set_label("Delete"); menu_item_delete.signal_activate().connect([this] { if(menu_popup_row_path.empty()) @@ -312,17 +312,17 @@ Directories::Directories() : Gtk::ListViewText(1) { int result = dialog.run(); if(result==Gtk::RESPONSE_YES) { bool is_directory=boost::filesystem::is_directory(menu_popup_row_path); - + boost::system::error_code ec; boost::filesystem::remove_all(menu_popup_row_path, ec); if(ec) Terminal::get().print("Error: could not delete "+menu_popup_row_path.string()+": "+ec.message()+"\n", true); else { update(); - + for(size_t c=0;c<Notebook::get().size();c++) { auto view=Notebook::get().get_view(c); - + if(is_directory) { if(filesystem::file_in_path(view->file_path, menu_popup_row_path)) view->get_buffer()->set_modified(); @@ -334,13 +334,13 @@ Directories::Directories() : Gtk::ListViewText(1) { } }); menu.append(menu_item_delete); - + menu.show_all(); menu.accelerate(*this); - + menu_root.show_all(); menu_root.accelerate(*this); - + set_headers_clickable(); forall([this](Gtk::Widget &widget) { if(widget.get_name()=="GtkButton") { @@ -363,11 +363,11 @@ void Directories::open(const boost::filesystem::path &dir_path) { boost::system::error_code ec; if(dir_path.empty() || !boost::filesystem::exists(dir_path, ec) || ec) return; - + tree_store->clear(); - + path=filesystem::get_normal_path(dir_path); - + //TODO: report that set_title does not handle '_' correctly? auto title=path.filename().string(); size_t pos=0; @@ -382,7 +382,7 @@ void Directories::open(const boost::filesystem::path &dir_path) { directory.second.repository->clear_saved_status(); } directories.clear(); - + add_or_update_path(path, Gtk::TreeModel::Row(), true); } @@ -406,24 +406,24 @@ void Directories::on_save_file(const boost::filesystem::path &file_path) { void Directories::select(const boost::filesystem::path &select_path) { if(path=="") return; - + if(!filesystem::file_in_path(select_path, path)) return; - + //return if the select_path is already selected auto iter=get_selection()->get_selected(); if(iter) { if(iter->get_value(column_record.path)==select_path) return; } - + std::list<boost::filesystem::path> paths; boost::filesystem::path parent_path; if(boost::filesystem::is_directory(select_path)) parent_path=select_path; else parent_path=select_path.parent_path(); - + //check if select_path is already expanded if(directories.find(parent_path.string())!=directories.end()) { //set cursor at select_path and return @@ -438,7 +438,7 @@ void Directories::select(const boost::filesystem::path &select_path) { }); return; } - + paths.emplace_front(parent_path); while(parent_path!=path) { parent_path=parent_path.parent_path(); @@ -455,7 +455,7 @@ void Directories::select(const boost::filesystem::path &select_path) { return false; }); } - + //set cursor at select_path tree_store->foreach_iter([this, &select_path](const Gtk::TreeModel::iterator &iter){ if(iter->get_value(column_record.path)==select_path) { @@ -493,7 +493,7 @@ bool Directories::on_button_press_event(GdkEventButton* event) { return true; } } - + return Gtk::TreeView::on_button_press_event(event); } @@ -504,19 +504,19 @@ void Directories::add_or_update_path(const boost::filesystem::path &dir_path, co directories.erase(path_it); return; } - + if(path_it==directories.end()) { auto g_file=Gio::File::create_for_path(dir_path.string()); auto monitor=g_file->monitor_directory(Gio::FileMonitorFlags::FILE_MONITOR_WATCH_MOVES); auto path_and_row=std::make_shared<std::pair<boost::filesystem::path, Gtk::TreeModel::Row> >(dir_path, row); auto connection=std::make_shared<sigc::connection>(); - + std::shared_ptr<Git::Repository> repository; try { repository=Git::get_repository(dir_path); } catch(const std::exception &) {} - + monitor->signal_changed().connect([this, connection, path_and_row, repository] (const Glib::RefPtr<Gio::File> &file, const Glib::RefPtr<Gio::File>&, Gio::FileMonitorEvent monitor_event) { @@ -531,12 +531,12 @@ void Directories::add_or_update_path(const boost::filesystem::path &dir_path, co }, 500); } }); - + std::shared_ptr<sigc::connection> repository_connection(new sigc::connection(), [](sigc::connection *connection) { connection->disconnect(); delete connection; }); - + if(repository) { auto connection=std::make_shared<sigc::connection>(); *repository_connection=repository->monitor->signal_changed().connect([this, connection, path_and_row](const Glib::RefPtr<Gio::File> &file, @@ -554,7 +554,7 @@ void Directories::add_or_update_path(const boost::filesystem::path &dir_path, co } directories[dir_path.string()]={row, monitor, repository, repository_connection}; } - + Gtk::TreeNodeChildren children(row?row.children():tree_store->children()); if(children) { if(children.begin()->get_value(column_record.path)=="") @@ -589,7 +589,7 @@ void Directories::add_or_update_path(const boost::filesystem::path &dir_path, co } else { child->set_value(column_record.id, '2'+filename); - + auto language=Source::guess_language(it->path().filename()); if(!language) child->set_value(column_record.type, PathType::UNKNOWN); @@ -611,7 +611,7 @@ void Directories::add_or_update_path(const boost::filesystem::path &dir_path, co child->set_value(column_record.markup, Glib::Markup::escape_text("(empty)")); child->set_value(column_record.type, PathType::UNKNOWN); } - + colorize_path(dir_path, include_parent_paths); } @@ -620,14 +620,14 @@ void Directories::remove_path(const boost::filesystem::path &dir_path) { if(it==directories.end()) return; auto children=it->second.row->children(); - + for(auto it=directories.begin();it!=directories.end();) { if(filesystem::file_in_path(it->first, dir_path)) it=directories.erase(it); else it++; } - + if(children) { while(children) { tree_store->erase(children.begin()); @@ -644,7 +644,7 @@ void Directories::colorize_path(boost::filesystem::path dir_path_, bool include_ auto it=directories.find(dir_path->string()); if(it==directories.end()) return; - + if(it!=directories.end() && it->second.repository) { auto repository=it->second.repository; std::thread git_status_thread([this, dir_path, repository, include_parent_paths] { @@ -655,12 +655,12 @@ void Directories::colorize_path(boost::filesystem::path dir_path_, bool include_ catch(const std::exception &e) { Terminal::get().async_print(std::string("Error (git): ")+e.what()+'\n', true); } - + dispatcher.post([this, dir_path, include_parent_paths, status=std::move(status)] { auto it=directories.find(dir_path->string()); if(it==directories.end()) return; - + auto normal_color=get_style_context()->get_color(Gtk::StateFlags::STATE_FLAG_NORMAL); Gdk::RGBA gray; gray.set_rgba(0.5, 0.5, 0.5); @@ -676,12 +676,12 @@ void Directories::colorize_path(boost::filesystem::path dir_path_, bool include_ green.set_red(normal_color.get_red()+factor*(green.get_red()-normal_color.get_red())); green.set_green(normal_color.get_green()+factor*(green.get_green()-normal_color.get_green())); green.set_blue(normal_color.get_blue()+factor*(green.get_blue()-normal_color.get_blue())); - + do { Gtk::TreeNodeChildren children(it->second.row?it->second.row.children():tree_store->children()); if(!children) return; - + for(auto &child: children) { auto name=Glib::Markup::escape_text(child.get_value(column_record.name)); auto path=child.get_value(column_record.path); @@ -692,22 +692,22 @@ void Directories::colorize_path(boost::filesystem::path dir_path_, bool include_ color=&green; else color=&normal_color; - + std::stringstream ss; ss << '#' << std::setfill('0') << std::hex; ss << std::setw(2) << std::hex << (color->get_red_u()>>8); ss << std::setw(2) << std::hex << (color->get_green_u()>>8); ss << std::setw(2) << std::hex << (color->get_blue_u()>>8); child.set_value(column_record.markup, "<span foreground=\""+ss.str()+"\">"+name+"</span>"); - + auto type=child.get_value(column_record.type); if(type==PathType::UNKNOWN) child.set_value(column_record.markup, "<i>"+child.get_value(column_record.markup)+"</i>"); } - + if(!include_parent_paths) break; - + auto path=boost::filesystem::path(it->first); if(boost::filesystem::exists(path/".git")) break; diff --git a/src/directories.h b/src/directories.h index 73c28618..0bac8718 100644 --- a/src/directories.h +++ b/src/directories.h @@ -19,17 +19,17 @@ class Directories : public Gtk::ListViewText { std::shared_ptr<Git::Repository> repository; std::shared_ptr<sigc::connection> connection; }; - + enum class PathType {KNOWN, UNKNOWN}; - + class TreeStore : public Gtk::TreeStore { protected: TreeStore()=default; - + bool row_drop_possible_vfunc(const Gtk::TreeModel::Path &path, const Gtk::SelectionData &selection_data) const override; bool drag_data_received_vfunc(const TreeModel::Path &path, const Gtk::SelectionData &selection_data) override; bool drag_data_delete_vfunc (const Gtk::TreeModel::Path &path) override; - + public: class ColumnRecord : public Gtk::TreeModel::ColumnRecord { public: @@ -46,7 +46,7 @@ class Directories : public Gtk::ListViewText { Gtk::TreeModelColumn<boost::filesystem::path> path; Gtk::TreeModelColumn<PathType> type; }; - + static Glib::RefPtr<TreeStore> create() {return Glib::RefPtr<TreeStore>(new TreeStore());} }; @@ -57,29 +57,29 @@ class Directories : public Gtk::ListViewText { return singleton; } ~Directories() override; - + void open(const boost::filesystem::path &dir_path=""); void update(); void on_save_file(const boost::filesystem::path &file_path); void select(const boost::filesystem::path &path); - + boost::filesystem::path path; - + protected: bool on_button_press_event(GdkEventButton *event) override; - + private: void add_or_update_path(const boost::filesystem::path &dir_path, const Gtk::TreeModel::Row &row, bool include_parent_paths); void remove_path(const boost::filesystem::path &dir_path); void colorize_path(boost::filesystem::path dir_path_, bool include_parent_paths); - + Glib::RefPtr<Gtk::TreeStore> tree_store; TreeStore::ColumnRecord column_record; - + std::unordered_map<std::string, DirectoryData> directories; - + Dispatcher dispatcher; - + Gtk::Menu menu; Gtk::MenuItem menu_item_new_file; Gtk::MenuItem menu_item_new_folder; diff --git a/src/documentation_cppreference.cc b/src/documentation_cppreference.cc index fe132ea6..ceec6225 100644 --- a/src/documentation_cppreference.cc +++ b/src/documentation_cppreference.cc @@ -12173,7 +12173,7 @@ std::experimental::filesystem::is_socket cpp/experimental/fs/is_socket std::experimental::filesystem::is_symlink cpp/experimental/fs/is_symlink std::experimental::filesystem::status_known cpp/experimental/fs/status_known"} )"; - + class SymbolToUrl { public: SymbolToUrl(const std::string &symbol_urls) { @@ -12197,7 +12197,7 @@ std::experimental::filesystem::status_known cpp/experimental/fs/status_known"} } std::unordered_map<std::string, std::string> map; }; - + static SymbolToUrl symbol_to_url(symbol_urls); auto it=symbol_to_url.map.find(symbol); if(it==symbol_to_url.map.end()) diff --git a/src/entrybox.h b/src/entrybox.h index 7781e5c1..749ae7a6 100644 --- a/src/entrybox.h +++ b/src/entrybox.h @@ -30,7 +30,7 @@ class EntryBox : public Gtk::Box { Label(std::function<void(int state, const std::string& message)> update_=nullptr); std::function<void(int state, const std::string& message)> update; }; - + private: EntryBox(); public: @@ -38,7 +38,7 @@ class EntryBox : public Gtk::Box { static EntryBox singleton; return singleton; } - + Gtk::Box upper_box; Gtk::Box lower_box; void clear(); @@ -48,7 +48,7 @@ class EntryBox : public Gtk::Box { std::list<Button> buttons; std::list<ToggleButton> toggle_buttons; std::list<Label> labels; - + private: static std::unordered_map<std::string, std::vector<std::string> > entry_histories; }; diff --git a/src/filesystem.cc b/src/filesystem.cc index 98df6a82..1d0e27e5 100644 --- a/src/filesystem.cc +++ b/src/filesystem.cc @@ -51,7 +51,7 @@ std::string filesystem::escape_argument(const std::string &argument) { std::string filesystem::unescape_argument(const std::string &argument) { auto unescaped=argument; - + if(unescaped.size()>=2) { if((unescaped[0]=='\'' && unescaped[unescaped.size()-1]=='\'') || (unescaped[0]=='"' && unescaped[unescaped.size()-1]=='"')) { @@ -72,7 +72,7 @@ std::string filesystem::unescape_argument(const std::string &argument) { return unescaped; } } - + size_t backslash_count=0; for(size_t pos=0;pos<unescaped.size();++pos) { if(backslash_count%2==1 && (unescaped[pos]=='\\' || unescaped[pos]==' ' || unescaped[pos]=='(' || unescaped[pos]==')' || unescaped[pos]=='\'' || unescaped[pos]=='"')) { @@ -134,7 +134,7 @@ boost::filesystem::path filesystem::find_file_in_path_parents(const std::string boost::filesystem::path filesystem::get_normal_path(const boost::filesystem::path &path) noexcept { boost::filesystem::path normal_path; - + for(auto &e: path) { if(e==".") continue; @@ -150,7 +150,7 @@ boost::filesystem::path filesystem::get_normal_path(const boost::filesystem::pat else normal_path/=e; } - + return normal_path; } @@ -159,7 +159,7 @@ boost::filesystem::path filesystem::get_relative_path(const boost::filesystem::p if(std::distance(path.begin(), path.end())<std::distance(base.begin(), base.end())) return boost::filesystem::path(); - + auto base_it=base.begin(); auto path_it=path.begin(); while(path_it!=path.end() && base_it!=base.end()) { @@ -180,13 +180,13 @@ boost::filesystem::path filesystem::get_executable(const boost::filesystem::path #endif static std::vector<boost::filesystem::path> bin_paths={"/usr/bin", "/usr/local/bin"}; - + try { for(auto &path: bin_paths) { if(boost::filesystem::exists(path/executable_name)) return executable_name; } - + auto &executable_name_str = executable_name.string(); for(auto &path: bin_paths) { boost::filesystem::path executable; @@ -211,7 +211,7 @@ boost::filesystem::path filesystem::get_executable(const boost::filesystem::path } } catch(...) {} - + return executable_name; } diff --git a/src/git.cc b/src/git.cc index 99d21746..3c4dd34a 100644 --- a/src/git.cc +++ b/src/git.cc @@ -22,7 +22,7 @@ Git::Repository::Diff::Diff(const boost::filesystem::path &path, git_repository error.code = git_revparse_single(reinterpret_cast<git_object**>(&blob), repository, spec.c_str()); if(error) throw std::runtime_error(error.message()); - + git_diff_init_options(&options, GIT_DIFF_OPTIONS_VERSION); options.context_lines=0; } @@ -38,7 +38,7 @@ int Git::Repository::Diff::hunk_cb(const git_diff_delta *delta, const git_diff_h lines->removed.emplace_back(start); else lines->modified.emplace_back(start, end); - + return 0; } @@ -105,11 +105,11 @@ Git::Repository::Repository(const boost::filesystem::path &path) { repository=std::unique_ptr<git_repository, std::function<void(git_repository *)> >(repository_ptr, [](git_repository *ptr) { git_repository_free(ptr); }); - + work_path=get_work_path(); if(work_path.empty()) throw std::runtime_error("Could not find work path"); - + auto git_directory=Gio::File::create_for_path(get_path().string()); monitor=git_directory->monitor_directory(Gio::FileMonitorFlags::FILE_MONITOR_WATCH_MOVES); monitor_changed_connection=monitor->signal_changed().connect([this](const Glib::RefPtr<Gio::File> &file, @@ -142,7 +142,7 @@ std::string Git::Repository::status_string(STATUS status) noexcept { int Git::Repository::status_callback(const char *path, unsigned int status_flags, void *data) noexcept { auto callback=static_cast<std::function<void(const char *path, STATUS status)>*>(data); - + STATUS status; if((status_flags&(GIT_STATUS_INDEX_NEW|GIT_STATUS_WT_NEW))>0) status=STATUS::NEW; @@ -162,10 +162,10 @@ int Git::Repository::status_callback(const char *path, unsigned int status_flags status=STATUS::CONFLICTED; else status=STATUS::CURRENT; - + if(*callback) (*callback)(path, status); - + return 0; } @@ -175,7 +175,7 @@ Git::Repository::Status Git::Repository::get_status() { if(has_saved_status) return saved_status; } - + Status status; bool first=true; std::unique_lock<std::mutex> status_saved_lock(saved_status_mutex, std::defer_lock); @@ -274,7 +274,7 @@ std::shared_ptr<Git::Repository> Git::get_repository(const boost::filesystem::pa initialize(); static std::unordered_map<std::string, std::weak_ptr<Git::Repository> > cache; static std::mutex mutex; - + std::lock_guard<std::mutex> lock(mutex); auto root_path=Repository::get_root_path(path).generic_string(); auto it=cache.find(root_path); diff --git a/src/git.h b/src/git.h index 616e8b0d..a8952ae7 100644 --- a/src/git.h +++ b/src/git.h @@ -19,7 +19,7 @@ class Git { int code=0; operator bool() noexcept {return code<0;} }; - + class Repository { public: class Diff { @@ -51,7 +51,7 @@ class Git { static std::vector<Hunk> get_hunks(const std::string &old_buffer, const std::string &new_buffer); std::string get_details(const std::string &buffer, int line_nr); }; - + enum class STATUS {CURRENT, NEW, MODIFIED, DELETED, RENAMED, TYPECHANGE, UNREADABLE, IGNORED, CONFLICTED}; class Status { public: @@ -61,11 +61,11 @@ class Git { private: friend class Git; Repository(const boost::filesystem::path &path); - + static int status_callback(const char *path, unsigned int status_flags, void *data) noexcept; - + std::unique_ptr<git_repository, std::function<void(git_repository *)> > repository; - + boost::filesystem::path work_path; sigc::connection monitor_changed_connection; Status saved_status; @@ -73,32 +73,32 @@ class Git { std::mutex saved_status_mutex; public: ~Repository(); - + static std::string status_string(STATUS status) noexcept; - + Status get_status(); void clear_saved_status(); - + boost::filesystem::path get_work_path() noexcept; boost::filesystem::path get_path() noexcept; static boost::filesystem::path get_root_path(const boost::filesystem::path &path); - + Diff get_diff(const boost::filesystem::path &path); - + std::string get_branch() noexcept; - + Glib::RefPtr<Gio::FileMonitor> monitor; }; - + private: static bool initialized; - + ///Mutex for thread safe operations static std::mutex mutex; - + ///Call initialize in public static methods static void initialize() noexcept; - + static boost::filesystem::path path(const char *cpath, size_t cpath_length=static_cast<size_t>(-1)) noexcept; public: static std::shared_ptr<Repository> get_repository(const boost::filesystem::path &path); diff --git a/src/menu.h b/src/menu.h index f1afec45..a028dd25 100644 --- a/src/menu.h +++ b/src/menu.h @@ -11,13 +11,13 @@ class Menu { static Menu singleton; return singleton; } - + void add_action(const std::string &name, const std::function<void()> &action); std::unordered_map<std::string, Glib::RefPtr<Gio::SimpleAction> > actions; void set_keys(); - + void build(); - + Glib::RefPtr<Gio::Menu> juci_menu; Glib::RefPtr<Gio::Menu> window_menu; std::unique_ptr<Gtk::Menu> right_click_line_menu; diff --git a/src/meson.cc b/src/meson.cc index 04b30de6..30485f12 100644 --- a/src/meson.cc +++ b/src/meson.cc @@ -36,11 +36,11 @@ bool Meson::update_debug_build(const boost::filesystem::path &debug_build_path, return false; if (!create_build_directory(debug_build_path)) return false; - + bool compile_commands_exists=boost::filesystem::exists(debug_build_path/"compile_commands.json"); if(!force && compile_commands_exists) return true; - + Dialog::Message message("Creating/updating debug build"); auto exit_status=Terminal::get().process(Config::get().project.meson.command+' '+(compile_commands_exists?"--internal regenerate ":"")+ "--buildtype debug "+filesystem::escape_argument(get_project_path().string()), debug_build_path); @@ -52,7 +52,7 @@ bool Meson::update_debug_build(const boost::filesystem::path &debug_build_path, boost::filesystem::path Meson::get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) { CompileCommands compile_commands(build_path); - + size_t best_match_size=-1; boost::filesystem::path best_match_executable; for(auto &command: compile_commands.commands) { @@ -77,6 +77,6 @@ boost::filesystem::path Meson::get_executable(const boost::filesystem::path &bui } } } - + return best_match_executable; } diff --git a/src/notebook.cc b/src/notebook.cc index 4c30b683..4d65b9e6 100644 --- a/src/notebook.cc +++ b/src/notebook.cc @@ -12,20 +12,20 @@ Notebook::TabLabel::TabLabel(const std::function<void()> &on_close) { set_can_focus(false); - + auto button=Gtk::manage(new Gtk::Button()); auto hbox=Gtk::manage(new Gtk::Box()); - + hbox->set_can_focus(false); 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); - + hbox->pack_start(label, Gtk::PACK_SHRINK); hbox->pack_end(*button, Gtk::PACK_SHRINK); add(*hbox); - + button->signal_clicked().connect(on_close); signal_button_press_event().connect([on_close](GdkEventButton *event) { if(event->button==GDK_BUTTON_MIDDLE) { @@ -34,7 +34,7 @@ Notebook::TabLabel::TabLabel(const std::function<void()> &on_close) { } return false; }); - + show_all(); } @@ -104,10 +104,10 @@ std::vector<Source::View*> &Notebook::get_views() { void Notebook::open(const boost::filesystem::path &file_path_, size_t notebook_index) { auto file_path=filesystem::get_normal_path(file_path_); - + if(notebook_index==1 && !split) toggle_split(); - + // Use canonical path to follow symbolic links boost::system::error_code ec; auto canonical_file_path=boost::filesystem::canonical(file_path, ec); @@ -121,7 +121,7 @@ void Notebook::open(const boost::filesystem::path &file_path_, size_t notebook_i return; } } - + if(boost::filesystem::exists(file_path)) { std::ifstream can_read(file_path.string()); if(!can_read) { @@ -130,11 +130,11 @@ void Notebook::open(const boost::filesystem::path &file_path_, size_t notebook_i } can_read.close(); } - + auto last_view=get_current_view(); - + auto language=Source::guess_language(file_path); - + std::string language_protocol_language_id; if(language) { language_protocol_language_id=language->get_id(); @@ -145,17 +145,17 @@ void Notebook::open(const boost::filesystem::path &file_path_, size_t notebook_i language_protocol_language_id="javascript"; } } - + if(language && (language->get_id()=="chdr" || language->get_id()=="cpphdr" || language->get_id()=="c" || language->get_id()=="cpp" || language->get_id()=="objc")) source_views.emplace_back(new Source::ClangView(file_path, language)); else if(language && !language_protocol_language_id.empty() && !filesystem::find_executable(language_protocol_language_id+"-language-server").empty()) source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id)); else source_views.emplace_back(new Source::GenericView(file_path, language)); - + auto source_view=source_views.back(); source_view->configure(); - + source_view->scroll_to_cursor_delayed=[this](Source::BaseView* view, bool center, bool show_tooltips) { while(Gtk::Main::events_pending()) Gtk::Main::iteration(false); @@ -189,7 +189,7 @@ void Notebook::open(const boost::filesystem::path &file_path_, size_t notebook_i source_view->update_status_diagnostics=[this](Source::BaseView* view) { if(get_current_view()==view) { std::string diagnostic_info; - + auto num_warnings=std::get<0>(view->status_diagnostics); auto num_errors=std::get<1>(view->status_diagnostics); auto num_fix_its=std::get<2>(view->status_diagnostics); @@ -213,7 +213,7 @@ void Notebook::open(const boost::filesystem::path &file_path_, size_t notebook_i green.set_red(normal_color.get_red()+factor*(green.get_red()-normal_color.get_red())); green.set_green(normal_color.get_green()+factor*(green.get_green()-normal_color.get_green())); green.set_blue(normal_color.get_blue()+factor*(green.get_blue()-normal_color.get_blue())); - + std::stringstream yellow_ss, red_ss, green_ss; yellow_ss << std::hex << std::setfill('0') << std::setw(2) << (int)(yellow.get_red_u()>>8) << std::setw(2) << (int)(yellow.get_green_u()>>8) << std::setw(2) << (int)(yellow.get_blue_u()>>8); red_ss << std::hex << std::setfill('0') << std::setw(2) << (int)(red.get_red_u()>>8) << std::setw(2) << (int)(red.get_green_u()>>8) << std::setw(2) << (int)(red.get_blue_u()>>8); @@ -251,7 +251,7 @@ void Notebook::open(const boost::filesystem::path &file_path_, size_t notebook_i if(get_current_view()==view) status_state.set_text(view->status_state+" "); }; - + scrolled_windows.emplace_back(new Gtk::ScrolledWindow()); hboxes.emplace_back(new Gtk::Box()); scrolled_windows.back()->add(*source_view); @@ -261,7 +261,7 @@ void Notebook::open(const boost::filesystem::path &file_path_, size_t notebook_i gtk_source_map_set_view(GTK_SOURCE_MAP(source_maps.back()->gobj()), source_view->gobj()); configure(source_views.size()-1); - + //Set up tab label tab_labels.emplace_back(new TabLabel([this, source_view]() { auto index=get_index(source_view); @@ -284,13 +284,13 @@ void Notebook::open(const boost::filesystem::path &file_path_, size_t notebook_i } }; source_view->update_tab_label(source_view); - + //Add star on tab label when the page is not saved: source_view->get_buffer()->signal_modified_changed().connect([source_view]() { if(source_view->update_tab_label) source_view->update_tab_label(source_view); }); - + //Cursor history auto update_cursor_locations=[this, source_view](const Gtk::TextBuffer::iterator &iter) { bool mark_moved=false; @@ -311,7 +311,7 @@ void Notebook::open(const boost::filesystem::path &file_path_, size_t notebook_i cursor_locations.emplace_back(source_view, source_view->get_buffer()->create_mark(iter)); current_cursor_location=cursor_locations.size()-1; } - + // Combine adjacent cursor histories that are similar if(!cursor_locations.empty()) { size_t cursor_locations_index=1; @@ -331,7 +331,7 @@ void Notebook::open(const boost::filesystem::path &file_path_, size_t notebook_i } } } - + // Remove start of cache if cache limit is exceeded while(cursor_locations.size()>10) { cursor_locations.begin()->view->get_buffer()->delete_mark(cursor_locations.begin()->mark); @@ -339,7 +339,7 @@ void Notebook::open(const boost::filesystem::path &file_path_, size_t notebook_i if(current_cursor_location!=static_cast<size_t>(-1)) --current_cursor_location; } - + if(current_cursor_location>=cursor_locations.size()) current_cursor_location=cursor_locations.size()-1; }; @@ -355,7 +355,7 @@ void Notebook::open(const boost::filesystem::path &file_path_, size_t notebook_i source_view->get_buffer()->signal_changed().connect([source_view, update_cursor_locations] { update_cursor_locations(source_view->get_buffer()->get_insert()->get_iter()); }); - + #ifdef JUCI_ENABLE_DEBUG if(dynamic_cast<Source::ClangView*>(source_view) || (source_view->language && source_view->language->get_id()=="rust")) { source_view->toggle_breakpoint=[source_view](int line_nr) { @@ -378,12 +378,12 @@ void Notebook::open(const boost::filesystem::path &file_path_, size_t notebook_i }; } #endif - + source_view->signal_focus_in_event().connect([this, source_view](GdkEventFocus *) { set_current_view(source_view); return false; }); - + if(notebook_index==static_cast<size_t>(-1)) { if(!split) notebook_index=0; @@ -395,13 +395,13 @@ void Notebook::open(const boost::filesystem::path &file_path_, size_t notebook_i notebook_index=get_notebook_page(get_index(last_view)).first; } auto ¬ebook=notebooks[notebook_index]; - + notebook.append_page(*hboxes.back(), *tab_labels.back()); - + notebook.set_tab_reorderable(*hboxes.back(), true); notebook.set_tab_detachable(*hboxes.back(), true); show_all_children(); - + notebook.set_current_page(notebook.get_n_pages()-1); last_index=-1; if(last_view) { @@ -410,14 +410,14 @@ void Notebook::open(const boost::filesystem::path &file_path_, size_t notebook_i if(notebook_page.first==notebook_index) last_index=index; } - + set_focus_child(*source_views.back()); focus_view(source_view); } void Notebook::configure(size_t index) { auto source_font_description=Pango::FontDescription(Config::get().source.font); - auto source_map_font_desc=Pango::FontDescription(static_cast<std::string>(source_font_description.get_family())+" "+Config::get().source.map_font_size); + auto source_map_font_desc=Pango::FontDescription(static_cast<std::string>(source_font_description.get_family())+" "+Config::get().source.map_font_size); source_maps.at(index)->override_font(source_map_font_desc); if(Config::get().source.show_map) { if(hboxes.at(index)->get_children().size()==1) @@ -474,19 +474,19 @@ bool Notebook::close(size_t index) { last_index=-1; else if(index<last_index && last_index!=static_cast<size_t>(-1)) last_index--; - + auto notebook_page=get_notebook_page(index); notebooks[notebook_page.first].remove_page(notebook_page.second); source_maps.erase(source_maps.begin()+index); if(on_close_page) on_close_page(view); - + delete_cursor_locations(view); - + SelectionDialog::get()=nullptr; CompletionDialog::get()=nullptr; - + if(auto clang_view=dynamic_cast<Source::ClangView*>(view)) clang_view->async_delete(); else diff --git a/src/notebook.h b/src/notebook.h index 5aae90f4..eace7fd6 100644 --- a/src/notebook.h +++ b/src/notebook.h @@ -12,14 +12,14 @@ class Notebook : public Gtk::Paned { TabLabel(const std::function<void()> &on_close); Gtk::Label label; }; - + class CursorLocation { public: CursorLocation(Source::View *view, Glib::RefPtr<Gtk::TextBuffer::Mark> mark_) : view(view), mark(std::move(mark_)) {} Source::View *view; Glib::RefPtr<Gtk::TextBuffer::Mark> mark; }; - + private: Notebook(); public: @@ -27,12 +27,12 @@ class Notebook : public Gtk::Paned { static Notebook singleton; return singleton; } - + size_t size(); Source::View* get_view(size_t index); Source::View* get_current_view(); std::vector<Source::View*> &get_views(); - + void open(const boost::filesystem::path &file_path, size_t notebook_index=-1); void configure(size_t index); bool save(size_t index); @@ -54,35 +54,35 @@ class Notebook : public Gtk::Paned { Gtk::Label status_state; void update_status(Source::BaseView *view); void clear_status(); - + std::function<void(Source::View*)> on_change_page; std::function<void(Source::View*)> on_close_page; - + /// Cursor history std::vector<CursorLocation> cursor_locations; size_t current_cursor_location=-1; bool disable_next_update_cursor_locations=false; void delete_cursor_locations(Source::View *view); - + private: size_t get_index(Source::View *view); Source::View *get_view(size_t notebook_index, int page); void focus_view(Source::View *view); std::pair<size_t, int> get_notebook_page(size_t index); - + std::vector<Gtk::Notebook> notebooks; std::vector<Source::View*> source_views; //Is NOT freed in destructor, this is intended for quick program exit. std::vector<std::unique_ptr<Gtk::Widget> > source_maps; std::vector<std::unique_ptr<Gtk::ScrolledWindow> > scrolled_windows; std::vector<std::unique_ptr<Gtk::Box> > hboxes; std::vector<std::unique_ptr<TabLabel> > tab_labels; - + bool split=false; size_t last_index=-1; - + void set_current_view(Source::View *view); Source::View* current_view=nullptr; Source::View* intermediate_view=nullptr; - + bool save_modified_dialog(size_t index); }; diff --git a/src/project.cc b/src/project.cc index 2f32f8e9..2740bbd8 100644 --- a/src/project.cc +++ b/src/project.cc @@ -60,7 +60,7 @@ void Project::on_save(size_t index) { else build_path=filesystem::find_file_in_path_parents("meson.build", view->file_path.parent_path()); } - + if(!build_path.empty()) { auto build=Build::create(build_path); if(dynamic_cast<CMakeBuild*>(build.get()) || dynamic_cast<MesonBuild*>(build.get())) { @@ -69,7 +69,7 @@ void Project::on_save(size_t index) { boost::system::error_code ec; if(boost::filesystem::exists(build->get_debug_path()), ec) build->update_debug(true); - + for(size_t c=0;c<Notebook::get().size();c++) { auto source_view=Notebook::get().get_view(c); if(auto source_clang_view=dynamic_cast<Source::ClangView*>(source_view)) { @@ -135,7 +135,7 @@ void Project::debug_update_stop() { std::shared_ptr<Project::Base> Project::create() { std::unique_ptr<Project::Build> build; - + if(auto view=Notebook::get().get_current_view()) { build=Build::create(view->file_path); if(view->language) { @@ -152,7 +152,7 @@ std::shared_ptr<Project::Base> Project::create() { } else build=Build::create(Directories::get().path); - + if(dynamic_cast<CMakeBuild*>(build.get()) || dynamic_cast<MesonBuild*>(build.get())) return std::shared_ptr<Project::Base>(new Project::Clang(std::move(build))); else if(dynamic_cast<CargoBuild*>(build.get())) @@ -182,7 +182,7 @@ void Project::Base::recreate_build() { void Project::Base::show_symbols() { auto view=Notebook::get().get_current_view(); - + boost::filesystem::path search_path; if(view) search_path=view->file_path.parent_path(); @@ -197,7 +197,7 @@ void Project::Base::show_symbols() { } } auto pair=Ctags::get_result(search_path); - + auto path=std::move(pair.first); auto stream=std::move(pair.second); stream->seekg(0, std::ios::end); @@ -206,25 +206,25 @@ void Project::Base::show_symbols() { return; } stream->seekg(0, std::ios::beg); - + if(view) { auto dialog_iter=view->get_iter_for_dialog(); SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); } else SelectionDialog::create(true, true); - + std::vector<Source::Offset> rows; - + std::string line; while(std::getline(*stream, line)) { auto location=Ctags::get_location(line, true); - + std::string row=location.file_path.string()+":"+std::to_string(location.line+1)+": "+location.source; rows.emplace_back(Source::Offset(location.line, location.index, location.file_path)); SelectionDialog::get()->add_row(row); } - + if(rows.size()==0) return; SelectionDialog::get()->on_select=[rows=std::move(rows), path=std::move(path)](unsigned int index, const std::string &text, bool hide_window) { @@ -260,17 +260,17 @@ std::pair<std::string, std::string> Project::LLDB::debug_get_run_arguments() { auto default_build_path=build->get_default_path(); if(debug_build_path.empty() || default_build_path.empty()) return {"", ""}; - + auto project_path=build->get_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.arguments; - + if(arguments.empty()) { auto view=Notebook::get().get_current_view(); auto executable=build->get_executable(view?view->file_path:Directories::get().path).string(); - + if(!executable.empty()) { size_t pos=executable.find(default_build_path.string()); if(pos!=std::string::npos) @@ -280,18 +280,18 @@ std::pair<std::string, std::string> Project::LLDB::debug_get_run_arguments() { else arguments=filesystem::escape_argument(filesystem::get_short_path(build->get_debug_path()).string()); } - + return {project_path, arguments}; } Project::DebugOptions *Project::LLDB::debug_get_options() { if(build->get_project_path().empty()) return nullptr; - + debug_options=std::make_unique<DebugOptions>(); - + auto &arguments=Project::debug_run_arguments[build->get_project_path().string()]; - + auto remote_enabled=Gtk::manage(new Gtk::CheckButton()); auto remote_host_port=Gtk::manage(new Gtk::Entry()); remote_enabled->set_active(arguments.remote_enabled); @@ -299,31 +299,31 @@ Project::DebugOptions *Project::LLDB::debug_get_options() { remote_enabled->signal_clicked().connect([remote_enabled, remote_host_port] { remote_host_port->set_sensitive(remote_enabled->get_active()); }); - + remote_host_port->set_sensitive(arguments.remote_enabled); remote_host_port->set_text(arguments.remote_host_port); remote_host_port->set_placeholder_text("host:port"); remote_host_port->signal_activate().connect([] { debug_options->hide(); }); - + auto self=this->shared_from_this(); debug_options->signal_hide().connect([self, remote_enabled, remote_host_port] { auto &arguments=Project::debug_run_arguments[self->build->get_project_path().string()]; arguments.remote_enabled=remote_enabled->get_active(); arguments.remote_host_port=remote_host_port->get_text(); }); - + auto remote_vbox=Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL)); remote_vbox->pack_start(*remote_enabled, true, true); remote_vbox->pack_end(*remote_host_port, true, true); - + auto remote_frame=Gtk::manage(new Gtk::Frame()); remote_frame->set_label("Remote Debugging"); remote_frame->add(*remote_vbox); - + debug_options->vbox.pack_end(*remote_frame, true, true); - + return debug_options.get(); } @@ -332,14 +332,14 @@ void Project::LLDB::debug_start() { auto default_build_path=build->get_default_path(); if(debug_build_path.empty() || !build->update_debug() || default_build_path.empty()) return; - + auto project_path=std::make_shared<boost::filesystem::path>(build->get_project_path()); - + auto run_arguments_it=debug_run_arguments.find(project_path->string()); auto run_arguments=std::make_shared<std::string>(); if(run_arguments_it!=debug_run_arguments.end()) *run_arguments=run_arguments_it->second.arguments; - + if(run_arguments->empty()) { auto view=Notebook::get().get_current_view(); *run_arguments=build->get_executable(view?view->file_path:Directories::get().path).string(); @@ -353,12 +353,12 @@ void Project::LLDB::debug_start() { run_arguments->replace(pos, default_build_path.string().size(), debug_build_path.string()); *run_arguments=filesystem::escape_argument(filesystem::get_short_path(*run_arguments).string()); } - + debugging=true; - + if(Config::get().project.clear_terminal_on_compile) Terminal::get().clear(); - + Terminal::get().print("Compiling and debugging "+*run_arguments+"\n"); Terminal::get().async_process(build->get_compile_command(), debug_build_path, [self=this->shared_from_this(), run_arguments, project_path](int exit_status){ if(exit_status!=EXIT_SUCCESS) @@ -376,12 +376,12 @@ void Project::LLDB::debug_start() { breakpoints.emplace_back(view->file_path, iter.get_line()+1); } } - + std::string remote_host; auto debug_run_arguments_it=debug_run_arguments.find(project_path->string()); if(debug_run_arguments_it!=debug_run_arguments.end() && debug_run_arguments_it->second.remote_enabled) remote_host=debug_run_arguments_it->second.remote_host_port; - + static auto on_exit_it=Debug::LLDB::get().on_exit.end(); if(on_exit_it!=Debug::LLDB::get().on_exit.end()) Debug::LLDB::get().on_exit.erase(on_exit_it); @@ -393,7 +393,7 @@ void Project::LLDB::debug_start() { }); }); on_exit_it=std::prev(Debug::LLDB::get().on_exit.end()); - + static auto on_event_it=Debug::LLDB::get().on_event.end(); if(on_event_it!=Debug::LLDB::get().on_event.end()) Debug::LLDB::get().on_event.erase(on_event_it); @@ -401,7 +401,7 @@ void Project::LLDB::debug_start() { std::string status; boost::filesystem::path stop_path; unsigned stop_line=0, stop_column=0; - + std::unique_lock<std::mutex> lock(Debug::LLDB::get().mutex); auto process=lldb::SBProcess::GetProcessFromEvent(event); auto state=lldb::SBProcess::GetStateFromEvent(event); @@ -432,7 +432,7 @@ void Project::LLDB::debug_start() { stop_column=column-1; } } - + self->dispatcher.post([status=std::move(status), stop_path=std::move(stop_path), stop_line, stop_column] { debug_update_status(status); Project::debug_stop.first=stop_path; @@ -444,7 +444,7 @@ void Project::LLDB::debug_start() { }); }); on_event_it=std::prev(Debug::LLDB::get().on_event.end()); - + std::vector<std::string> startup_commands; if(dynamic_cast<CargoBuild*>(self->build.get())) { std::stringstream istream, ostream; @@ -496,7 +496,7 @@ void Project::LLDB::debug_backtrace() { if(debugging) { auto view=Notebook::get().get_current_view(); auto backtrace=Debug::LLDB::get().get_backtrace(); - + if(view) { auto iter=view->get_iter_for_dialog(); SelectionDialog::create(view, view->get_buffer()->create_mark(iter), true, true); @@ -508,11 +508,11 @@ void Project::LLDB::debug_backtrace() { Info::get().print("No backtrace found"); return; } - + bool cursor_set=false; 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); @@ -530,7 +530,7 @@ void Project::LLDB::debug_backtrace() { cursor_set=true; } } - + SelectionDialog::get()->on_select=[rows=std::move(rows)](unsigned int index, const std::string &text, bool hide_window) { if(index>=rows.size()) return; @@ -539,7 +539,7 @@ void Project::LLDB::debug_backtrace() { Notebook::get().open(frame.file_path); if(auto view=Notebook::get().get_current_view()) { Debug::LLDB::get().select_frame(frame.index); - + view->place_cursor_at_line_index(frame.line_nr-1, frame.line_index-1); view->scroll_to_cursor_delayed(view, true, true); } @@ -555,7 +555,7 @@ void Project::LLDB::debug_show_variables() { if(debugging) { auto view=Notebook::get().get_current_view(); auto variables=Debug::LLDB::get().get_variables(); - + Gtk::TextIter iter; if(view) { iter=view->get_iter_for_dialog(); @@ -568,14 +568,14 @@ void Project::LLDB::debug_show_variables() { Info::get().print("No variables found"); 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->emplace_back(variable); SelectionDialog::get()->add_row(row); } - + SelectionDialog::get()->on_select=[rows](unsigned int index, const std::string &text, bool hide_window) { if(index>=rows->size()) return; @@ -591,12 +591,12 @@ void Project::LLDB::debug_show_variables() { if(!variable.declaration_found) Info::get().print("Debugger did not find declaration for the variable: "+variable.name); }; - + SelectionDialog::get()->on_hide=[self=this->shared_from_this()]() { self->debug_variable_tooltips.hide(); self->debug_variable_tooltips.clear(); }; - + SelectionDialog::get()->on_changed=[self=this->shared_from_this(), rows, view, iter](unsigned int index, const std::string &text) { if(index>=rows->size()) { self->debug_variable_tooltips.hide(); @@ -606,7 +606,7 @@ void Project::LLDB::debug_show_variables() { auto create_tooltip_buffer=[rows, view, index]() { auto variable=(*rows)[index]; auto tooltip_buffer=view?Gtk::TextBuffer::create(view->get_buffer()->get_tag_table()):Gtk::TextBuffer::create(); - + Glib::ustring value=variable.value; if(!value.empty()) { Glib::ustring::iterator iter; @@ -620,18 +620,18 @@ void Project::LLDB::debug_show_variables() { else tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), value.substr(0, value.size()-1)); } - + return tooltip_buffer; }; - + if(view) self->debug_variable_tooltips.emplace_back(create_tooltip_buffer, view, view->get_buffer()->create_mark(iter), view->get_buffer()->create_mark(iter)); else self->debug_variable_tooltips.emplace_back(create_tooltip_buffer); - + self->debug_variable_tooltips.show(true); }; - + if(view) view->hide_tooltips(); SelectionDialog::get()->show(); @@ -672,24 +672,24 @@ void Project::LanguageProtocol::show_symbols() { Info::get().print("Could not find project folder"); return; } - + auto language_id=get_language_id(); auto executable_name=language_id+"-language-server"; if(filesystem::find_executable(executable_name).empty()) { Info::get().print("Executable "+executable_name+" not found"); return; } - + auto project_path=std::make_shared<boost::filesystem::path>(build->get_project_path()); - + auto client=::LanguageProtocol::Client::get(*project_path, language_id); auto capabilities=client->initialize(nullptr); - + if(!capabilities.workspace_symbol) { Info::get().print("Language server does not support workspace/symbol"); return; } - + auto view=Notebook::get().get_current_view(); if(view) { auto dialog_iter=view->get_iter_for_dialog(); @@ -697,11 +697,11 @@ void Project::LanguageProtocol::show_symbols() { } else SelectionDialog::create(true, true); - + SelectionDialog::get()->on_hide=[] { SelectionDialog::get()->on_search_entry_changed=nullptr; // To delete client object }; - + auto offsets=std::make_shared<std::vector<Source::Offset>>(); SelectionDialog::get()->on_search_entry_changed=[client, project_path, offsets](const std::string &text) { if(text.size()>1) @@ -746,7 +746,7 @@ void Project::LanguageProtocol::show_symbols() { for(size_t c=0;c<offsets->size() && c<names.size();++c) SelectionDialog::get()->add_row(filesystem::get_relative_path((*offsets)[c].file_path, *project_path).string()+':'+std::to_string((*offsets)[c].line+1)+':'+std::to_string((*offsets)[c].index+1)+": "+names[c]); }; - + SelectionDialog::get()->on_select=[offsets](unsigned int index, const std::string &text, bool hide_window) { auto &offset=(*offsets)[index]; if(!boost::filesystem::is_regular_file(offset.file_path)) @@ -757,7 +757,7 @@ void Project::LanguageProtocol::show_symbols() { view->scroll_to_cursor_delayed(view, true, false); view->hide_tooltips(); }; - + if(view) view->hide_tooltips(); SelectionDialog::get()->show(); @@ -767,23 +767,23 @@ std::pair<std::string, std::string> Project::Clang::get_run_arguments() { auto build_path=build->get_default_path(); if(build_path.empty()) return {"", ""}; - + auto project_path=build->get_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 view=Notebook::get().get_current_view(); auto executable=build->get_executable(view?view->file_path:Directories::get().path); - + if(!executable.empty()) arguments=filesystem::escape_argument(filesystem::get_short_path(executable).string()); else arguments=filesystem::escape_argument(filesystem::get_short_path(build->get_default_path()).string()); } - + return {project_path, arguments}; } @@ -791,12 +791,12 @@ void Project::Clang::compile() { auto default_build_path=build->get_default_path(); if(default_build_path.empty() || !build->update_default()) return; - + compiling=true; - + if(Config::get().project.clear_terminal_on_compile) Terminal::get().clear(); - + Terminal::get().print("Compiling project "+filesystem::get_short_path(build->get_project_path()).string()+"\n"); Terminal::get().async_process(build->get_compile_command(), default_build_path, [](int exit_status) { compiling=false; @@ -807,14 +807,14 @@ void Project::Clang::compile_and_run() { auto default_build_path=build->get_default_path(); if(default_build_path.empty() || !build->update_default()) return; - + auto project_path=build->get_project_path(); - + 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()) { auto view=Notebook::get().get_current_view(); auto executable=build->get_executable(view?view->file_path:Directories::get().path); @@ -825,12 +825,12 @@ void Project::Clang::compile_and_run() { } arguments=filesystem::escape_argument(filesystem::get_short_path(executable).string()); } - + compiling=true; - + if(Config::get().project.clear_terminal_on_compile) Terminal::get().clear(); - + Terminal::get().print("Compiling and running "+arguments+"\n"); Terminal::get().async_process(build->get_compile_command(), default_build_path, [arguments, project_path](int exit_status){ compiling=false; @@ -848,11 +848,11 @@ void Project::Clang::recreate_build() { auto default_build_path=build->get_default_path(); if(default_build_path.empty()) return; - + auto debug_build_path=build->get_debug_path(); bool has_default_build=boost::filesystem::exists(default_build_path); bool has_debug_build=!debug_build_path.empty() && boost::filesystem::exists(debug_build_path); - + if(has_default_build || has_debug_build) { Gtk::MessageDialog dialog(*static_cast<Gtk::Window*>(Notebook::get().get_toplevel()), "Recreate Build", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO); dialog.set_default_response(Gtk::RESPONSE_NO); @@ -879,11 +879,11 @@ void Project::Clang::recreate_build() { return; } } - + build->update_default(true); if(has_debug_build) build->update_debug(true); - + for(size_t c=0;c<Notebook::get().size();c++) { auto source_view=Notebook::get().get_view(c); if(auto source_clang_view=dynamic_cast<Source::ClangView*>(source_view)) { @@ -891,7 +891,7 @@ void Project::Clang::recreate_build() { source_clang_view->full_reparse_needed=true; } } - + if(auto view=Notebook::get().get_current_view()) { if(view->full_reparse_needed) view->full_reparse(); @@ -910,7 +910,7 @@ void Project::Markdown::compile_and_run() { 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, "command -v grip"); if(exit_status==0) { @@ -976,21 +976,21 @@ std::pair<std::string, std::string> Project::Rust::get_run_arguments() { std::string arguments; if(run_arguments_it!=run_arguments.end()) arguments=run_arguments_it->second; - + if(arguments.empty()) arguments=filesystem::get_short_path(build->get_executable(project_path)).string(); - + return {project_path, arguments}; } void Project::Rust::compile() { compiling=true; - + if(Config::get().project.clear_terminal_on_compile) Terminal::get().clear(); - + Terminal::get().print("Compiling project "+filesystem::get_short_path(build->get_project_path()).string()+"\n"); - + auto command=build->get_compile_command(); Terminal::get().async_process(command, build->get_project_path(), [](int exit_status) { compiling=false; @@ -999,13 +999,13 @@ void Project::Rust::compile() { void Project::Rust::compile_and_run() { compiling=true; - + if(Config::get().project.clear_terminal_on_compile) Terminal::get().clear(); - + auto arguments=get_run_arguments().second; Terminal::get().print("Compiling and running "+arguments+"\n"); - + auto self=this->shared_from_this(); Terminal::get().async_process(build->get_compile_command(), build->get_project_path(), [self, arguments=std::move(arguments)](int exit_status) { compiling=false; diff --git a/src/project.h b/src/project.h index ca09ac52..ee01bfaf 100644 --- a/src/project.h +++ b/src/project.h @@ -15,17 +15,17 @@ namespace Project { bool remote_enabled; std::string remote_host_port; }; - + class DebugOptions : public Gtk::Popover { public: DebugOptions() : Gtk::Popover(), vbox(Gtk::Orientation::ORIENTATION_VERTICAL) { add(vbox); } Gtk::Box vbox; }; - + Gtk::Label &debug_status_label(); void save_files(const boost::filesystem::path &path); void on_save(size_t index); - + extern boost::filesystem::path debug_last_stop_file_path; extern std::unordered_map<std::string, std::string> run_arguments; extern std::unordered_map<std::string, DebugRunArguments> debug_run_arguments; @@ -36,7 +36,7 @@ namespace Project { void debug_update_status(const std::string &new_debug_status); void debug_activate_menu_items(); void debug_update_stop(); - + class Base : public std::enable_shared_from_this<Base> { protected: static std::unique_ptr<DebugOptions> debug_options; @@ -44,18 +44,18 @@ namespace Project { Base() = default; Base(std::unique_ptr<Build> &&build): build(std::move(build)) {} virtual ~Base() = default; - + std::unique_ptr<Build> build; - + Dispatcher dispatcher; - + virtual std::pair<std::string, std::string> get_run_arguments(); virtual void compile(); virtual void compile_and_run(); virtual void recreate_build(); - + virtual void show_symbols(); - + virtual std::pair<std::string, std::string> debug_get_run_arguments(); virtual Project::DebugOptions *debug_get_options() { return nullptr; } Tooltips debug_variable_tooltips; @@ -75,11 +75,11 @@ namespace Project { virtual void debug_write(const std::string &buffer) {} virtual void debug_cancel() {} }; - + class LLDB : public virtual Base { public: ~LLDB() override { dispatcher.disconnect(); } - + #ifdef JUCI_ENABLE_DEBUG std::pair<std::string, std::string> debug_get_run_arguments() override; Project::DebugOptions *debug_get_options() override; @@ -100,68 +100,68 @@ namespace Project { void debug_cancel() override; #endif }; - + class LanguageProtocol : public virtual Base { public: virtual std::string get_language_id()=0; void show_symbols() override; }; - + class Clang : public LLDB { public: Clang(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} - + std::pair<std::string, std::string> get_run_arguments() override; void compile() override; void compile_and_run() override; - void recreate_build() override; + void recreate_build() override; }; - + class Markdown : public Base { public: Markdown(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} ~Markdown() override; - + boost::filesystem::path last_temp_path; void compile_and_run() override; }; - + class Python : public LanguageProtocol { public: Python(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} - + void compile_and_run() override; - + std::string get_language_id() override { return "python"; } }; - + class JavaScript : public LanguageProtocol { public: JavaScript(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} - + void compile_and_run() override; - + std::string get_language_id() override { return "javascript"; } }; - + class HTML : public Base { public: HTML(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} - + void compile_and_run() override; }; - + class Rust : public LLDB, public LanguageProtocol { public: Rust(std::unique_ptr<Build> &&build) : Base(std::move(build)) {} - + std::pair<std::string, std::string> get_run_arguments() override; void compile() override; void compile_and_run() override; - + std::string get_language_id() override { return "rust"; } }; - + std::shared_ptr<Base> create(); extern std::shared_ptr<Base> current; }; diff --git a/src/project_build.h b/src/project_build.h index 058ccd6e..43c23d6d 100644 --- a/src/project_build.h +++ b/src/project_build.h @@ -7,17 +7,17 @@ namespace Project { class Build { public: virtual ~Build() {} - + virtual boost::filesystem::path get_default_path(); virtual bool update_default(bool force=false) {return false;} virtual boost::filesystem::path get_debug_path(); virtual bool update_debug(bool force=false) {return false;} - + virtual std::string get_compile_command() { return std::string(); } virtual boost::filesystem::path get_executable(const boost::filesystem::path &path) {return boost::filesystem::path();} const boost::filesystem::path& get_project_path() const noexcept { return project_path; } - + static std::unique_ptr<Build> create(const boost::filesystem::path &path); protected: diff --git a/src/rapidxml/rapidxml.h b/src/rapidxml/rapidxml.h index 49e1b2aa..ee1b7d3b 100644 --- a/src/rapidxml/rapidxml.h +++ b/src/rapidxml/rapidxml.h @@ -277,7 +277,7 @@ namespace rapidxml { } else { memory = new char[size]; #ifdef RAPIDXML_NO_EXCEPTIONS - if (!memory) + if (!memory) RAPIDXML_PARSE_ERROR("out of memory", 0); #endif } diff --git a/src/selection_dialog.cc b/src/selection_dialog.cc index aed3bda8..72793657 100644 --- a/src/selection_dialog.cc +++ b/src/selection_dialog.cc @@ -9,7 +9,7 @@ SelectionDialogBase::ListViewText::ListViewText(bool use_markup) : Gtk::TreeView get_column(0)->add_attribute(cell_renderer.property_markup(), column_record.text); else get_column(0)->add_attribute(cell_renderer.property_text(), column_record.text); - + get_selection()->set_mode(Gtk::SelectionMode::SELECTION_BROWSE); set_enable_search(true); set_headers_visible(false); @@ -42,20 +42,20 @@ SelectionDialogBase::SelectionDialogBase(Gtk::TextView *text_view, const Glib::R auto gio_application=Glib::wrap(g_application, true); auto application=Glib::RefPtr<Gtk::Application>::cast_static(gio_application); window.set_transient_for(*application->get_active_window()); - + window.set_type_hint(Gdk::WindowTypeHint::WINDOW_TYPE_HINT_COMBO); - + search_entry.signal_changed().connect([this] { if(on_search_entry_changed) on_search_entry_changed(search_entry.get_text()); }, false); - + list_view_text.set_search_entry(search_entry); - + window.set_default_size(0, 0); window.property_decorated()=false; window.set_skip_taskbar_hint(true); - + scrolled_window.set_policy(Gtk::PolicyType::POLICY_AUTOMATIC, Gtk::PolicyType::POLICY_AUTOMATIC); scrolled_window.add(list_view_text); @@ -69,7 +69,7 @@ SelectionDialogBase::SelectionDialogBase(Gtk::TextView *text_view, const Glib::R auto gio_application=Glib::wrap(g_application, true); auto application=Glib::RefPtr<Gtk::Application>::cast_static(gio_application); auto application_window=application->get_active_window(); - + // Calculate window width and height int row_width=0, padding_height=0, window_height=0; Gdk::Rectangle rect; @@ -84,19 +84,19 @@ SelectionDialogBase::SelectionDialogBase(Gtk::TextView *text_view, const Glib::R window_height+=rect.get_height()+padding_height; ++c; } - + if(this->text_view && row_width>this->text_view->get_width()*2/3) row_width=this->text_view->get_width()*2/3; else if(row_width>application_window->get_width()/2) row_width=application_window->get_width()/2; else scrolled_window.set_policy(Gtk::PolicyType::POLICY_NEVER, Gtk::PolicyType::POLICY_AUTOMATIC); - + if(this->show_search_entry) window_height+=search_entry.get_height(); int window_width=row_width+1; window.resize(window_width, window_height); - + if(this->text_view) { Gdk::Rectangle iter_rect; this->text_view->get_iter_location(this->start_mark->get_iter(), iter_rect); @@ -118,7 +118,7 @@ SelectionDialogBase::SelectionDialogBase(Gtk::TextView *text_view, const Glib::R window.move(root_x, root_y); } }); - + list_view_text.signal_cursor_changed().connect([this] { cursor_changed(); }); @@ -158,7 +158,7 @@ void SelectionDialogBase::show() { window.show_all(); if(text_view) text_view->grab_focus(); - + if(list_view_text.get_model()->children().size()>0) { if(!list_view_text.get_selection()->get_selected()) { list_view_text.set_cursor(list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); @@ -198,7 +198,7 @@ std::unique_ptr<SelectionDialog> SelectionDialog::instance; SelectionDialog::SelectionDialog(Gtk::TextView *text_view, const Glib::RefPtr<Gtk::TextBuffer::Mark> &start_mark, bool show_search_entry, bool use_markup) : SelectionDialogBase(text_view, start_mark, show_search_entry, use_markup) { auto search_key=std::make_shared<std::string>(); auto filter_model=Gtk::TreeModelFilter::create(list_view_text.get_model()); - + filter_model->set_visible_func([this, search_key](const Gtk::TreeModel::const_iterator& iter){ std::string row_lc; iter->get_value(0, row_lc); @@ -217,13 +217,13 @@ SelectionDialog::SelectionDialog(Gtk::TextView *text_view, const Glib::RefPtr<Gt return true; return false; }); - + list_view_text.set_model(filter_model); - + list_view_text.set_search_equal_func([](const Glib::RefPtr<Gtk::TreeModel>& model, int column, const Glib::ustring& key, const Gtk::TreeModel::iterator& iter) { return false; }); - + search_entry.signal_changed().connect([this, search_key, filter_model](){ *search_key=search_entry.get_text(); filter_model->refilter(); @@ -233,7 +233,7 @@ SelectionDialog::SelectionDialog(Gtk::TextView *text_view, const Glib::RefPtr<Gt list_view_text.set_cursor(list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); } }); - + auto activate=[this](){ auto it=list_view_text.get_selection()->get_selected(); if(on_select && it) { @@ -324,9 +324,9 @@ std::unique_ptr<CompletionDialog> CompletionDialog::instance; CompletionDialog::CompletionDialog(Gtk::TextView *text_view, const Glib::RefPtr<Gtk::TextBuffer::Mark> &start_mark) : SelectionDialogBase(text_view, start_mark, false, false) { show_offset=text_view->get_buffer()->get_insert()->get_iter().get_offset(); - + auto search_key=std::make_shared<std::string>(); - auto filter_model=Gtk::TreeModelFilter::create(list_view_text.get_model()); + auto filter_model=Gtk::TreeModelFilter::create(list_view_text.get_model()); if(show_offset==start_mark->get_iter().get_offset()) { filter_model->set_visible_func([search_key](const Gtk::TreeModel::const_iterator& iter){ std::string row_lc; @@ -354,11 +354,11 @@ CompletionDialog::CompletionDialog(Gtk::TextView *text_view, const Glib::RefPtr< filter_model->refilter(); list_view_text.set_search_entry(search_entry); //TODO:Report the need of this to GTK's git (bug) }); - + list_view_text.signal_row_activated().connect([this](const Gtk::TreeModel::Path& path, Gtk::TreeViewColumn*) { select(); }); - + auto text=text_view->get_buffer()->get_text(start_mark->get_iter(), text_view->get_buffer()->get_insert()->get_iter()); if(text.size()>0) { search_entry.set_text(text); @@ -368,7 +368,7 @@ CompletionDialog::CompletionDialog(Gtk::TextView *text_view, const Glib::RefPtr< void CompletionDialog::select(bool hide_window) { row_in_entry=true; - + auto it=list_view_text.get_selection()->get_selected(); if(on_select && it) { auto index=it->get_value(list_view_text.column_record.index); @@ -382,7 +382,7 @@ void CompletionDialog::select(bool hide_window) { bool CompletionDialog::on_key_release(GdkEventKey* key) { if(key->keyval==GDK_KEY_Down || key->keyval==GDK_KEY_KP_Down || key->keyval==GDK_KEY_Up || key->keyval==GDK_KEY_KP_Up) return false; - + if(show_offset>text_view->get_buffer()->get_insert()->get_iter().get_offset()) { hide(); } @@ -400,7 +400,7 @@ bool CompletionDialog::on_key_release(GdkEventKey* key) { } bool CompletionDialog::on_key_press(GdkEventKey* key) { - if((key->keyval>=GDK_KEY_0 && key->keyval<=GDK_KEY_9) || + if((key->keyval>=GDK_KEY_0 && key->keyval<=GDK_KEY_9) || (key->keyval>=GDK_KEY_A && key->keyval<=GDK_KEY_Z) || (key->keyval>=GDK_KEY_a && key->keyval<=GDK_KEY_z) || key->keyval==GDK_KEY_underscore || key->keyval==GDK_KEY_BackSpace) { diff --git a/src/selection_dialog.h b/src/selection_dialog.h index cf61f1aa..9c8d432a 100644 --- a/src/selection_dialog.h +++ b/src/selection_dialog.h @@ -26,13 +26,13 @@ class SelectionDialogBase { Gtk::CellRendererText cell_renderer; unsigned int size=0; }; - + class SearchEntry : public Gtk::Entry { public: SearchEntry() : Gtk::Entry() {} bool on_key_press_event(GdkEventKey *event) override { return Gtk::Entry::on_key_press_event(event); }; }; - + public: SelectionDialogBase(Gtk::TextView *text_view, const Glib::RefPtr<Gtk::TextBuffer::Mark> &start_mark, bool show_search_entry, bool use_markup); virtual ~SelectionDialogBase(); @@ -41,20 +41,20 @@ class SelectionDialogBase { void set_cursor_at_last_row(); void show(); void hide(); - + bool is_visible() {return window.is_visible();} void get_position(int &root_x, int &root_y) {window.get_position(root_x, root_y);} - + std::function<void()> on_show; std::function<void()> on_hide; std::function<void(unsigned int index, const std::string &text, bool hide_window)> on_select; std::function<void(unsigned int index, const std::string &text)> on_changed; std::function<void(const std::string &text)> on_search_entry_changed; Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark; - + protected: void cursor_changed(); - + Gtk::TextView *text_view; Gtk::Window window; Gtk::Box vbox; @@ -62,7 +62,7 @@ class SelectionDialogBase { ListViewText list_view_text; SearchEntry search_entry; bool show_search_entry; - + unsigned int last_index=static_cast<unsigned int>(-1); }; @@ -71,7 +71,7 @@ class SelectionDialog : public SelectionDialogBase { static std::unique_ptr<SelectionDialog> instance; public: bool on_key_press(GdkEventKey* key); - + static void create(Gtk::TextView *text_view, const Glib::RefPtr<Gtk::TextBuffer::Mark> &start_mark, bool show_search_entry=true, bool use_markup=false) { instance=std::unique_ptr<SelectionDialog>(new SelectionDialog(text_view, start_mark, show_search_entry, use_markup)); } @@ -87,14 +87,14 @@ class CompletionDialog : public SelectionDialogBase { public: bool on_key_release(GdkEventKey* key); bool on_key_press(GdkEventKey* key); - + static void create(Gtk::TextView *text_view, const Glib::RefPtr<Gtk::TextBuffer::Mark> &start_mark) { instance=std::unique_ptr<CompletionDialog>(new CompletionDialog(text_view, start_mark)); } static std::unique_ptr<CompletionDialog> &get() {return instance;} private: void select(bool hide_window=true); - + int show_offset; bool row_in_entry=false; }; diff --git a/src/source.cc b/src/source.cc index 6e5d40bb..3b4ce963 100644 --- a/src/source.cc +++ b/src/source.cc @@ -87,7 +87,7 @@ std::string Source::FixIt::string(const Glib::RefPtr<Gtk::TextBuffer> &buffer) { unsigned first_line_offset=iter.get_line_offset()+1; iter=buffer->get_iter_at_line_index(offsets.second.line, offsets.second.index); unsigned second_line_offset=iter.get_line_offset()+1; - + std::string text; if(type==Type::INSERT) { text+="Insert "+source+" at "; @@ -104,7 +104,7 @@ std::string Source::FixIt::string(const Glib::RefPtr<Gtk::TextBuffer> &buffer) { text+=std::to_string(offsets.first.line+1)+":"+std::to_string(first_line_offset)+" - "; text+=std::to_string(offsets.second.line+1)+":"+std::to_string(second_line_offset); } - + return text; } @@ -117,7 +117,7 @@ std::unordered_set<Source::View*> Source::View::views; Source::View::View(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language, bool is_generic_view): BaseView(file_path, language), SpellCheckView(file_path, language), DiffView(file_path, language) { non_deleted_views.emplace(this); views.emplace(this); - + search_settings = gtk_source_search_settings_new(); gtk_source_search_settings_set_wrap_around(search_settings, true); search_context = gtk_source_search_context_new(get_source_buffer()->gobj(), search_settings); @@ -127,16 +127,16 @@ Source::View::View(const boost::filesystem::path &file_path, const Glib::RefPtr< //TODO: We can drop this, only work on newer versions of gtksourceview. //search_match_style=(GtkSourceStyle*)g_object_new(GTK_SOURCE_TYPE_STYLE, "background-set", 1, "background", "#00FF00", nullptr); //gtk_source_search_context_set_match_style(search_context, search_match_style); - + //TODO: either use lambda if possible or create a gtkmm wrapper around search_context (including search_settings): //TODO: (gtkmm's Gtk::Object has connect_property_changed, so subclassing this might be an idea) g_signal_connect(search_context, "notify::occurrences-count", G_CALLBACK(search_occurrences_updated), this); - + get_buffer()->create_tag("def:warning"); get_buffer()->create_tag("def:warning_underline"); get_buffer()->create_tag("def:error"); get_buffer()->create_tag("def:error_underline"); - + auto mark_attr_debug_breakpoint=Gsv::MarkAttributes::create(); Gdk::RGBA rgba; rgba.set_red(1.0); @@ -157,12 +157,12 @@ Source::View::View(const boost::filesystem::path &file_path, const Glib::RefPtr< rgba.set_blue(0.75); mark_attr_debug_breakpoint_and_stop->set_background(rgba); set_mark_attributes("debug_breakpoint_and_stop", mark_attr_debug_breakpoint_and_stop, 102); - + get_buffer()->signal_changed().connect([this](){ if(update_status_location) update_status_location(this); }); - + signal_realize().connect([this] { auto gutter=get_gutter(Gtk::TextWindowType::TEXT_WINDOW_LEFT); auto renderer=gutter->get_renderer_at_pos(15, 0); @@ -174,7 +174,7 @@ Source::View::View(const boost::filesystem::path &file_path, const Glib::RefPtr< }); } }); - + if(language && (language->get_id()=="chdr" || language->get_id()=="cpphdr" || language->get_id()=="c" || language->get_id()=="cpp" || language->get_id()=="objc" || language->get_id()=="java" || language->get_id()=="js" || language->get_id()=="ts" || language->get_id()=="proto" || @@ -183,10 +183,10 @@ Source::View::View(const boost::filesystem::path &file_path, const Glib::RefPtr< language->get_id()=="go" || language->get_id()=="scala" || language->get_id()=="opencl" || language->get_id()=="json" || language->get_id()=="css")) is_bracket_language=true; - + setup_tooltip_and_dialog_events(); setup_format_style(is_generic_view); - + #ifndef __APPLE__ set_tab_width(4); //Visual size of a \t hardcoded to be equal to visual size of 4 spaces. Buggy on OS X #endif @@ -202,13 +202,13 @@ Source::View::View(const boost::filesystem::path &file_path, const Glib::RefPtr< else tab_str="<tab>"; } - + tab_char=tab_char_and_size.first; tab_size=tab_char_and_size.second; } } set_tab_char_and_size(tab_char, tab_size); - + std::string comment_characters; if(is_bracket_language) comment_characters="//"; @@ -316,7 +316,7 @@ Source::View::View(const boost::filesystem::path &file_path, const Glib::RefPtr< void Source::View::set_tab_char_and_size(char tab_char, unsigned tab_size) { this->tab_char=tab_char; this->tab_size=tab_size; - + tab.clear(); for(unsigned c=0;c<tab_size;c++) tab+=tab_char; @@ -347,7 +347,7 @@ void Source::View::cleanup_whitespace_characters() { Gsv::DrawSpacesFlags Source::View::parse_show_whitespace_characters(const std::string &text) { namespace qi = boost::spirit::qi; - + qi::symbols<char, Gsv::DrawSpacesFlags> options; options.add ("space", Gsv::DRAW_SPACES_SPACE) @@ -360,10 +360,10 @@ Gsv::DrawSpacesFlags Source::View::parse_show_whitespace_characters(const std::s ("all", Gsv::DRAW_SPACES_ALL); std::set<Gsv::DrawSpacesFlags> out; - + // parse comma-separated list of options qi::phrase_parse(text.begin(), text.end(), options % ',', qi::space, out); - + return out.count(Gsv::DRAW_SPACES_ALL)>0 ? Gsv::DRAW_SPACES_ALL : static_cast<Gsv::DrawSpacesFlags>(std::accumulate(out.begin(), out.end(), 0)); @@ -374,7 +374,7 @@ bool Source::View::save() { return false; if(Config::get().source.cleanup_whitespace_characters) cleanup_whitespace_characters(); - + if(format_style) { if(Config::get().source.format_style_on_save) format_style(true); @@ -382,7 +382,7 @@ bool Source::View::save() { format_style(false); hide_tooltips(); } - + std::ofstream output(file_path.string(), std::ofstream::binary); if(output) { auto start_iter=get_buffer()->begin(); @@ -418,15 +418,15 @@ bool Source::View::save() { void Source::View::configure() { SpellCheckView::configure(); DiffView::configure(); - + if(Config::get().source.style.size()>0) { auto scheme = StyleSchemeManager::get_default()->get_scheme(Config::get().source.style); if(scheme) get_source_buffer()->set_style_scheme(scheme); } - + set_draw_spaces(parse_show_whitespace_characters(Config::get().source.show_whitespace_characters)); - + if(Config::get().source.wrap_lines) set_wrap_mode(Gtk::WrapMode::WRAP_CHAR); else @@ -441,7 +441,7 @@ void Source::View::configure() { gtk_source_view_set_background_pattern(this->gobj(), GTK_SOURCE_BACKGROUND_PATTERN_TYPE_NONE); property_show_right_margin() = Config::get().source.show_right_margin; property_right_margin_position() = Config::get().source.right_margin_position; - + //Create tags for diagnostic warnings and errors: auto scheme = get_source_buffer()->get_style_scheme(); auto tag_table=get_buffer()->get_tag_table(); @@ -456,7 +456,7 @@ void Source::View::configure() { } else if(style->property_background_set()) warning_property=style->property_background().get_value(); - + diagnostic_tag_underline->property_underline()=Pango::Underline::UNDERLINE_ERROR; auto tag_class=G_OBJECT_GET_CLASS(diagnostic_tag_underline->gobj()); //For older GTK+ 3 versions: auto param_spec=g_object_class_find_property(tag_class, "underline-rgba"); @@ -475,12 +475,12 @@ void Source::View::configure() { } else if(style->property_background_set()) error_property=style->property_background().get_value(); - + diagnostic_tag_underline->property_underline()=Pango::Underline::UNDERLINE_ERROR; diagnostic_tag_underline->set_property("underline-rgba", Gdk::RGBA(error_property)); } //TODO: clear tag_class and param_spec? - + if(Config::get().menu.keys["source_show_completion"].empty()) { get_completion()->unblock_interactive(); interactive_completion=true; @@ -498,11 +498,11 @@ void Source::View::setup_tooltip_and_dialog_events() { diagnostic_tooltips.on_motion=[this] { delayed_tooltips_connection.disconnect(); }; - + get_buffer()->signal_changed().connect([this] { hide_tooltips(); }); - + signal_motion_notify_event().connect([this](GdkEventMotion* event) { if(on_motion_last_x!=event->x || on_motion_last_y!=event->y) { delayed_tooltips_connection.disconnect(); @@ -530,11 +530,11 @@ void Source::View::setup_tooltip_and_dialog_events() { on_motion_last_y=event->y; return false; }); - + get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator& iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark>& mark) { if(get_buffer()->get_has_selection() && mark->get_name()=="selection_bound") delayed_tooltips_connection.disconnect(); - + if(mark->get_name()=="insert") { hide_tooltips(); delayed_tooltips_connection=Glib::signal_timeout().connect([this]() { @@ -552,12 +552,12 @@ void Source::View::setup_tooltip_and_dialog_events() { } return false; }, 500); - + if(SelectionDialog::get()) SelectionDialog::get()->hide(); if(CompletionDialog::get()) CompletionDialog::get()->hide(); - + if(update_status_location) update_status_location(this); } @@ -568,12 +568,12 @@ void Source::View::setup_tooltip_and_dialog_events() { hide_dialogs(); return false; }); - + signal_focus_out_event().connect([this](GdkEventFocus* event) { hide_tooltips(); return false; }); - + signal_leave_notify_event().connect([this](GdkEventCrossing*) { delayed_tooltips_connection.disconnect(); return false; @@ -607,9 +607,9 @@ void Source::View::setup_format_style(bool is_generic_view) { else return; } - + command+=" --stdin-filepath "+this->file_path.string()+" --print-width 120 --config-precedence prefer-file"; - + if(get_buffer()->get_has_selection()) { // Cannot be used together with --cursor-offset Gtk::TextIter start, end; get_buffer()->get_selection_bounds(start, end); @@ -618,11 +618,11 @@ void Source::View::setup_format_style(bool is_generic_view) { } else command+=" --cursor-offset "+std::to_string(get_buffer()->get_insert()->get_iter().get_offset()); - + size_t num_warnings=0, num_errors=0, num_fix_its=0; if(is_generic_view) clear_diagnostic_tooltips(); - + std::stringstream stdin_stream(get_buffer()->get_text()), stdout_stream, stderr_stream; auto exit_status=Terminal::get().process(stdin_stream, stdout_stream, command, this->file_path.parent_path(), &stderr_stream); if(exit_status==0) { @@ -655,7 +655,7 @@ void Source::View::setup_format_style(bool is_generic_view) { end.forward_char(); if(start==end) start.forward_char(); - + add_diagnostic_tooltip(start, end, sm[1].str(), true); } catch(...) {} @@ -671,15 +671,15 @@ void Source::View::setup_format_style(bool is_generic_view) { else if(is_bracket_language) { format_style=[this](bool continue_without_style_file) { static auto clang_format_command = filesystem::get_executable("clang-format").string(); - + auto command=clang_format_command+" -output-replacements-xml -assume-filename="+filesystem::escape_argument(this->file_path.string()); - + if(get_buffer()->get_has_selection()) { Gtk::TextIter start, end; get_buffer()->get_selection_bounds(start, end); command+=" -lines="+std::to_string(start.get_line()+1)+':'+std::to_string(end.get_line()+1); } - + bool use_style_file=false; auto style_file_search_path=this->file_path.parent_path(); while(true) { @@ -691,7 +691,7 @@ void Source::View::setup_format_style(bool is_generic_view) { break; style_file_search_path=style_file_search_path.parent_path(); } - + if(use_style_file) command+=" -style=file"; else { @@ -714,13 +714,13 @@ void Source::View::setup_format_style(bool is_generic_view) { command+=", "+Config::get().source.clang_format_style; command+="}\""; } - + std::stringstream stdin_stream(get_buffer()->get_text()), stdout_stream; - + auto exit_status=Terminal::get().process(stdin_stream, stdout_stream, command, this->file_path.parent_path()); if(exit_status==0) { // The following code is complex due to clang-format returning offsets in byte offsets instead of char offsets - + // Create bytes_in_lines cache to significantly speed up the processing of finding iterators from byte offsets std::vector<size_t> bytes_in_lines; auto line_count=get_buffer()->get_line_count(); @@ -728,7 +728,7 @@ void Source::View::setup_format_style(bool is_generic_view) { auto iter=get_buffer()->get_iter_at_line(line_nr); bytes_in_lines.emplace_back(iter.get_bytes_in_line()); } - + get_buffer()->begin_user_action(); try { boost::property_tree::ptree pt; @@ -739,7 +739,7 @@ void Source::View::setup_format_style(bool is_generic_view) { auto offset=it->second.get<size_t>("<xmlattr>.offset"); auto length=it->second.get<size_t>("<xmlattr>.length"); auto replacement_str=it->second.get<std::string>(""); - + size_t bytes=0; for(size_t c=0;c<bytes_in_lines.size();++c) { auto previous_bytes=bytes; @@ -747,7 +747,7 @@ void Source::View::setup_format_style(bool is_generic_view) { if(offset<bytes || (c==bytes_in_lines.size()-1 && offset==bytes)) { std::pair<size_t, size_t> line_index(c, offset-previous_bytes); auto start=get_buffer()->get_iter_at_line_index(line_index.first, line_index.second); - + // Use left gravity insert to avoid moving cursor from end of line bool left_gravity_insert=false; if(get_buffer()->get_insert()->get_iter()==start) { @@ -759,7 +759,7 @@ void Source::View::setup_format_style(bool is_generic_view) { } } while(iter.forward_char()); } - + if(length>0) { auto offset_end=offset+length; size_t bytes=0; @@ -811,22 +811,22 @@ void Source::View::setup_format_style(bool is_generic_view) { } if(!has_style_file && !continue_without_style_file) return; - + auto special_character=[](Gtk::TextIter iter) { if(*iter=='*' || *iter=='#' || *iter=='<' || *iter=='>' || *iter==' ' || *iter=='=' || *iter=='`' || *iter=='-') return true; - // Tests if a line starts with for instance: 2. + // Tests if a line starts with for instance: 2. if(*iter>='0' && *iter<='9' && iter.forward_char() && *iter=='.' && iter.forward_char() && *iter==' ') return true; return false; }; - + get_buffer()->begin_user_action(); disable_spellcheck=true; cleanup_whitespace_characters(); - + auto iter=get_buffer()->begin(); size_t last_space_offset=-1; bool headline=false; @@ -919,11 +919,11 @@ void Source::View::search_occurrences_updated(GtkWidget* widget, GParamSpec* pro Source::View::~View() { g_clear_object(&search_context); g_clear_object(&search_settings); - + delayed_tooltips_connection.disconnect(); delayed_tag_similar_symbols_connection.disconnect(); renderer_activate_connection.disconnect(); - + non_deleted_views.erase(this); views.erase(this); } @@ -984,7 +984,7 @@ void Source::View::replace_forward(const std::string &replacement) { #else gtk_source_search_context_replace(search_context, match_start.gobj(), match_end.gobj(), replacement.c_str(), replacement.size(), nullptr); #endif - + Glib::ustring replacement_ustring=replacement; get_buffer()->select_range(get_buffer()->get_iter_at_offset(offset), get_buffer()->get_iter_at_offset(offset+replacement_ustring.size())); scroll_to(get_buffer()->get_insert()); @@ -1026,9 +1026,9 @@ void Source::View::paste() { ~Guard() { value = false; } }; Guard guard{multiple_cursors_use}; - + std::string text=Gtk::Clipboard::get()->wait_for_text(); - + //Replace carriage returns (which leads to crash) with newlines for(size_t c=0;c<text.size();c++) { if(text[c]=='\r') { @@ -1038,7 +1038,7 @@ void Source::View::paste() { text.replace(c, 1, "\n"); } } - + //Exception for when pasted text is only whitespaces bool only_whitespaces=true; for(auto &chr: text) { @@ -1053,7 +1053,7 @@ void Source::View::paste() { scroll_to_cursor_delayed(this, false, false); return; } - + get_buffer()->begin_user_action(); if(get_buffer()->get_has_selection()) { Gtk::TextIter start, end; @@ -1133,7 +1133,7 @@ void Source::View::paste() { if(!(first_paste_line && !first_paste_line_has_tabs) && line_tabs<paste_line_tabs) { tabs=line_tabs; } - + if(first_paste_line) { if(first_paste_line_has_tabs) get_buffer()->insert_at_cursor(text.substr(start_line+tabs, end_line-start_line-tabs)); @@ -1164,9 +1164,9 @@ void Source::View::hide_tooltips() { void Source::View::add_diagnostic_tooltip(const Gtk::TextIter &start, const Gtk::TextIter &end, std::string spelling, bool error) { diagnostic_offsets.emplace(start.get_offset()); - + std::string severity_tag_name = error ? "def:error" : "def:warning"; - + auto create_tooltip_buffer=[this, spelling=std::move(spelling), error, severity_tag_name]() { auto tooltip_buffer=Gtk::TextBuffer::create(get_buffer()->get_tag_table()); tooltip_buffer->insert_with_tag(tooltip_buffer->get_insert()->get_iter(), error ? "Error" : "Warning", severity_tag_name); @@ -1174,9 +1174,9 @@ void Source::View::add_diagnostic_tooltip(const Gtk::TextIter &start, const Gtk: return tooltip_buffer; }; diagnostic_tooltips.emplace_back(create_tooltip_buffer, this, get_buffer()->create_mark(start), get_buffer()->create_mark(end)); - + get_buffer()->apply_tag_by_name(severity_tag_name+"_underline", start, end); - + auto iter=get_buffer()->get_insert()->get_iter(); if(iter.ends_line()) { auto next_iter=iter; @@ -1204,7 +1204,7 @@ bool Source::View::find_open_non_curly_bracket_backward(Gtk::TextIter iter, Gtk: long para_count=0; long square_count=0; long curly_count=0; - + do { if(*iter=='(' && is_code_iter(iter)) para_count++; @@ -1218,10 +1218,10 @@ bool Source::View::find_open_non_curly_bracket_backward(Gtk::TextIter iter, Gtk: curly_count++; else if(*iter=='}' && is_code_iter(iter)) curly_count--; - + if(curly_count>0) break; - + if(para_count>0 || square_count>0) { found_iter=iter; return true; @@ -1233,21 +1233,21 @@ bool Source::View::find_open_non_curly_bracket_backward(Gtk::TextIter iter, Gtk: Gtk::TextIter Source::View::find_start_of_sentence(Gtk::TextIter iter) { if(iter.starts_line()) return iter; - + bool stream_operator_test=false; bool colon_test=false; - + if(*iter==';') stream_operator_test=true; if(*iter=='{') { iter.backward_char(); colon_test=true; } - + int para_count=0; int square_count=0; long curly_count=0; - + do { if(*iter=='(' && is_code_iter(iter)) para_count++; @@ -1261,10 +1261,10 @@ Gtk::TextIter Source::View::find_start_of_sentence(Gtk::TextIter iter) { curly_count++; else if(*iter=='}' && is_code_iter(iter)) curly_count--; - + if(curly_count>0) break; - + if(iter.starts_line() && para_count==0 && square_count==0) { bool stream_operator_found=false; bool colon_found=false; @@ -1302,13 +1302,13 @@ Gtk::TextIter Source::View::find_start_of_sentence(Gtk::TextIter iter) { } } } while(iter.backward_char()); - + return iter; } bool Source::View::find_open_curly_bracket_backward(Gtk::TextIter iter, Gtk::TextIter &found_iter) { long count=0; - + do { if(*iter=='{') { if(count==0 && is_code_iter(iter)) { @@ -1325,7 +1325,7 @@ bool Source::View::find_open_curly_bracket_backward(Gtk::TextIter iter, Gtk::Tex bool Source::View::find_close_curly_bracket_forward(Gtk::TextIter iter, Gtk::TextIter &found_iter) { long count=0; - + do { if(*iter=='}' && is_code_iter(iter)) { if(count==0) { @@ -1350,7 +1350,7 @@ long Source::View::symbol_count(Gtk::TextIter iter, unsigned int positive_char, bool check_if_next_iter_is_code_iter=false; if(positive_char=='\'' || negative_char=='\'' || positive_char=='"' || negative_char=='"') check_if_next_iter_is_code_iter=true; - + Gtk::TextIter previous_iter; do { if(*iter==positive_char && is_code_iter(iter)) @@ -1369,16 +1369,16 @@ long Source::View::symbol_count(Gtk::TextIter iter, unsigned int positive_char, else if(*iter==negative_char && is_code_iter(next_iter)) symbol_count--; } - + if(break_on_curly && curly_count>0) break; } while(iter.backward_char()); - + iter=iter_stored; if(!iter.forward_char()) { return symbol_count; } - + curly_count=0; do { if(*iter==positive_char && is_code_iter(iter)) @@ -1397,11 +1397,11 @@ long Source::View::symbol_count(Gtk::TextIter iter, unsigned int positive_char, else if(*iter==negative_char && is_code_iter(next_iter)) symbol_count--; } - + if(break_on_curly && curly_count<0) break; } while(iter.forward_char()); - + return symbol_count; } @@ -1409,10 +1409,10 @@ bool Source::View::is_templated_function(Gtk::TextIter iter, Gtk::TextIter &pare auto iter_stored=iter; long bracket_count=0; long curly_count=0; - + if(!(iter.backward_char() && *iter=='>' && *iter_stored=='(')) return false; - + do { if(*iter=='<' && is_code_iter(iter)) bracket_count++; @@ -1422,17 +1422,17 @@ bool Source::View::is_templated_function(Gtk::TextIter iter, Gtk::TextIter &pare curly_count++; else if(*iter=='}' && is_code_iter(iter)) curly_count--; - + if(bracket_count==0) break; - + if(curly_count>0) break; } while(iter.backward_char()); - + if(bracket_count!=0) return false; - + iter=iter_stored; bracket_count=0; curly_count=0; @@ -1445,23 +1445,23 @@ bool Source::View::is_templated_function(Gtk::TextIter iter, Gtk::TextIter &pare curly_count++; else if(*iter=='}' && is_code_iter(iter)) curly_count--; - + if(bracket_count==0) { parenthesis_end_iter=iter; return true; } - + if(curly_count<0) return false; } while(iter.forward_char()); - + return false; } std::string Source::View::get_token(Gtk::TextIter iter) { auto start=iter; auto end=iter; - + while((*iter>='A' && *iter<='Z') || (*iter>='a' && *iter<='z') || (*iter>='0' && *iter<='9') || *iter=='_') { start=iter; if(!iter.backward_char()) @@ -1471,7 +1471,7 @@ std::string Source::View::get_token(Gtk::TextIter iter) { if(!end.forward_char()) break; } - + return get_buffer()->get_text(start, end); } @@ -1502,7 +1502,7 @@ bool Source::View::on_key_press_event(GdkEventKey* key) { ~Guard() { value = false; } }; Guard guard{multiple_cursors_use}; - + if(SelectionDialog::get() && SelectionDialog::get()->is_visible()) { if(SelectionDialog::get()->on_key_press(key)) return true; @@ -1511,14 +1511,14 @@ bool Source::View::on_key_press_event(GdkEventKey* key) { if(CompletionDialog::get()->on_key_press(key)) return true; } - + if(last_keyval<GDK_KEY_Shift_L || last_keyval>GDK_KEY_Hyper_R) previous_non_modifier_keyval=last_keyval; last_keyval=key->keyval; - + if(Config::get().source.enable_multiple_cursors && on_key_press_event_multiple_cursors(key)) return true; - + //Move cursor one paragraph down if((key->keyval==GDK_KEY_Down || key->keyval==GDK_KEY_KP_Down) && (key->state&GDK_CONTROL_MASK)>0) { auto selection_start_iter=get_buffer()->get_selection_bound()->get_iter(); @@ -1586,7 +1586,7 @@ bool Source::View::on_key_press_event(GdkEventKey* key) { } get_buffer()->begin_user_action(); - + // Shift+enter: go to end of line and enter if((key->keyval==GDK_KEY_Return || key->keyval==GDK_KEY_KP_Enter) && (key->state&GDK_SHIFT_MASK)>0) { auto iter=get_buffer()->get_insert()->get_iter(); @@ -1595,7 +1595,7 @@ bool Source::View::on_key_press_event(GdkEventKey* key) { get_buffer()->place_cursor(iter); } } - + if(Config::get().source.smart_brackets && on_key_press_event_smart_brackets(key)) { get_buffer()->end_user_action(); return true; @@ -1604,7 +1604,7 @@ bool Source::View::on_key_press_event(GdkEventKey* key) { get_buffer()->end_user_action(); return true; } - + if(is_bracket_language && on_key_press_event_bracket_language(key)) { get_buffer()->end_user_action(); return true; @@ -1626,10 +1626,10 @@ bool Source::View::on_key_press_event_basic(GdkEventKey* key) { //Indent as in next or previous line if((key->keyval==GDK_KEY_Return || key->keyval==GDK_KEY_KP_Enter) && !get_buffer()->get_has_selection() && !iter.starts_line()) { cleanup_whitespace_characters_on_return(iter); - + iter=get_buffer()->get_insert()->get_iter(); auto tabs=get_line_before(get_tabs_end_iter(iter)); - + auto previous_iter=iter; if(previous_iter.backward_char() && *previous_iter==':' && language && language->get_id()=="python") // Python indenting after : tabs+=tab; @@ -1665,7 +1665,7 @@ bool Source::View::on_key_press_event_basic(GdkEventKey* key) { while(next_line_iter.starts_line() && next_line_iter.forward_char()) {} auto next_line_tabs_end_iter=get_tabs_end_iter(next_line_iter); auto next_line_tabs=get_line_before(next_line_tabs_end_iter); - + std::string tabs; if(previous_line_tabs.size()<next_line_tabs.size()) tabs=previous_line_tabs; @@ -1676,7 +1676,7 @@ bool Source::View::on_key_press_event_basic(GdkEventKey* key) { return true; } } - + Gtk::TextIter selection_start, selection_end; get_buffer()->get_selection_bounds(selection_start, selection_end); auto selection_end_mark=get_buffer()->create_mark(selection_end); @@ -1696,18 +1696,18 @@ bool Source::View::on_key_press_event_basic(GdkEventKey* key) { get_buffer()->get_selection_bounds(selection_start, selection_end); int line_start=selection_start.get_line(); int line_end=selection_end.get_line(); - + unsigned indent_left_steps=tab_size; std::vector<bool> ignore_line; for(int line_nr=line_start;line_nr<=line_end;line_nr++) { auto line_it = get_buffer()->get_iter_at_line(line_nr); - if(!get_buffer()->get_has_selection() || line_it!=selection_end) { + if(!get_buffer()->get_has_selection() || line_it!=selection_end) { auto tabs_end_iter=get_tabs_end_iter(line_nr); if(tabs_end_iter.starts_line() && tabs_end_iter.ends_line()) ignore_line.push_back(true); else { auto line_tabs=get_line_before(tabs_end_iter); - + if(line_tabs.size()>0) { indent_left_steps=std::min(indent_left_steps, static_cast<unsigned>(line_tabs.size())); ignore_line.push_back(false); @@ -1717,7 +1717,7 @@ bool Source::View::on_key_press_event_basic(GdkEventKey* key) { } } } - + for(int line_nr=line_start;line_nr<=line_end;line_nr++) { Gtk::TextIter line_it = get_buffer()->get_iter_at_line(line_nr); Gtk::TextIter line_plus_it=line_it; @@ -1811,11 +1811,11 @@ bool Source::View::on_key_press_event_basic(GdkEventKey* key) { } get_buffer()->insert_at_cursor(Glib::ustring(1, unicode)); scroll_to(get_buffer()->get_insert()); - + //Trick to make the cursor visible right after insertion: set_cursor_visible(false); set_cursor_visible(); - + return true; } @@ -1832,14 +1832,14 @@ bool Source::View::on_key_press_event_bracket_language(GdkEventKey* key) { if(get_buffer()->get_has_selection()) return false; - + if(!is_code_iter(iter)) { // Add * at start of line in comment blocks if(key->keyval==GDK_KEY_Return || key->keyval==GDK_KEY_KP_Enter) { if(!iter.starts_line() && (!string_tag || (!iter.has_tag(string_tag) && !iter.ends_tag(string_tag)))) { cleanup_whitespace_characters_on_return(iter); iter=get_buffer()->get_insert()->get_iter(); - + auto start_iter=get_tabs_end_iter(iter.get_line()); auto end_iter=start_iter; end_iter.forward_chars(2); @@ -1851,7 +1851,7 @@ bool Source::View::on_key_press_event_bracket_language(GdkEventKey* key) { if(start_of_sentence[0]=='/') insert_str+=' '; insert_str+="* "; - + get_buffer()->insert_at_cursor(insert_str); return true; } @@ -1863,7 +1863,7 @@ bool Source::View::on_key_press_event_bracket_language(GdkEventKey* key) { else return false; } - + // get iter for if expressions below, which is moved backwards past any comment auto get_condition_iter=[this](const Gtk::TextIter &iter) { auto condition_iter=iter; @@ -1880,12 +1880,12 @@ bool Source::View::on_key_press_event_bracket_language(GdkEventKey* key) { condition_iter.backward_char()) {} return condition_iter; }; - + //Indent depending on if/else/etc and brackets if((key->keyval==GDK_KEY_Return || key->keyval==GDK_KEY_KP_Enter) && !iter.starts_line()) { cleanup_whitespace_characters_on_return(iter); iter=get_buffer()->get_insert()->get_iter(); - + auto condition_iter=get_condition_iter(iter); auto start_iter=condition_iter; if(*start_iter=='{') @@ -1899,7 +1899,7 @@ bool Source::View::on_key_press_event_bracket_language(GdkEventKey* key) { else start_iter=get_tabs_end_iter(get_buffer()->get_iter_at_line(find_start_of_sentence(condition_iter).get_line())); auto tabs=get_line_before(start_iter); - + /* * Change tabs after ending comment block with an extra space (as in this case) */ @@ -1914,7 +1914,7 @@ bool Source::View::on_key_press_event_bracket_language(GdkEventKey* key) { return true; } } - + if(*condition_iter=='{' && is_code_iter(condition_iter)) { Gtk::TextIter found_iter; // Check if an '}' is needed @@ -1929,7 +1929,7 @@ bool Source::View::on_key_press_event_bracket_language(GdkEventKey* key) { // special case for functions and classes with no indentation after: namespace { if(tabs.empty() && has_right_curly_bracket) has_right_curly_bracket=symbol_count(iter, '{', '}')!=1; - + if(*get_buffer()->get_insert()->get_iter()=='}') { get_buffer()->insert_at_cursor("\n"+tabs+tab+"\n"+tabs); auto insert_it = get_buffer()->get_insert()->get_iter(); @@ -1972,7 +1972,7 @@ bool Source::View::on_key_press_event_bracket_language(GdkEventKey* key) { --para_count; else if(*it=='(' && is_code_iter(it)) ++para_count; - + if(square_outside_para_found && square_count==0 && para_count==0) { add_semicolon=true; break; @@ -2005,7 +2005,7 @@ bool Source::View::on_key_press_event_bracket_language(GdkEventKey* key) { return true; } } - + //Indent multiline expressions if(open_non_curly_bracket_iter_found) { auto tabs_end_iter=get_tabs_end_iter(open_non_curly_bracket_iter); @@ -2141,14 +2141,14 @@ bool Source::View::on_key_press_event_bracket_language(GdkEventKey* key) { } } } - + return false; } bool Source::View::on_key_press_event_smart_brackets(GdkEventKey *key) { if(get_buffer()->get_has_selection()) return false; - + auto iter=get_buffer()->get_insert()->get_iter(); auto previous_iter=iter; previous_iter.backward_char(); @@ -2189,7 +2189,7 @@ bool Source::View::on_key_press_event_smart_brackets(GdkEventKey *key) { } } } - + return false; } @@ -2271,19 +2271,19 @@ bool Source::View::on_key_press_event_smart_inserts(GdkEventKey *key) { } return false; } - + auto iter=get_buffer()->get_insert()->get_iter(); auto previous_iter=iter; previous_iter.backward_char(); auto next_iter=iter; next_iter.forward_char(); - + auto allow_insertion=[](const Gtk::TextIter &iter) { if(iter.ends_line() || *iter==' ' || *iter=='\t' || *iter==';' || *iter==')' || *iter==']' || *iter=='[' || *iter=='{' || *iter=='}') return true; return false; }; - + // Move right when clicking ' before a ' or when clicking " before a " if(((key->keyval==GDK_KEY_apostrophe && *iter=='\'') || (key->keyval==GDK_KEY_quotedbl && *iter=='\"')) && is_code_iter(next_iter)) { @@ -2314,7 +2314,7 @@ bool Source::View::on_key_press_event_smart_inserts(GdkEventKey *key) { return true; } } - + if(is_code_iter(iter)) { // Insert () if(key->keyval==GDK_KEY_parenleft && allow_insertion(iter)) { @@ -2405,7 +2405,7 @@ bool Source::View::on_key_press_event_smart_inserts(GdkEventKey *key) { } } } - + return false; } @@ -2420,7 +2420,7 @@ bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) { break; } } - + if(mark->get_name()=="insert") { if(multiple_cursors_use) { multiple_cursors_use=false; @@ -2438,7 +2438,7 @@ bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) { multiple_cursors_last_insert=get_buffer()->create_mark(mark->get_iter(), false); } }); - + // TODO: this handler should run after signal_insert get_buffer()->signal_insert().connect([this](const Gtk::TextBuffer::iterator &iter, const Glib::ustring &text, int bytes) { if(multiple_cursors_use) { @@ -2452,7 +2452,7 @@ bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) { multiple_cursors_use=true; } }); - + get_buffer()->signal_erase().connect([this](const Gtk::TextBuffer::iterator &iter_start, const Gtk::TextBuffer::iterator &iter_end) { if(multiple_cursors_use) { auto insert_offset=get_buffer()->get_insert()->get_iter().get_offset(); @@ -2460,7 +2460,7 @@ bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) { multiple_cursors_erase_forward_length=iter_end.get_offset()-insert_offset; } }, false); - + get_buffer()->signal_erase().connect([this](const Gtk::TextBuffer::iterator &iter_start, const Gtk::TextBuffer::iterator &iter_end) { if(multiple_cursors_use) { multiple_cursors_use=false; @@ -2475,8 +2475,8 @@ bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) { } }); } - - + + if(key->keyval==GDK_KEY_Escape && !multiple_cursors_extra_cursors.empty()) { for(auto &extra_cursor: multiple_cursors_extra_cursors) { extra_cursor.first->set_visible(false); @@ -2485,10 +2485,10 @@ bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) { multiple_cursors_extra_cursors.clear(); return true; } - + unsigned create_cursor_mask=GDK_MOD1_MASK; unsigned move_last_created_cursor_mask=GDK_SHIFT_MASK|GDK_MOD1_MASK; - + // Move last created cursor if((key->keyval==GDK_KEY_Left || key->keyval==GDK_KEY_KP_Left) && (key->state&move_last_created_cursor_mask)==move_last_created_cursor_mask) { if(multiple_cursors_extra_cursors.empty()) @@ -2538,7 +2538,7 @@ bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) { } return true; } - + // Create extra cursor if((key->keyval==GDK_KEY_Up || key->keyval==GDK_KEY_KP_Up) && (key->state&create_cursor_mask)==create_cursor_mask) { auto insert_iter=get_buffer()->get_insert()->get_iter(); @@ -2574,7 +2574,7 @@ bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) { } return true; } - + // Move cursors left/right if((key->keyval==GDK_KEY_Left || key->keyval==GDK_KEY_KP_Left) && (key->state&GDK_CONTROL_MASK)>0) { multiple_cursors_use=false; @@ -2608,7 +2608,7 @@ bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) { get_buffer()->move_mark_by_name("selection_bound", iter); return true; } - + // Move cursors up/down if((key->keyval==GDK_KEY_Up || key->keyval==GDK_KEY_KP_Up)) { multiple_cursors_use=false; @@ -2640,7 +2640,7 @@ bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) { } return false; } - + // Smart Home-key, start of line if((key->keyval==GDK_KEY_Home || key->keyval==GDK_KEY_KP_Home) && (key->state&GDK_CONTROL_MASK)==0) { for(auto &extra_cursor: multiple_cursors_extra_cursors) @@ -2655,7 +2655,7 @@ bool Source::View::on_key_press_event_multiple_cursors(GdkEventKey *key) { multiple_cursors_use=false; return false; } - + return false; } @@ -2692,7 +2692,7 @@ bool Source::View::on_button_press_event(GdkEventButton *event) { get_iter_at_location(iter, x, y); if(iter) get_buffer()->place_cursor(iter); - + Menu::get().actions["source_goto_declaration_or_implementation"]->activate(); return true; } @@ -2896,19 +2896,19 @@ std::pair<char, unsigned> Source::View::find_tab_char_and_size() { Source::GenericView::GenericView(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language) : BaseView(file_path, language), View(file_path, language, true) { configure(); spellcheck_all=true; - + if(language) get_source_buffer()->set_language(language); - + auto completion=get_completion(); completion->property_show_headers()=false; completion->property_show_icons()=false; completion->property_accelerators()=0; - + auto completion_words=Gsv::CompletionWords::create("", Glib::RefPtr<Gdk::Pixbuf>()); completion_words->register_provider(get_buffer()); completion->add_provider(completion_words); - + if(language) { auto language_manager=LanguageManager::get_default(); auto search_paths=language_manager->get_search_path(); @@ -2966,7 +2966,7 @@ void Source::GenericView::parse_language_file(Glib::RefPtr<CompletionBuffer> &co try { parse_language_file(completion_buffer, has_context_class, node.second); } - catch(const std::exception &e) { + catch(const std::exception &e) { } } } diff --git a/src/source.h b/src/source.h index 16a23f8c..a8805fc8 100644 --- a/src/source.h +++ b/src/source.h @@ -20,29 +20,29 @@ namespace Source { public: static Glib::RefPtr<Gsv::StyleSchemeManager> get_default(); }; - + Glib::RefPtr<Gsv::Language> guess_language(const boost::filesystem::path &file_path); - + class Offset { public: Offset() = default; Offset(unsigned line, unsigned index, boost::filesystem::path file_path_=""): line(line), index(index), file_path(std::move(file_path_)) {} operator bool() { return !file_path.empty(); } bool operator==(const Offset &o) {return (line==o.line && index==o.index);} - + unsigned line; unsigned index; boost::filesystem::path file_path; }; - + class FixIt { public: enum class Type {INSERT, REPLACE, ERASE}; - + FixIt(std::string source_, std::pair<Offset, Offset> offsets_); - + std::string string(const Glib::RefPtr<Gtk::TextBuffer> &buffer); - + Type type; std::string source; std::pair<Offset, Offset> offsets; @@ -52,14 +52,14 @@ namespace Source { public: static std::unordered_set<View*> non_deleted_views; static std::unordered_set<View*> views; - + View(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language, bool is_generic_view=false); ~View() override; - + bool save() override; - + void configure() override; - + void search_highlight(const std::string &text, bool case_sensitive, bool regex); std::function<void(int number)> update_search_occurrences; void search_forward(); @@ -67,9 +67,9 @@ namespace Source { void replace_forward(const std::string &replacement); void replace_backward(const std::string &replacement); void replace_all(const std::string &replacement); - + void paste(); - + std::function<void()> non_interactive_completion; std::function<void(bool)> format_style; std::function<Offset()> get_declaration_location; @@ -87,13 +87,13 @@ namespace Source { std::function<void()> toggle_comments; std::function<std::tuple<Source::Offset, std::string, size_t>()> get_documentation_template; std::function<void(int)> toggle_breakpoint; - + void hide_tooltips() override; void hide_dialogs() override; - + void set_tab_char_and_size(char tab_char, unsigned tab_size); std::pair<char, unsigned> get_tab_char_and_size() {return {tab_char, tab_size};} - + bool soft_reparse_needed=false; bool full_reparse_needed=false; virtual void soft_reparse(bool delayed=false) {soft_reparse_needed=false;} @@ -110,7 +110,7 @@ namespace Source { virtual void show_type_tooltips(const Gdk::Rectangle &rectangle) {} gdouble on_motion_last_x=0.0; gdouble on_motion_last_y=0.0; - + /// Usually returns at start of line, but not always Gtk::TextIter find_start_of_sentence(Gtk::TextIter iter); bool find_open_non_curly_bracket_backward(Gtk::TextIter iter, Gtk::TextIter &found_iter); @@ -118,11 +118,11 @@ namespace Source { bool find_close_curly_bracket_forward(Gtk::TextIter iter, Gtk::TextIter &found_iter); long symbol_count(Gtk::TextIter iter, unsigned int positive_char, unsigned int negative_char); bool is_templated_function(Gtk::TextIter iter, Gtk::TextIter &parenthesis_end_iter); - + std::string get_token(Gtk::TextIter iter); - + void cleanup_whitespace_characters_on_return(const Gtk::TextIter &iter); - + bool is_bracket_language=false; bool on_key_press_event(GdkEventKey* key) override; bool on_key_press_event_basic(GdkEventKey* key); @@ -131,28 +131,28 @@ namespace Source { bool on_key_press_event_smart_inserts(GdkEventKey* key); bool on_button_press_event(GdkEventButton *event) override; bool on_motion_notify_event(GdkEventMotion *motion_event) override; - + std::pair<char, unsigned> find_tab_char_and_size(); unsigned tab_size; char tab_char; std::string tab; - + bool interactive_completion=true; - + guint previous_non_modifier_keyval=0; private: void setup_tooltip_and_dialog_events(); void setup_format_style(bool is_generic_view); - + void cleanup_whitespace_characters(); Gsv::DrawSpacesFlags parse_show_whitespace_characters(const std::string &text); - + GtkSourceSearchContext *search_context; GtkSourceSearchSettings *search_settings; static void search_occurrences_updated(GtkWidget* widget, GParamSpec* property, gpointer data); - + sigc::connection renderer_activate_connection; - + bool multiple_cursors_signals_set=false; bool multiple_cursors_use=false; std::vector<std::pair<Glib::RefPtr<Gtk::TextBuffer::Mark>, int>> multiple_cursors_extra_cursors; @@ -161,7 +161,7 @@ namespace Source { int multiple_cursors_erase_forward_length; bool on_key_press_event_multiple_cursors(GdkEventKey* key); }; - + class GenericView : public View { private: class CompletionBuffer : public Gtk::TextBuffer { @@ -170,7 +170,7 @@ namespace Source { }; public: GenericView(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language); - + void parse_language_file(Glib::RefPtr<CompletionBuffer> &completion_buffer, bool &has_context_class, const boost::property_tree::ptree &pt); }; } diff --git a/src/source_base.cc b/src/source_base.cc index 25c293ba..eb40bfb8 100644 --- a/src/source_base.cc +++ b/src/source_base.cc @@ -8,13 +8,13 @@ Source::BaseView::BaseView(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language): Gsv::View(), file_path(file_path), language(language), status_diagnostics(0, 0, 0) { load(true); get_buffer()->place_cursor(get_buffer()->get_iter_at_offset(0)); - + signal_focus_in_event().connect([this](GdkEventFocus *event) { if(this->last_write_time!=static_cast<std::time_t>(-1)) check_last_write_time(); return false; }); - + monitor_file(); } @@ -28,11 +28,11 @@ bool Source::BaseView::load(bool not_undoable_action) { last_write_time=boost::filesystem::last_write_time(file_path, ec); if(ec) last_write_time=static_cast<std::time_t>(-1); - + disable_spellcheck=true; if(not_undoable_action) get_source_buffer()->begin_not_undoable_action(); - + class Guard { public: Source::BaseView *view; @@ -44,14 +44,14 @@ bool Source::BaseView::load(bool not_undoable_action) { } }; Guard guard{this, not_undoable_action}; - + if(language) { std::ifstream input(file_path.string(), std::ofstream::binary); if(input) { std::stringstream ss; ss << input.rdbuf(); Glib::ustring ustr=ss.str(); - + bool valid=true; Glib::ustring::iterator iter; while(!ustr.validate(iter)) { @@ -60,10 +60,10 @@ bool Source::BaseView::load(bool not_undoable_action) { ustr.replace(iter, next_char_iter, "?"); valid=false; } - + if(!valid) Terminal::get().print("Warning: "+file_path.string()+" is not a valid UTF-8 file. Saving might corrupt the file.\n"); - + if(get_buffer()->size()==0) get_buffer()->insert_at_cursor(ustr); else @@ -78,7 +78,7 @@ bool Source::BaseView::load(bool not_undoable_action) { std::stringstream ss; ss << input.rdbuf(); Glib::ustring ustr=ss.str(); - + if(ustr.validate()) { if(get_buffer()->size()==0) get_buffer()->insert_at_cursor(ustr); @@ -93,14 +93,14 @@ bool Source::BaseView::load(bool not_undoable_action) { else return false; } - + get_buffer()->set_modified(false); return true; } void Source::BaseView::replace_text(const std::string &new_text) { get_buffer()->begin_user_action(); - + if(get_buffer()->size()==0) { get_buffer()->insert_at_cursor(new_text); get_buffer()->end_user_action(); @@ -111,13 +111,13 @@ void Source::BaseView::replace_text(const std::string &new_text) { get_buffer()->end_user_action(); return; } - + auto iter=get_buffer()->get_insert()->get_iter(); int cursor_line_nr=iter.get_line(); int cursor_line_offset=iter.ends_line() ? std::numeric_limits<int>::max() : iter.get_line_offset(); - + std::vector<std::pair<const char*, const char*>> new_lines; - + const char* line_start=new_text.c_str(); for(const char &chr: new_text) { if(chr=='\n') { @@ -127,17 +127,17 @@ void Source::BaseView::replace_text(const std::string &new_text) { } if(new_text.empty() || new_text.back()!='\n') new_lines.emplace_back(line_start, &new_text[new_text.size()]); - + try { auto hunks = Git::Repository::Diff::get_hunks(get_buffer()->get_text().raw(), new_text); - + for(auto it=hunks.rbegin();it!=hunks.rend();++it) { bool place_cursor=false; Gtk::TextIter start; if(it->old_lines.second!=0) { start=get_buffer()->get_iter_at_line(it->old_lines.first-1); auto end=get_buffer()->get_iter_at_line(it->old_lines.first-1+it->old_lines.second); - + if(cursor_line_nr>=start.get_line() && cursor_line_nr<end.get_line()) { if(it->new_lines.second!=0) { place_cursor = true; @@ -145,7 +145,7 @@ void Source::BaseView::replace_text(const std::string &new_text) { cursor_line_nr+=static_cast<int>(0.5+(static_cast<float>(line_diff)/it->old_lines.second)*it->new_lines.second)-line_diff; } } - + get_buffer()->erase(start, end); start=get_buffer()->get_iter_at_line(it->old_lines.first-1); } @@ -161,13 +161,13 @@ void Source::BaseView::replace_text(const std::string &new_text) { catch(...) { Terminal::get().print("Error: Could not replace text in buffer\n", true); } - + get_buffer()->end_user_action(); } void Source::BaseView::rename(const boost::filesystem::path &path) { file_path=path; - + if(update_status_file_path) update_status_file_path(this); if(update_tab_label) @@ -215,7 +215,7 @@ void Source::BaseView::monitor_file() { void Source::BaseView::check_last_write_time(std::time_t last_write_time_) { if(this->last_write_time==static_cast<std::time_t>(-1)) return; - + if(Config::get().source.auto_reload_changed_files && !get_buffer()->get_modified()) { boost::system::error_code ec; auto last_write_time=last_write_time_!=static_cast<std::time_t>(-1) ? last_write_time_ : boost::filesystem::last_write_time(file_path, ec); @@ -306,10 +306,10 @@ void Source::BaseView::place_cursor_at_line_index(int line, int index) { Gtk::TextIter Source::BaseView::get_smart_home_iter(const Gtk::TextIter &iter) { auto start_line_iter=get_buffer()->get_iter_at_line(iter.get_line()); auto start_sentence_iter=start_line_iter; - while(!start_sentence_iter.ends_line() && + while(!start_sentence_iter.ends_line() && (*start_sentence_iter==' ' || *start_sentence_iter=='\t') && start_sentence_iter.forward_char()) {} - + if(iter>start_sentence_iter || iter==start_line_iter) return start_sentence_iter; else @@ -319,12 +319,12 @@ Gtk::TextIter Source::BaseView::get_smart_home_iter(const Gtk::TextIter &iter) { Gtk::TextIter Source::BaseView::get_smart_end_iter(const Gtk::TextIter &iter) { auto end_line_iter=get_iter_at_line_end(iter.get_line()); auto end_sentence_iter=end_line_iter; - while(!end_sentence_iter.starts_line() && + while(!end_sentence_iter.starts_line() && (*end_sentence_iter==' ' || *end_sentence_iter=='\t' || end_sentence_iter.ends_line()) && end_sentence_iter.backward_char()) {} if(!end_sentence_iter.ends_line() && *end_sentence_iter!=' ' && *end_sentence_iter!='\t') end_sentence_iter.forward_char(); - + if(iter==end_line_iter) return end_sentence_iter; else diff --git a/src/source_base.h b/src/source_base.h index 3307f4b6..ae3085dd 100644 --- a/src/source_base.h +++ b/src/source_base.h @@ -11,55 +11,55 @@ namespace Source { BaseView(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language); ~BaseView() override; boost::filesystem::path file_path; - + Glib::RefPtr<Gsv::Language> language; - + bool load(bool not_undoable_action=false); /// Set new text more optimally and without unnecessary scrolling void replace_text(const std::string &new_text); virtual void rename(const boost::filesystem::path &path); virtual bool save() = 0; - + Glib::RefPtr<Gio::FileMonitor> monitor; sigc::connection monitor_changed_connection; sigc::connection delayed_monitor_changed_connection; - + virtual void configure() = 0; virtual void hide_tooltips() = 0; virtual void hide_dialogs() = 0; - + std::function<void(BaseView* view, bool center, bool show_tooltips)> scroll_to_cursor_delayed=[](BaseView* view, bool center, bool show_tooltips) {}; - + /// Safely returns iter given line and an offset using either byte index or character offset. Defaults to using byte index. virtual Gtk::TextIter get_iter_at_line_pos(int line, int pos); /// Safely returns iter given line and character offset Gtk::TextIter get_iter_at_line_offset(int line, int offset); /// Safely returns iter given line and byte index Gtk::TextIter get_iter_at_line_index(int line, int index); - + Gtk::TextIter get_iter_at_line_end(int line_nr); Gtk::TextIter get_iter_for_dialog(); - + /// Safely places cursor at line using get_iter_at_line_pos. void place_cursor_at_line_pos(int line, int pos); /// Safely places cursor at line offset void place_cursor_at_line_offset(int line, int offset); /// Safely places cursor at line index void place_cursor_at_line_index(int line, int index); - + protected: std::time_t last_write_time; void monitor_file(); void check_last_write_time(std::time_t last_write_time_=static_cast<std::time_t>(-1)); - + /// Move iter to line start. Depending on iter position, before or after indentation. - /// Works with wrapped lines. + /// Works with wrapped lines. Gtk::TextIter get_smart_home_iter(const Gtk::TextIter &iter); /// Move iter to line end. Depending on iter position, before or after indentation. - /// Works with wrapped lines. + /// Works with wrapped lines. /// Note that smart end goes FIRST to end of line to avoid hiding empty chars after expressions. Gtk::TextIter get_smart_end_iter(const Gtk::TextIter &iter); - + std::string get_line(const Gtk::TextIter &iter); std::string get_line(const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark); std::string get_line(int line_nr); @@ -71,7 +71,7 @@ namespace Source { Gtk::TextIter get_tabs_end_iter(const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark); Gtk::TextIter get_tabs_end_iter(int line_nr); Gtk::TextIter get_tabs_end_iter(); - + std::set<int> diagnostic_offsets; void place_cursor_at_next_diagnostic(); public: @@ -84,7 +84,7 @@ namespace Source { std::string status_state; std::function<void(BaseView *view)> update_status_branch; std::string status_branch; - + bool disable_spellcheck=false; }; } diff --git a/src/source_clang.cc b/src/source_clang.cc index b3e14388..06585cdf 100644 --- a/src/source_clang.cc +++ b/src/source_clang.cc @@ -19,7 +19,7 @@ clangmm::Index Source::ClangViewParse::clang_index(0, 0); Source::ClangViewParse::ClangViewParse(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language): BaseView(file_path, language), Source::View(file_path, language) { Usages::Clang::erase_cache(file_path); - + auto tag_table=get_buffer()->get_tag_table(); for (auto &item : clang_types()) { auto tag=tag_table->lookup(item.second); @@ -28,16 +28,16 @@ Source::ClangViewParse::ClangViewParse(const boost::filesystem::path &file_path, else syntax_tags.emplace(item.first, tag); } - + if(get_buffer()->size()==0 && (language->get_id()=="chdr" || language->get_id()=="cpphdr")) { disable_spellcheck=true; get_buffer()->insert_at_cursor("#pragma once\n"); disable_spellcheck=false; Info::get().print("Added \"#pragma once\" to empty C/C++ header file"); } - + parse_initialize(); - + get_buffer()->signal_changed().connect([this]() { soft_reparse(true); }); @@ -46,7 +46,7 @@ Source::ClangViewParse::ClangViewParse(const boost::filesystem::path &file_path, bool Source::ClangViewParse::save() { if(!Source::View::save()) return false; - + if(language->get_id()=="chdr" || language->get_id()=="cpphdr") { for(auto &view: views) { if(auto clang_view=dynamic_cast<Source::ClangView*>(view)) { @@ -60,7 +60,7 @@ bool Source::ClangViewParse::save() { void Source::ClangViewParse::configure() { Source::View::configure(); - + auto scheme = get_source_buffer()->get_style_scheme(); auto tag_table=get_buffer()->get_tag_table(); for (auto &item : clang_types()) { @@ -90,7 +90,7 @@ void Source::ClangViewParse::parse_initialize() { parse_thread.join(); parse_state=ParseState::PROCESSING; parse_process_state=ParseProcessState::STARTING; - + auto buffer=get_buffer()->get_text(); //Remove includes for first parse for initial syntax highlighting std::size_t pos=0; @@ -107,7 +107,7 @@ void Source::ClangViewParse::parse_initialize() { auto &buffer_raw=const_cast<std::string&>(buffer.raw()); if(language && (language->get_id()=="chdr" || language->get_id()=="cpphdr")) clangmm::remove_include_guard(buffer_raw); - + auto build=Project::Build::create(file_path); if(build->get_project_path().empty()) Info::get().print(file_path.filename().string()+": could not find a supported build system"); @@ -120,7 +120,7 @@ void Source::ClangViewParse::parse_initialize() { for(auto &token: *clang_tokens) clang_tokens_offsets.emplace_back(token.get_source_range().get_offsets()); update_syntax(); - + status_state="parsing..."; if(update_status_state) update_status_state(this); @@ -244,10 +244,10 @@ void Source::ClangViewParse::update_syntax() { buffer->apply_tag(syntax_tag_it->second, begin_iter, end_iter); } }; - + for(auto &pair: syntax_tags) buffer->remove_tag(pair.second, buffer->begin(), buffer->end()); - + for(size_t c=0;c<clang_tokens->size();++c) { auto &token=(*clang_tokens)[c]; auto &token_offsets=clang_tokens_offsets[c]; @@ -277,7 +277,7 @@ void Source::ClangViewParse::update_diagnostics() { size_t num_errors=0; size_t num_fix_its=0; for(auto &diagnostic: clang_diagnostics) { - if(diagnostic.path==file_path.string()) { + if(diagnostic.path==file_path.string()) { int line=diagnostic.offsets.first.line-1; if(line<0 || line>=get_buffer()->get_line_count()) line=get_buffer()->get_line_count()-1; @@ -290,7 +290,7 @@ void Source::ClangViewParse::update_diagnostics() { start.backward_char(); } diagnostic_offsets.emplace(start.get_offset()); - + line=diagnostic.offsets.second.line-1; if(line<0 || line>=get_buffer()->get_line_count()) line=get_buffer()->get_line_count()-1; @@ -298,7 +298,7 @@ void Source::ClangViewParse::update_diagnostics() { index=diagnostic.offsets.second.index-1; if(index>=0 && index<end.get_line_index()) end=get_buffer()->get_iter_at_line_index(line, index); - + bool error=false; std::string severity_tag_name; if(diagnostic.severity<=clangmm::Diagnostic::Severity::Warning) { @@ -310,7 +310,7 @@ void Source::ClangViewParse::update_diagnostics() { num_errors++; error=true; } - + std::string fix_its_string; unsigned fix_its_count=0; for(auto &fix_it: diagnostic.fix_its) { @@ -320,24 +320,24 @@ void Source::ClangViewParse::update_diagnostics() { offsets.first.index=clang_offsets.first.index-1; offsets.second.line=clang_offsets.second.line-1; offsets.second.index=clang_offsets.second.index-1; - + fix_its.emplace_back(fix_it.source, offsets); - + if(fix_its_string.size()>0) fix_its_string+='\n'; fix_its_string+=fix_its.back().string(get_buffer()); fix_its_count++; num_fix_its++; } - + if(fix_its_count==1) fix_its_string.insert(0, "Fix-it:\n"); else if(fix_its_count>1) fix_its_string.insert(0, "Fix-its:\n"); - + if(!fix_its_string.empty()) diagnostic.spelling+="\n\n"+fix_its_string; - + add_diagnostic_tooltip(start, end, diagnostic.spelling, error); } } @@ -357,7 +357,7 @@ void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle) get_iter_location(iter, iter_rectangle); if(iter.ends_line() && location_x>iter_rectangle.get_x()) return; - + auto line=static_cast<unsigned>(iter.get_line()); auto index=static_cast<unsigned>(iter.get_line_index()); type_tooltips.clear(); @@ -377,13 +377,13 @@ void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle) auto brief_comment=token.get_cursor().get_brief_comments(); if(brief_comment!="") tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), "\n\n"+brief_comment); - + #ifdef JUCI_ENABLE_DEBUG if(Debug::LLDB::get().is_stopped()) { auto referenced=token.get_cursor().get_referenced(); auto location=referenced.get_source_location(); Glib::ustring value_type="Value"; - + auto iter=start; while((*iter>='a' && *iter<='z') || (*iter>='A' && *iter<='Z') || (*iter>='0' && *iter<='9') || *iter=='_' || *iter=='.') { start=iter; @@ -399,7 +399,7 @@ void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle) } } auto spelling=get_buffer()->get_text(start, end).raw(); - + Glib::ustring debug_value; auto cursor_kind=referenced.get_kind(); if(cursor_kind!=clangmm::Cursor::Kind::FunctionDecl && cursor_kind!=clangmm::Cursor::Kind::CXXMethod && @@ -426,10 +426,10 @@ void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle) } } #endif - + return tooltip_buffer; }; - + type_tooltips.emplace_back(create_tooltip_buffer, this, get_buffer()->create_mark(start), get_buffer()->create_mark(end)); type_tooltips.show(); return; @@ -448,29 +448,29 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa return; autocomplete.run(); }; - + autocomplete.is_processing=[this] { return parse_state==ParseState::PROCESSING; }; - + autocomplete.reparse=[this] { selected_completion_string=nullptr; code_complete_results=nullptr; soft_reparse(true); }; - + autocomplete.cancel_reparse=[this] { delayed_reparse_connection.disconnect(); }; - + autocomplete.get_parse_lock=[this]() { return std::make_unique<std::lock_guard<std::mutex>>(parse_mutex); }; - + autocomplete.stop_parse=[this]() { parse_process_state=ParseProcessState::IDLE; }; - + // Activate argument completions get_buffer()->signal_changed().connect([this] { if(!interactive_completion) @@ -514,10 +514,10 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa autocomplete.is_continue_key=[](guint keyval) { if((keyval>='0' && keyval<='9') || (keyval>='a' && keyval<='z') || (keyval>='A' && keyval<='Z') || keyval=='_') return true; - + return false; }; - + autocomplete.is_restart_key=[this](guint keyval) { auto iter=get_buffer()->get_insert()->get_iter(); iter.backward_chars(2); @@ -525,15 +525,15 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa return true; return false; }; - + autocomplete.run_check=[this]() { auto iter=get_buffer()->get_insert()->get_iter(); iter.backward_char(); if(!is_code_iter(iter)) return false; - + show_arguments=false; - + std::string line=" "+get_line_before(); const static std::regex dot_or_arrow(R"(^.*[a-zA-Z0-9_\)\]\>](\.|->)([a-zA-Z0-9_]*)$)"); const static std::regex colon_colon(R"(^.*::([a-zA-Z0-9_]*)$)"); @@ -579,29 +579,29 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa autocomplete.prefix=get_buffer()->get_text(iter, end_iter); return true; } - + return false; }; - + autocomplete.before_add_rows=[this] { status_state="autocomplete..."; if(update_status_state) update_status_state(this); }; - + autocomplete.after_add_rows=[this] { status_state=""; if(update_status_state) update_status_state(this); }; - + autocomplete.on_add_rows_error=[this] { Terminal::get().print("Error: autocomplete failed, reparsing "+this->file_path.string()+"\n", true); selected_completion_string=nullptr; code_complete_results=nullptr; full_reparse(); }; - + autocomplete.add_rows=[this](std::string &buffer, int line_number, int column) { if(this->language && (this->language->get_id()=="chdr" || this->language->get_id()=="cpphdr")) clangmm::remove_include_guard(buffer); @@ -611,14 +611,14 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa parse_state.compare_exchange_strong(expected, ParseState::RESTARTING); return; } - + if(autocomplete.state==Autocomplete::State::STARTING) { std::string prefix_copy; { std::lock_guard<std::mutex> lock(autocomplete.prefix_mutex); prefix_copy=autocomplete.prefix; } - + completion_strings.clear(); for (unsigned i = 0; i < code_complete_results->size(); ++i) { auto result=code_complete_results->get(i); @@ -684,20 +684,20 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa } } }; - + autocomplete.on_show = [this] { hide_tooltips(); }; - + autocomplete.on_hide = [this] { selected_completion_string=nullptr; code_complete_results=nullptr; }; - + autocomplete.on_changed = [this](unsigned int index, const std::string &text) { selected_completion_string=completion_strings[index]; }; - + autocomplete.on_select = [this](unsigned int index, const std::string &text, bool hide_window) { std::string row; auto pos=text.find(" → "); @@ -767,7 +767,7 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa } } } - + if(start_pos!=std::string::npos && end_pos!=std::string::npos) { int start_offset=CompletionDialog::get()->start_mark->get_iter().get_offset()+start_pos; int end_offset=CompletionDialog::get()->start_mark->get_iter().get_offset()+end_pos; @@ -785,7 +785,7 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa } } }; - + autocomplete.get_tooltip = [this](unsigned int index) { return clangmm::to_string(clang_getCompletionBriefComment(completion_strings[index])); }; @@ -820,7 +820,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file BaseView(file_path, language), Source::ClangViewParse(file_path, language) { similar_identifiers_tag=get_buffer()->create_tag(); similar_identifiers_tag->property_weight()=Pango::WEIGHT_ULTRAHEAVY; - + get_buffer()->signal_changed().connect([this]() { if(last_tagged_identifier) { for(auto &mark: similar_identifiers_marks) { @@ -832,7 +832,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file last_tagged_identifier=Identifier(); } }); - + get_token_spelling=[this]() { if(!parsed) { Info::get().print("Buffer is parsing"); @@ -848,7 +848,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file } return identifier.spelling; }; - + rename_similar_tokens=[this](const std::string &text) { if(!parsed) { Info::get().print("Buffer is parsing"); @@ -857,7 +857,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file auto identifier=get_identifier(); if(identifier) { wait_parsing(); - + std::vector<clangmm::TranslationUnit*> translation_units; translation_units.emplace_back(clang_tu.get()); for(auto &view: views) { @@ -866,10 +866,10 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file translation_units.emplace_back(clang_view->clang_tu.get()); } } - + auto build=Project::Build::create(this->file_path); auto usages=Usages::Clang::get_usages(build->get_project_path(), build->get_default_path(), build->get_debug_path(), identifier.spelling, identifier.cursor, translation_units); - + std::vector<Source::View*> renamed_views; std::vector<Usages::Clang::Usages*> usages_renamed; for(auto &usage: usages) { @@ -932,7 +932,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file Terminal::get().print("Error: could not write to file "+usage.path.string()+'\n', true); } } - + if(!usages_renamed.empty()) { Terminal::get().print("Renamed "); Terminal::get().print(identifier.spelling, true); @@ -961,12 +961,12 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file ++line_c; } } - + for(auto &view: renamed_views) view->soft_reparse_needed=false; } }; - + get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator& iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark>& mark){ if(mark->get_name()=="insert") { delayed_tag_similar_symbols_connection.disconnect(); @@ -977,7 +977,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file }, 100); } }); - + auto declaration_location=[this]() { auto identifier=get_identifier(); if(identifier) { @@ -998,7 +998,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file std::string sm_str; }; ClientData client_data{this->file_path, std::string(), get_buffer()->get_insert()->get_iter().get_line(), sm[1].str()}; - + // Attempt to find the 100% correct include file first clang_getInclusions(clang_tu->cx_tu, [](CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData client_data_) { auto client_data=static_cast<ClientData*>(client_data_); @@ -1009,10 +1009,10 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file client_data->found_include=clangmm::to_string(clang_getFileName(included_file)); } }, &client_data); - + if(!client_data.found_include.empty()) return Offset(0, 0, client_data.found_include); - + // Find a matching include file if no include was found previously clang_getInclusions(clang_tu->cx_tu, [](CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData client_data_) { auto client_data=static_cast<ClientData*>(client_data_); @@ -1031,14 +1031,14 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file } } }, &client_data); - + if(!client_data.found_include.empty()) return Offset(0, 0, client_data.found_include); } } return Offset(); }; - + get_declaration_location=[this, declaration_location](){ if(!parsed) { if(selected_completion_string) { @@ -1049,14 +1049,14 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file if(CompletionDialog::get()) CompletionDialog::get()->hide(); auto offset=Offset(source_location_offset.line-1, source_location_offset.index-1, source_location.get_path()); - + // Workaround for bug in ArchLinux's clang_getFileName() // TODO: remove the workaround when this is fixed auto include_path=filesystem::get_normal_path(offset.file_path); boost::system::error_code ec; if(!boost::filesystem::exists(include_path, ec)) offset.file_path="/usr/include"/include_path; - + return offset; } else { @@ -1064,24 +1064,24 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file return Offset(); } } - + Info::get().print("Buffer is parsing"); return Offset(); } auto offset=declaration_location(); if(!offset) Info::get().print("No declaration found"); - + // Workaround for bug in ArchLinux's clang_getFileName() // TODO: remove the workaround when this is fixed auto include_path=filesystem::get_normal_path(offset.file_path); boost::system::error_code ec; if(!boost::filesystem::exists(include_path, ec)) offset.file_path="/usr/include"/include_path; - + return offset; }; - + get_type_declaration_location=[this](){ if(!parsed) { Info::get().print("Buffer is parsing"); @@ -1096,14 +1096,14 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file if(!path.empty()) { auto source_location_offset=source_location.get_offset(); auto offset=Offset(source_location_offset.line-1, source_location_offset.index-1, path); - + // Workaround for bug in ArchLinux's clang_getFileName() // TODO: remove the workaround when this is fixed auto include_path=filesystem::get_normal_path(offset.file_path); boost::system::error_code ec; if(!boost::filesystem::exists(include_path, ec)) offset.file_path="/usr/include"/include_path; - + return offset; } } @@ -1111,12 +1111,12 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file Info::get().print("No type declaration found"); return Offset(); }; - + auto implementation_locations=[this](const Identifier &identifier) { std::vector<Offset> offsets; if(identifier) { wait_parsing(); - + //First, look for a definition cursor that is equal auto identifier_usr=identifier.cursor.get_usr(); for(auto &view: views) { @@ -1146,7 +1146,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file } if(!offsets.empty()) return offsets; - + //If no implementation was found, try using clang_getCursorDefinition auto definition=identifier.cursor.get_definition(); if(definition) { @@ -1159,7 +1159,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file offsets.emplace_back(offset); return offsets; } - + //If no implementation was found, use declaration if it is a function template auto canonical=identifier.cursor.get_canonical(); auto cursor=clang_tu->get_cursor(canonical.get_source_location()); @@ -1173,7 +1173,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file offsets.emplace_back(offset); return offsets; } - + //If no implementation was found, try using Ctags auto name=identifier.cursor.get_spelling(); auto parent=identifier.cursor.get_semantic_parent(); @@ -1196,7 +1196,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file } return offsets; }; - + get_implementation_locations=[this, implementation_locations](){ if(!parsed) { if(selected_completion_string) { @@ -1209,7 +1209,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file } if(CompletionDialog::get()) CompletionDialog::get()->hide(); - + // Workaround for bug in ArchLinux's clang_getFileName() // TODO: remove the workaround when this is fixed for(auto &offset: offsets) { @@ -1218,7 +1218,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file if(!boost::filesystem::exists(include_path, ec)) offset.file_path="/usr/include"/include_path; } - + return offsets; } else { @@ -1226,14 +1226,14 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file return std::vector<Offset>(); } } - + Info::get().print("Buffer is parsing"); return std::vector<Offset>(); } auto offsets=implementation_locations(get_identifier()); if(offsets.empty()) Info::get().print("No implementation found"); - + // Workaround for bug in ArchLinux's clang_getFileName() // TODO: remove the workaround when this is fixed for(auto &offset: offsets) { @@ -1242,18 +1242,18 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file if(!boost::filesystem::exists(include_path, ec)) offset.file_path="/usr/include"/include_path; } - + return offsets; }; - + get_declaration_or_implementation_locations=[this, declaration_location, implementation_locations]() { if(!parsed) { Info::get().print("Buffer is parsing"); return std::vector<Offset>(); } - + std::vector<Offset> offsets; - + bool is_implementation=false; auto iter=get_buffer()->get_insert()->get_iter(); auto line=static_cast<unsigned>(iter.get_line()); @@ -1286,10 +1286,10 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file offsets.emplace_back(offset); } } - + if(offsets.empty()) Info::get().print("No declaration or implementation found"); - + // Workaround for bug in ArchLinux's clang_getFileName() // TODO: remove the workaround when this is fixed for(auto &offset: offsets) { @@ -1298,10 +1298,10 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file if(!boost::filesystem::exists(include_path, ec)) offset.file_path="/usr/include"/include_path; } - + return offsets; }; - + get_usages=[this]() { std::vector<std::pair<Offset, std::string> > usages; if(!parsed) { @@ -1311,7 +1311,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file auto identifier=get_identifier(); if(identifier) { wait_parsing(); - + auto embolden_token=[](std::string &line, unsigned token_start_pos, unsigned token_end_pos) { //markup token as bold size_t pos=0; @@ -1329,14 +1329,14 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file } line.insert(token_end_pos, "</b>"); line.insert(token_start_pos, "<b>"); - + size_t start_pos=0; while(start_pos<line.size() && (line[start_pos]==' ' || line[start_pos]=='\t')) ++start_pos; if(start_pos>0) line.erase(0, start_pos); }; - + std::vector<clangmm::TranslationUnit*> translation_units; translation_units.emplace_back(clang_tu.get()); for(auto &view: views) { @@ -1345,7 +1345,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file translation_units.emplace_back(clang_view->clang_tu.get()); } } - + auto build=Project::Build::create(this->file_path); auto usages_clang=Usages::Clang::get_usages(build->get_project_path(), build->get_default_path(), build->get_debug_path(), {identifier.spelling}, {identifier.cursor}, translation_units); for(auto &usage: usages_clang) { @@ -1356,12 +1356,12 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file } } } - + if(usages.empty()) Info::get().print("No symbol found at current cursor location"); return usages; }; - + get_method=[this] { if(!parsed) { Info::get().print("Buffer is parsing"); @@ -1392,7 +1392,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file if(kind==clangmm::Cursor::Kind::FunctionDecl || kind==clangmm::Cursor::Kind::CXXMethod) { auto start_offset=cursor.get_source_range().get_start().get_offset(); auto end_offset=token_offsets.first; - + // To accurately get result type with needed namespace and class/struct names: int angle_brackets=0; for(size_t c=0;c<clang_tokens->size();++c) { @@ -1402,7 +1402,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file (token_offsets.first.line>start_offset.line && token_offsets.second.line<end_offset.line) || (token_offsets.first.line!=start_offset.line && token_offsets.second.line==end_offset.line && token_offsets.second.index<=end_offset.index) || (token_offsets.first.line==start_offset.line && token_offsets.second.line==end_offset.line && - token_offsets.first.index>=start_offset.index && token_offsets.second.index<=end_offset.index)) { + token_offsets.first.index>=start_offset.index && token_offsets.second.index<=end_offset.index)) { auto token_spelling=token.get_spelling(); if(token.get_kind()==clangmm::Token::Kind::Identifier) { if(c==0 || (*clang_tokens)[c-1].get_spelling()!="::") { @@ -1433,10 +1433,10 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file } } } - + if(!result.empty() && result.back()!='*' && result.back()!='&' && result.back()!=' ') result+=' '; - + if(clang_CXXMethod_isConst(cursor.cx_cursor)) specifier+=" const"; @@ -1446,7 +1446,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file specifier+=" noexcept"; #endif } - + auto name=cursor.get_spelling(); auto parent=cursor.get_semantic_parent(); std::vector<std::string> semantic_parents; @@ -1458,7 +1458,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file } parent=parent.get_semantic_parent(); } - + std::string arguments; for(auto &argument_cursor: cursor.get_arguments()) { auto argument_type=argument_cursor.get_type().get_spelling(); @@ -1485,7 +1485,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file Info::get().print("No method found at current cursor location"); return std::string(); }; - + get_methods=[this](){ std::vector<std::pair<Offset, std::string> > methods; if(!parsed) { @@ -1504,7 +1504,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file if(offset==last_offset) continue; last_offset=offset; - + std::string method; if(kind!=clangmm::Cursor::Kind::Constructor && kind!=clangmm::Cursor::Kind::Destructor) { method+=cursor.get_type().get_result().get_spelling(); @@ -1514,14 +1514,14 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file method+=" "; } method+=cursor.get_display_name(); - + std::string prefix; auto parent=cursor.get_semantic_parent(); while(parent && parent.get_kind()!=clangmm::Cursor::Kind::TranslationUnit) { prefix.insert(0, parent.get_display_name()+(prefix.empty()?"":"::")); parent=parent.get_semantic_parent(); } - + method=Glib::Markup::escape_text(method); //Add bold method token size_t token_end_pos=method.find('('); @@ -1532,25 +1532,25 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file --token_start_pos; method.insert(token_end_pos, "</b>"); method.insert(token_start_pos, "<b>"); - + if(!prefix.empty()) prefix+=':'; prefix+=std::to_string(offset.line)+": "; prefix=Glib::Markup::escape_text(prefix); - + methods.emplace_back(Offset(offset.line-1, offset.index-1), prefix+method); } } } if(methods.empty()) Info::get().print("No methods found in current buffer"); - + return methods; }; - + get_token_data=[this]() -> std::vector<std::string> { clangmm::Cursor cursor; - + std::vector<std::string> data; if(!parsed) { if(selected_completion_string) { @@ -1565,16 +1565,16 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file return data; } } - + if(!cursor) { auto identifier=get_identifier(); if(identifier) cursor=identifier.cursor.get_canonical(); } - + if(cursor) { data.emplace_back("clang"); - + std::string symbol; clangmm::Cursor last_cursor; auto it=data.end(); @@ -1590,21 +1590,21 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file last_cursor=cursor; cursor=cursor.get_semantic_parent(); } while(cursor.get_kind()!=clangmm::Cursor::Kind::TranslationUnit); - + if(last_cursor.get_kind()!=clangmm::Cursor::Kind::Namespace) data.emplace(++data.begin(), ""); - + auto url=Documentation::CppReference::get_url(symbol); if(!url.empty()) return {url}; } - + if(data.empty()) Info::get().print("No symbol found at current cursor location"); - + return data; }; - + goto_next_diagnostic=[this]() { if(!parsed) { Info::get().print("Buffer is parsing"); @@ -1612,7 +1612,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file } place_cursor_at_next_diagnostic(); }; - + get_fix_its=[this]() { if(!parsed) { Info::get().print("Buffer is parsing"); @@ -1622,7 +1622,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file Info::get().print("No fix-its found in current buffer"); return fix_its; }; - + get_documentation_template=[this]() { if(!parsed) { Info::get().print("Buffer is parsing"); @@ -1643,22 +1643,22 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file auto first_line=tabs+"/**\n"; auto second_line=tabs+" * \n"; auto iter_offset=first_line.size()+second_line.size()-1; - + std::string param_lines; for(int c=0;c<clang_Cursor_getNumArguments(cursor.cx_cursor);++c) param_lines+=tabs+" * @param "+clangmm::Cursor(clang_Cursor_getArgument(cursor.cx_cursor, c)).get_spelling()+'\n'; - + std::string return_line; auto return_spelling=cursor.get_type().get_result().get_spelling(); if(!return_spelling.empty() && return_spelling!="void") return_line+=tabs+" * @return\n"; - + auto documentation=first_line+second_line; if(!param_lines.empty() || !return_line.empty()) documentation+=tabs+" *\n"; - + documentation+=param_lines+return_line+tabs+" */\n"; - + return std::tuple<Source::Offset, std::string, size_t>(source_offset, documentation, iter_offset); } else { @@ -1733,7 +1733,7 @@ void Source::ClangViewRefactor::tag_similar_identifiers(const Identifier &identi auto start_iter=get_buffer()->get_iter_at_line_index(offset.first.line-1, offset.first.index-1); auto end_iter=get_buffer()->get_iter_at_line_index(offset.second.line-1, offset.second.index-1); get_buffer()->apply_tag(similar_identifiers_tag, start_iter, end_iter); - similar_identifiers_marks.emplace_back(get_buffer()->create_mark(start_iter), get_buffer()->create_mark(end_iter)); + similar_identifiers_marks.emplace_back(get_buffer()->create_mark(start_iter), get_buffer()->create_mark(end_iter)); } last_tagged_identifier=identifier; } @@ -1756,7 +1756,7 @@ Source::ClangView::ClangView(const boost::filesystem::path &file_path, const Gli get_source_buffer()->set_highlight_syntax(true); get_source_buffer()->set_language(language); } - + do_delete_object.connect([this]() { if(delete_thread.joinable()) delete_thread.join(); @@ -1802,7 +1802,7 @@ void Source::ClangView::full_reparse() { void Source::ClangView::async_delete() { delayed_show_arguments_connection.disconnect(); - + views.erase(this); std::set<boost::filesystem::path> project_paths_in_use; for(auto &view: views) { @@ -1814,23 +1814,23 @@ void Source::ClangView::async_delete() { } Usages::Clang::erase_unused_caches(project_paths_in_use); Usages::Clang::cache_in_progress(); - + if(!get_buffer()->get_modified()) { if(full_reparse_needed) full_reparse(); else if(soft_reparse_needed) soft_reparse(); } - + auto before_parse_time=std::time(nullptr); delete_thread=std::thread([this, before_parse_time, project_paths_in_use=std::move(project_paths_in_use)] { while(!parsed) std::this_thread::sleep_for(std::chrono::milliseconds(10)); - + delayed_reparse_connection.disconnect(); parse_state=ParseState::STOP; dispatcher.disconnect(); - + if(get_buffer()->get_modified()) { std::ifstream stream(file_path.string(), std::ios::binary); if(stream) { @@ -1844,12 +1844,12 @@ void Source::ClangView::async_delete() { else clang_tokens=nullptr; } - + if(clang_tokens) { auto build=Project::Build::create(file_path); Usages::Clang::cache(build->get_project_path(), build->get_default_path(), file_path, before_parse_time, project_paths_in_use, clang_tu.get(), clang_tokens.get()); } - + if(full_reparse_thread.joinable()) full_reparse_thread.join(); if(parse_thread.joinable()) diff --git a/src/source_clang.h b/src/source_clang.h index 3aca95e7..8f1d26b4 100644 --- a/src/source_clang.h +++ b/src/source_clang.h @@ -14,13 +14,13 @@ namespace Source { protected: enum class ParseState {PROCESSING, RESTARTING, STOP}; enum class ParseProcessState {IDLE, STARTING, PREPROCESSING, PROCESSING, POSTPROCESSING}; - + public: ClangViewParse(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language); - + bool save() override; void configure() override; - + void soft_reparse(bool delayed=false) override; protected: Dispatcher dispatcher; @@ -29,30 +29,30 @@ namespace Source { std::unique_ptr<clangmm::Tokens> clang_tokens; std::vector<std::pair<clangmm::Offset, clangmm::Offset>> clang_tokens_offsets; sigc::connection delayed_reparse_connection; - + void show_type_tooltips(const Gdk::Rectangle &rectangle) override; - + std::vector<FixIt> fix_its; - + std::thread parse_thread; std::mutex parse_mutex; std::atomic<ParseState> parse_state; std::atomic<ParseProcessState> parse_process_state; - + CXCompletionString selected_completion_string=nullptr; private: Glib::ustring parse_thread_buffer; - + static const std::unordered_map<int, std::string> &clang_types(); void update_syntax(); std::map<int, Glib::RefPtr<Gtk::TextTag>> syntax_tags; void update_diagnostics(); std::vector<clangmm::Diagnostic> clang_diagnostics; - + static clangmm::Index clang_index; }; - + class ClangViewAutocomplete : public virtual ClangViewParse { public: ClangViewAutocomplete(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language); @@ -73,7 +73,7 @@ namespace Source { Identifier(std::string spelling_, const clangmm::Cursor &cursor) : kind(cursor.get_kind()), spelling(std::move(spelling_)), usr_extended(cursor.get_usr_extended()), cursor(cursor) {} Identifier() : kind(static_cast<clangmm::Cursor::Kind>(0)) {} - + operator bool() const { return static_cast<int>(kind)!=0; } bool operator==(const Identifier &rhs) const { return spelling==rhs.spelling && usr_extended==rhs.usr_extended; } bool operator!=(const Identifier &rhs) const { return !(*this==rhs); } @@ -88,20 +88,20 @@ namespace Source { private: Identifier get_identifier(); void wait_parsing(); - + std::list<std::pair<Glib::RefPtr<Gtk::TextMark>, Glib::RefPtr<Gtk::TextMark> > > similar_identifiers_marks; void tag_similar_identifiers(const Identifier &identifier); Glib::RefPtr<Gtk::TextTag> similar_identifiers_tag; Identifier last_tagged_identifier; }; - + class ClangView : public ClangViewAutocomplete, public ClangViewRefactor { public: ClangView(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language); - + void full_reparse() override; void async_delete(); - + private: Glib::Dispatcher do_delete_object; std::thread delete_thread; diff --git a/src/source_diff.cc b/src/source_diff.cc index df4df206..9d0ea809 100644 --- a/src/source_diff.cc +++ b/src/source_diff.cc @@ -39,7 +39,7 @@ Source::DiffView::DiffView(const boost::filesystem::path &file_path, const Glib: canonical_file_path=boost::filesystem::canonical(file_path, ec); if(ec) canonical_file_path=file_path; - + renderer->tag_added=get_buffer()->create_tag("git_added"); renderer->tag_modified=get_buffer()->create_tag("git_modified"); renderer->tag_removed=get_buffer()->create_tag("git_removed"); @@ -56,7 +56,7 @@ Source::DiffView::~DiffView() { monitor_changed_connection.disconnect(); delayed_buffer_changed_connection.disconnect(); delayed_monitor_changed_connection.disconnect(); - + parse_stop=true; if(parse_thread.joinable()) parse_thread.join(); @@ -75,30 +75,30 @@ void Source::DiffView::configure() { monitor_changed_connection.disconnect(); delayed_buffer_changed_connection.disconnect(); delayed_monitor_changed_connection.disconnect(); - + parse_stop=true; if(parse_thread.joinable()) parse_thread.join(); repository=nullptr; diff=nullptr; - + return; } else return; - + try { repository=Git::get_repository(this->file_path.parent_path()); } catch(const std::exception &) { return; } - + get_gutter(Gtk::TextWindowType::TEXT_WINDOW_LEFT)->insert(renderer.get(), -40); parse_state=ParseState::STARTING; parse_stop=false; monitor_changed=false; - + buffer_insert_connection=get_buffer()->signal_insert().connect([this](const Gtk::TextBuffer::iterator &iter ,const Glib::ustring &text, int) { //Do not perform git diff if no newline is added and line is already marked as added if(!iter.starts_line() && iter.has_tag(renderer->tag_added)) { @@ -127,12 +127,12 @@ void Source::DiffView::configure() { return false; }, 250); }, false); - + buffer_erase_connection=get_buffer()->signal_erase().connect([this](const Gtk::TextBuffer::iterator &start_iter, const Gtk::TextBuffer::iterator &end_iter) { //Do not perform git diff if start_iter and end_iter is at the same line in addition to the line is tagged added if(start_iter.get_line()==end_iter.get_line() && start_iter.has_tag(renderer->tag_added)) return; - + parse_state=ParseState::IDLE; delayed_buffer_changed_connection.disconnect(); delayed_buffer_changed_connection=Glib::signal_timeout().connect([this]() { @@ -140,7 +140,7 @@ void Source::DiffView::configure() { return false; }, 250); }, false); - + monitor_changed_connection=repository->monitor->signal_changed().connect([this](const Glib::RefPtr<Gio::File> &file, const Glib::RefPtr<Gio::File>&, Gio::FileMonitorEvent monitor_event) { @@ -155,7 +155,7 @@ void Source::DiffView::configure() { }, 500); } }); - + parse_thread=std::thread([this]() { std::string status_branch; try { @@ -170,7 +170,7 @@ void Source::DiffView::configure() { if(update_status_branch) update_status_branch(this); }); - + try { while(true) { while(!parse_stop && parse_state!=ParseState::STARTING && parse_state!=ParseState::PROCESSING) @@ -255,7 +255,7 @@ void Source::DiffView::configure() { void Source::DiffView::rename(const boost::filesystem::path &path) { Source::BaseView::rename(path); - + std::lock_guard<std::mutex> lock(canonical_file_path_mutex); boost::system::error_code ec; canonical_file_path=boost::filesystem::canonical(path, ec); @@ -326,7 +326,7 @@ void Source::DiffView::update_lines() { get_buffer()->remove_tag(renderer->tag_removed, get_buffer()->begin(), get_buffer()->end()); get_buffer()->remove_tag(renderer->tag_removed_below, get_buffer()->begin(), get_buffer()->end()); get_buffer()->remove_tag(renderer->tag_removed_above, get_buffer()->begin(), get_buffer()->end()); - + for(auto &added: lines.added) { auto start_iter=get_buffer()->get_iter_at_line(added.first); auto end_iter=get_iter_at_line_end(added.second-1); @@ -360,6 +360,6 @@ void Source::DiffView::update_lines() { } get_buffer()->apply_tag(renderer->tag_removed, removed_start, removed_end); } - + renderer->queue_draw(); } diff --git a/src/source_diff.h b/src/source_diff.h index c1465634..4b53e2a5 100644 --- a/src/source_diff.h +++ b/src/source_diff.h @@ -12,17 +12,17 @@ namespace Source { class DiffView : virtual public Source::BaseView { enum class ParseState {IDLE, STARTING, PREPROCESSING, PROCESSING, POSTPROCESSING}; - + class Renderer : public Gsv::GutterRenderer { public: Renderer(); - + Glib::RefPtr<Gtk::TextTag> tag_added; Glib::RefPtr<Gtk::TextTag> tag_modified; Glib::RefPtr<Gtk::TextTag> tag_removed; Glib::RefPtr<Gtk::TextTag> tag_removed_below; Glib::RefPtr<Gtk::TextTag> tag_removed_above; - + protected: void draw_vfunc(const Cairo::RefPtr<Cairo::Context> &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::TextIter &start, Gtk::TextIter &end, @@ -31,26 +31,26 @@ namespace Source { public: DiffView(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language); ~DiffView() override; - + void configure() override; - + void rename(const boost::filesystem::path &path) override; - + void git_goto_next_diff(); std::string git_get_diff_details(); - + /// Use canonical path to follow symbolic links boost::filesystem::path canonical_file_path; private: std::mutex canonical_file_path_mutex; - + std::unique_ptr<Renderer> renderer; Dispatcher dispatcher; - + std::shared_ptr<Git::Repository> repository; std::unique_ptr<Git::Repository::Diff> diff; std::unique_ptr<Git::Repository::Diff> get_diff(); - + std::thread parse_thread; std::atomic<ParseState> parse_state; std::mutex parse_mutex; @@ -62,7 +62,7 @@ namespace Source { sigc::connection delayed_buffer_changed_connection; sigc::connection delayed_monitor_changed_connection; std::atomic<bool> monitor_changed; - + Git::Repository::Diff::Lines lines; void update_lines(); }; diff --git a/src/source_language_protocol.cc b/src/source_language_protocol.cc index f6f5de98..5075c0c6 100644 --- a/src/source_language_protocol.cc +++ b/src/source_language_protocol.cc @@ -31,9 +31,9 @@ std::shared_ptr<LanguageProtocol::Client> LanguageProtocol::Client::get(const bo root_uri=build->get_project_path().string(); else root_uri=file_path.parent_path().string(); - + auto cache_id=root_uri+'|'+language_id; - + static std::unordered_map<std::string, std::weak_ptr<Client>> cache; static std::mutex mutex; std::lock_guard<std::mutex> lock(mutex); @@ -59,11 +59,11 @@ LanguageProtocol::Client::~Client() { result_processed.set_value(); }); result_processed.get_future().get(); - + std::unique_lock<std::mutex> lock(timeout_threads_mutex); for(auto &thread: timeout_threads) thread.join(); - + int exit_status=-1; for(size_t c=0;c<20;++c) { std::this_thread::sleep_for(std::chrono::milliseconds(500)); @@ -81,12 +81,12 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang std::unique_lock<std::mutex> lock(views_mutex); views.emplace(view); } - + std::lock_guard<std::mutex> lock(initialize_mutex); - + if(initialized) return capabilities; - + std::promise<void> result_processed; write_request(nullptr, "initialize", "\"processId\":"+std::to_string(process->get_id())+R"(,"rootUri":"file://)"+root_uri+R"(","capabilities":{"workspace":{"didChangeConfiguration":{"dynamicRegistration":true},"didChangeWatchedFiles":{"dynamicRegistration":true},"symbol":{"dynamicRegistration":true},"executeCommand":{"dynamicRegistration":true}},"textDocument":{"synchronization":{"dynamicRegistration":true,"willSave":true,"willSaveWaitUntil":true,"didSave":true},"completion":{"dynamicRegistration":true,"completionItem":{"snippetSupport":true}},"hover":{"dynamicRegistration":true},"signatureHelp":{"dynamicRegistration":true},"definition":{"dynamicRegistration":true},"references":{"dynamicRegistration":true},"documentHighlight":{"dynamicRegistration":true},"documentSymbol":{"dynamicRegistration":true},"codeAction":{"dynamicRegistration":true},"codeLens":{"dynamicRegistration":true},"formatting":{"dynamicRegistration":true},"rangeFormatting":{"dynamicRegistration":true},"onTypeFormatting":{"dynamicRegistration":true},"rename":{"dynamicRegistration":true},"documentLink":{"dynamicRegistration":true}}},"initializationOptions":{"omitInitBuild":true},"trace":"off")", [this, &result_processed](const boost::property_tree::ptree &result, bool error) { if(!error) { @@ -103,7 +103,7 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang capabilities.document_range_formatting=capabilities_pt->second.get<bool>("documentRangeFormattingProvider", false); capabilities.rename=capabilities_pt->second.get<bool>("renameProvider", false); } - + write_notification("initialized", ""); if(language_id=="rust") write_notification("workspace/didChangeConfiguration", R"("settings":{"rust":{"sysroot":null,"target":null,"rustflags":null,"clear_env_rust_log":true,"build_lib":null,"build_bin":null,"cfg_test":false,"unstable_features":false,"wait_to_build":500,"show_warnings":true,"goto_def_racer_fallback":false,"use_crate_blacklist":true,"build_on_save":false,"workspace_mode":true,"analyze_package":null,"features":[],"all_features":false,"no_default_features":false}})"); @@ -111,7 +111,7 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang result_processed.set_value(); }); result_processed.get_future().get(); - + initialized=true; return capabilities; } @@ -153,7 +153,7 @@ void LanguageProtocol::Client::parse_server_message() { } } } - + if(header_read) { server_message_stream.seekg(0, std::ios::end); size_t read_size=server_message_stream.tellg(); @@ -167,16 +167,16 @@ void LanguageProtocol::Client::parse_server_message() { server_message_stream.put(' '); } } - + server_message_stream.seekg(server_message_content_pos, std::ios::beg); boost::property_tree::ptree pt; boost::property_tree::read_json(server_message_stream, pt); - + if(output_messages_and_errors) { std::cout << "language server: "; boost::property_tree::write_json(std::cout, pt); } - + auto message_id=pt.get<size_t>("id", 0); auto result_it=pt.find("result"); auto error_it=pt.find("error"); @@ -220,11 +220,11 @@ void LanguageProtocol::Client::parse_server_message() { } } } - + server_message_stream=std::stringstream(); header_read=false; server_message_size=static_cast<size_t>(-1); - + tmp.seekg(0, std::ios::end); if(tmp.tellg()>0) { tmp.seekg(0, std::ios::beg); @@ -239,7 +239,7 @@ void LanguageProtocol::Client::write_request(Source::LanguageProtocolView *view, std::unique_lock<std::mutex> lock(read_write_mutex); if(function) { handlers.emplace(message_id, std::make_pair(view, std::move(function))); - + auto message_id=this->message_id; std::unique_lock<std::mutex> lock(timeout_threads_mutex); timeout_threads.emplace_back([this, message_id] { @@ -322,14 +322,14 @@ Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path configure(); get_source_buffer()->set_language(language); get_source_buffer()->set_highlight_syntax(true); - + similar_symbol_tag=get_buffer()->create_tag(); similar_symbol_tag->property_weight()=Pango::WEIGHT_ULTRAHEAVY; - + status_state="initializing..."; if(update_status_state) update_status_state(this); - + if(language_id=="javascript") { boost::filesystem::path project_path; auto build=Project::Build::create(file_path); @@ -344,24 +344,24 @@ Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path } } } - + initialize_thread=std::thread([this] { auto capabilities=client->initialize(this); - + if(!flow_coverage_executable.empty()) add_flow_coverage_tooltips(true); - + dispatcher.post([this, capabilities] { this->capabilities=capabilities; - + std::string text=get_buffer()->get_text(); escape_text(text); client->write_notification("textDocument/didOpen", R"("textDocument":{"uri":")"+uri+R"(","languageId":")"+language_id+R"(","version":)"+std::to_string(document_version++)+R"(,"text":")"+text+"\"}"); - + setup_autocomplete(); setup_navigation_and_refactoring(); Menu::get().toggle_menu_items(); - + if(status_state=="initializing...") { status_state=""; if(update_status_state) @@ -369,11 +369,11 @@ Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path } }); }); - + get_buffer()->signal_changed().connect([this] { get_buffer()->remove_tag(similar_symbol_tag, get_buffer()->begin(), get_buffer()->end()); }); - + get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { if(mark->get_name() == "insert") { delayed_tag_similar_symbols_connection.disconnect(); @@ -419,24 +419,24 @@ Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path Source::LanguageProtocolView::~LanguageProtocolView() { if(initialize_thread.joinable()) initialize_thread.join(); - + autocomplete.state=Autocomplete::State::IDLE; if(autocomplete.thread.joinable()) autocomplete.thread.join(); - + client->write_notification("textDocument/didClose", R"("textDocument":{"uri":")"+uri+"\"}"); client->close(this); - + client=nullptr; } bool Source::LanguageProtocolView::save() { if(!Source::View::save()) return false; - + if(!flow_coverage_executable.empty()) add_flow_coverage_tooltips(false); - + return true; } @@ -447,7 +447,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { bool has_style_file=false; auto style_file_search_path=this->file_path.parent_path(); auto style_file='.'+language_id+"-format"; - + while(true) { if(boost::filesystem::exists(style_file_search_path/style_file)) { has_style_file=true; @@ -457,11 +457,11 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { break; style_file_search_path=style_file_search_path.parent_path(); } - + if(!has_style_file && !continue_without_style_file) return; } - + class Replace { public: Offset start, end; @@ -469,7 +469,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { }; std::vector<Replace> replaces; std::promise<void> result_processed; - + std::string method; std::string params; std::string options("\"tabSize\":"+std::to_string(tab_size)+",\"insertSpaces\":"+(tab_char==' '?"true":"false")); @@ -483,7 +483,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { method="textDocument/formatting"; params=R"("textDocument":{"uri":")"+uri+R"("},"options":{)"+options+"}"; } - + client->write_request(this, method, params, [&replaces, &result_processed](const boost::property_tree::ptree &result, bool error) { if(!error) { for(auto it=result.begin();it!=result.end();++it) { @@ -506,7 +506,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { result_processed.set_value(); }); result_processed.get_future().get(); - + auto end_iter=get_buffer()->end(); if(replaces.size()==1 && replaces[0].start.line==0 && replaces[0].start.index==0 && @@ -529,7 +529,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { } }; } - + if(capabilities.definition) { get_declaration_location=[this]() { auto offset=get_declaration(get_buffer()->get_insert()->get_iter()); @@ -545,7 +545,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { return offsets; }; } - + if(capabilities.references) { get_usages=[this] { auto iter=get_buffer()->get_insert()->get_iter(); @@ -579,12 +579,12 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { result_processed.set_value(); }); result_processed.get_future().get(); - + auto embolden_token=[](std::string &line_, unsigned token_start_pos, unsigned token_end_pos) { Glib::ustring line=line_; if(token_start_pos>=line.size() || token_end_pos>=line.size()) return; - + //markup token as bold size_t pos=0; while((pos=line.find('&', pos))!=Glib::ustring::npos) { @@ -601,16 +601,16 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { } line.insert(token_end_pos, "</b>"); line.insert(token_start_pos, "<b>"); - + size_t start_pos=0; while(start_pos<line.size() && (line[start_pos]==' ' || line[start_pos]=='\t')) ++start_pos; if(start_pos>0) line.erase(0, start_pos); - + line_=line.raw(); }; - + std::map<boost::filesystem::path, std::vector<std::string>> file_lines; auto c=static_cast<size_t>(-1); for(auto &usage: usages) { @@ -651,36 +651,36 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { it=pair.first; } } - + if(usage.first.line<it->second.size()) { usage.second=it->second[usage.first.line]; embolden_token(usage.second, usage.first.index, end_offsets[c].index); } } } - + if(usages.empty()) Info::get().print("No symbol found at current cursor location"); - + return usages; }; } - + get_token_spelling=[this]() -> std::string { auto start=get_buffer()->get_insert()->get_iter(); auto end=start; auto previous=start; while(previous.backward_char() && ((*previous>='A' && *previous<='Z') || (*previous>='a' && *previous<='z') || (*previous>='0' && *previous<='9') || *previous=='_') && start.backward_char()) {} while(((*end>='A' && *end<='Z') || (*end>='a' && *end<='z') || (*end>='0' && *end<='9') || *end=='_') && end.forward_char()) {} - + if(start==end) { Info::get().print("No valid symbol found at current cursor location"); return ""; } - + return get_buffer()->get_text(start, end); }; - + if(capabilities.rename) { rename_similar_tokens=[this](const std::string &text) { class Usages { @@ -689,11 +689,11 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { std::unique_ptr<std::string> new_text; std::vector<std::pair<Offset, Offset>> offsets; }; - + auto previous_text=get_token_spelling(); if(previous_text.empty()) return; - + auto iter=get_buffer()->get_insert()->get_iter(); std::vector<Usages> usages; std::promise<void> result_processed; @@ -770,7 +770,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { result_processed.set_value(); }); result_processed.get_future().get(); - + std::vector<Usages*> usages_renamed; for(auto &usage: usages) { auto view_it=views.end(); @@ -843,7 +843,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { Terminal::get().print("Error: could not write to file "+usage.path.string()+'\n', true); } } - + if(!usages_renamed.empty()) { Terminal::get().print("Renamed "); Terminal::get().print(previous_text, true); @@ -853,7 +853,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { } }; } - + goto_next_diagnostic=[this]() { place_cursor_at_next_diagnostic(); }; @@ -899,14 +899,14 @@ void Source::LanguageProtocolView::update_diagnostics(std::vector<LanguageProtoc if(diagnostic.uri==uri) { auto start=get_iter_at_line_pos(diagnostic.offsets.first.line, diagnostic.offsets.first.index); auto end=get_iter_at_line_pos(diagnostic.offsets.second.line, diagnostic.offsets.second.index); - + if(start==end) { if(!end.is_end()) end.forward_char(); else start.forward_char(); } - + bool error=false; std::string severity_tag_name; if(diagnostic.severity>=2) { @@ -918,14 +918,14 @@ void Source::LanguageProtocolView::update_diagnostics(std::vector<LanguageProtoc num_errors++; error=true; } - + add_diagnostic_tooltip(start, end, diagnostic.spelling, error); } } - + for(auto &mark: flow_coverage_marks) add_diagnostic_tooltip(mark.first->get_iter(), mark.second->get_iter(), flow_coverage_message, false); - + status_diagnostics=std::make_tuple(num_warnings+num_flow_coverage_warnings, num_errors, num_fix_its); if(update_status_diagnostics) update_status_diagnostics(this); @@ -939,7 +939,7 @@ Gtk::TextIter Source::LanguageProtocolView::get_iter_at_line_pos(int line, int p void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rectangle) { if(!capabilities.hover) return; - + Gtk::TextIter iter; int location_x, location_y; window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, rectangle.get_x(), rectangle.get_y(), location_x, location_y); @@ -949,9 +949,9 @@ void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rect get_iter_location(iter, iter_rectangle); if(iter.ends_line() && location_x > iter_rectangle.get_x()) return; - + auto offset=iter.get_offset(); - + static int request_count=0; request_count++; auto current_request=request_count; @@ -989,18 +989,18 @@ void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rect auto create_tooltip_buffer=[this, offset, content]() { auto tooltip_buffer=Gtk::TextBuffer::create(get_buffer()->get_tag_table()); tooltip_buffer->insert(tooltip_buffer->get_insert()->get_iter(), *content); - + #ifdef JUCI_ENABLE_DEBUG if(language_id=="rust" && capabilities.definition) { if(Debug::LLDB::get().is_stopped()) { Glib::ustring value_type="Value"; - + auto start=get_buffer()->get_iter_at_offset(offset); auto end=start; auto previous=start; while(previous.backward_char() && ((*previous>='A' && *previous<='Z') || (*previous>='a' && *previous<='z') || (*previous>='0' && *previous<='9') || *previous=='_') && start.backward_char()) {} while(((*end>='A' && *end<='Z') || (*end>='a' && *end<='z') || (*end>='0' && *end<='9') || *end=='_') && end.forward_char()) {} - + auto offset=get_declaration(start); Glib::ustring debug_value=Debug::LLDB::get().get_value(get_buffer()->get_text(start, end), offset.file_path, offset.line+1, offset.index+1); if(debug_value.empty()) { @@ -1022,10 +1022,10 @@ void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rect } } #endif - + return tooltip_buffer; }; - + auto start=get_buffer()->get_iter_at_offset(offset); auto end=start; auto previous=start; @@ -1042,14 +1042,14 @@ void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rect void Source::LanguageProtocolView::tag_similar_symbols() { if(!capabilities.document_highlight && !capabilities.references) return; - + auto iter=get_buffer()->get_insert()->get_iter(); std::string method; if(capabilities.document_highlight) method="textDocument/documentHighlight"; else method="textDocument/references"; - + static int request_count=0; request_count++; auto current_request=request_count; @@ -1113,25 +1113,25 @@ Source::Offset Source::LanguageProtocolView::get_declaration(const Gtk::TextIter void Source::LanguageProtocolView::setup_autocomplete() { if(!capabilities.completion) return; - + non_interactive_completion=[this] { if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) return; autocomplete.run(); }; - + autocomplete.reparse=[this] { autocomplete_comment.clear(); autocomplete_insert.clear(); }; - + autocomplete.is_continue_key=[](guint keyval) { if((keyval>='0' && keyval<='9') || (keyval>='a' && keyval<='z') || (keyval>='A' && keyval<='Z') || keyval=='_') return true; - + return false; }; - + autocomplete.is_restart_key=[this](guint keyval) { auto iter=get_buffer()->get_insert()->get_iter(); iter.backward_chars(2); @@ -1139,13 +1139,13 @@ void Source::LanguageProtocolView::setup_autocomplete() { return true; return false; }; - + autocomplete.run_check=[this]() { auto iter=get_buffer()->get_insert()->get_iter(); iter.backward_char(); if(!is_code_iter(iter)) return false; - + std::string line=" "+get_line_before(); const static std::regex dot_or_arrow(R"(^.*[a-zA-Z0-9_\)\]\>"'](\.)([a-zA-Z0-9_]*)$)"); const static std::regex colon_colon(R"(^.*::([a-zA-Z0-9_]*)$)"); @@ -1185,27 +1185,27 @@ void Source::LanguageProtocolView::setup_autocomplete() { autocomplete.prefix=get_buffer()->get_text(iter, end_iter); return true; } - + return false; }; - + autocomplete.before_add_rows=[this] { status_state="autocomplete..."; if(update_status_state) update_status_state(this); }; - + autocomplete.after_add_rows=[this] { status_state=""; if(update_status_state) update_status_state(this); }; - + autocomplete.on_add_rows_error=[this] { autocomplete_comment.clear(); autocomplete_insert.clear(); }; - + autocomplete.add_rows=[this](std::string &buffer, int line_number, int column) { if(autocomplete.state==Autocomplete::State::STARTING) { autocomplete_comment.clear(); @@ -1269,7 +1269,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { result_processed.get_future().get(); } }; - + signal_key_press_event().connect([this](GdkEventKey *event) { if((event->keyval==GDK_KEY_Tab || event->keyval==GDK_KEY_ISO_Left_Tab) && (event->state&GDK_SHIFT_MASK)==0) { if(!autocomplete_marks.empty()) { @@ -1289,7 +1289,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { } return false; }, false); - + get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) { if(mark->get_name() == "insert") { if(!autocomplete_keep_marks) { @@ -1301,7 +1301,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { } } }); - + autocomplete.on_show = [this] { for(auto &pair: autocomplete_marks) { get_buffer()->delete_mark(pair.first); @@ -1310,17 +1310,17 @@ void Source::LanguageProtocolView::setup_autocomplete() { autocomplete_marks.clear(); hide_tooltips(); }; - + autocomplete.on_hide = [this] { autocomplete_comment.clear(); autocomplete_insert.clear(); }; - + autocomplete.on_select = [this](unsigned int index, const std::string &text, bool hide_window) { get_buffer()->erase(CompletionDialog::get()->start_mark->get_iter(), get_buffer()->get_insert()->get_iter()); if(hide_window) { Glib::ustring insert=autocomplete_insert[index]; - + // Do not insert function/template parameters if they already exist auto iter=get_buffer()->get_insert()->get_iter(); if(*iter=='(' || *iter=='<') { @@ -1329,7 +1329,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { insert=insert.substr(0, bracket_pos); } } - + // Find and add position marks that one can move to using tab-key size_t pos1=0; std::vector<std::pair<size_t, size_t>> mark_offsets; @@ -1371,7 +1371,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { else get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), text); }; - + autocomplete.get_tooltip = [this](unsigned int index) { return autocomplete_comment[index]; }; @@ -1389,7 +1389,7 @@ void Source::LanguageProtocolView::add_flow_coverage_tooltips(bool called_in_thr get_buffer()->delete_mark(mark.second); } flow_coverage_marks.clear(); - + if(exit_status==0) { boost::property_tree::ptree pt; try { @@ -1400,10 +1400,10 @@ void Source::LanguageProtocolView::add_flow_coverage_tooltips(bool called_in_thr auto start=get_iter_at_line_offset(start_pt.get<int>("line")-1, start_pt.get<int>("column")-1); auto end_pt=it->second.get_child("end"); auto end=get_iter_at_line_offset(end_pt.get<int>("line")-1, end_pt.get<int>("column")); - + add_diagnostic_tooltip(start, end, flow_coverage_message, false); ++num_flow_coverage_warnings; - + flow_coverage_marks.emplace_back(get_buffer()->create_mark(start), get_buffer()->create_mark(end)); } } diff --git a/src/source_language_protocol.h b/src/source_language_protocol.h index 0c647bc2..02878a7b 100644 --- a/src/source_language_protocol.h +++ b/src/source_language_protocol.h @@ -48,7 +48,7 @@ namespace LanguageProtocol { std::unordered_set<Source::LanguageProtocolView *> views; std::mutex views_mutex; - + std::mutex initialize_mutex; std::unique_ptr<TinyProcessLib::Process> process; @@ -73,7 +73,7 @@ namespace LanguageProtocol { bool initialized = false; Capabilities initialize(Source::LanguageProtocolView *view); void close(Source::LanguageProtocolView *view); - + void parse_server_message(); void write_request(Source::LanguageProtocolView *view, const std::string &method, const std::string ¶ms, std::function<void(const boost::property_tree::ptree &, bool)> &&function = nullptr); void write_notification(const std::string &method, const std::string ¶ms); @@ -87,11 +87,11 @@ namespace Source { LanguageProtocolView(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language, std::string language_id_); ~LanguageProtocolView() override; std::string uri; - + bool save() override; void update_diagnostics(std::vector<LanguageProtocol::Diagnostic> &&diagnostics); - + Gtk::TextIter get_iter_at_line_pos(int line, int pos) override; protected: @@ -100,22 +100,22 @@ namespace Source { private: std::string language_id; LanguageProtocol::Capabilities capabilities; - + std::shared_ptr<LanguageProtocol::Client> client; size_t document_version = 1; std::thread initialize_thread; Dispatcher dispatcher; - + void setup_navigation_and_refactoring(); void escape_text(std::string &text); void unescape_text(std::string &text); - + Glib::RefPtr<Gtk::TextTag> similar_symbol_tag; void tag_similar_symbols(); - + Offset get_declaration(const Gtk::TextIter &iter); Autocomplete autocomplete; @@ -124,7 +124,7 @@ namespace Source { std::vector<std::string> autocomplete_insert; std::list<std::pair<Glib::RefPtr<Gtk::TextBuffer::Mark>, Glib::RefPtr<Gtk::TextBuffer::Mark>>> autocomplete_marks; bool autocomplete_keep_marks = false; - + boost::filesystem::path flow_coverage_executable; std::vector<std::pair<Glib::RefPtr<Gtk::TextMark>, Glib::RefPtr<Gtk::TextMark>>> flow_coverage_marks; const std::string flow_coverage_message="Not covered by Flow"; diff --git a/src/source_spellcheck.cc b/src/source_spellcheck.cc index fea72487..99cf942e 100644 --- a/src/source_spellcheck.cc +++ b/src/source_spellcheck.cc @@ -12,32 +12,32 @@ Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path, spellcheck_checker=nullptr; spellcheck_error_tag=get_buffer()->create_tag("spellcheck_error"); spellcheck_error_tag->property_underline()=Pango::Underline::UNDERLINE_ERROR; - + signal_key_press_event().connect([](GdkEventKey *event) { if(SelectionDialog::get() && SelectionDialog::get()->is_visible()) { if(SelectionDialog::get()->on_key_press(event)) return true; } - + return false; }, false); - + //The following signal is added in case SpellCheckView is not subclassed signal_key_press_event().connect([this](GdkEventKey *event) { last_keyval=event->keyval; return false; }); - + get_buffer()->signal_changed().connect([this]() { if(spellcheck_checker==nullptr) return; - + delayed_spellcheck_suggestions_connection.disconnect(); - + auto iter=get_buffer()->get_insert()->get_iter(); if(!is_word_iter(iter) && !iter.starts_line()) iter.backward_char(); - + if(disable_spellcheck) { if(is_word_iter(iter)) { auto word=get_word(iter); @@ -45,7 +45,7 @@ Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path, } return; } - + if(!is_code_iter(iter)) { if(last_keyval==GDK_KEY_Return || last_keyval==GDK_KEY_KP_Enter) { auto previous_line_iter=iter; @@ -91,7 +91,7 @@ Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path, while(iter!=get_buffer()->end()) { if(!get_source_buffer()->iter_forward_to_context_class_toggle(iter, "no-spell-check")) iter=get_buffer()->end(); - + spell_check=!spell_check; if(!spell_check) begin_no_spellcheck_iter=iter; @@ -100,7 +100,7 @@ Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path, } return false; } - + bool spell_check=get_source_buffer()->iter_has_context_class(iter, "string") || get_source_buffer()->iter_has_context_class(iter, "comment"); if(!spell_check) begin_no_spellcheck_iter=iter; @@ -111,7 +111,7 @@ Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path, iter1=get_buffer()->end(); if(!get_source_buffer()->iter_forward_to_context_class_toggle(iter2, "comment")) iter2=get_buffer()->end(); - + if(iter2<iter1) iter=iter2; else @@ -125,15 +125,15 @@ Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path, return false; }, 1000); }); - + // In case of for instance text paste or undo/redo get_buffer()->signal_insert().connect([this](const Gtk::TextIter &start_iter, const Glib::ustring &inserted_string, int) { if(spellcheck_checker==nullptr) return; - + if(!disable_spellcheck) return; - + auto iter=start_iter; if(!is_word_iter(iter) && !iter.starts_line()) iter.backward_char(); @@ -142,11 +142,11 @@ Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path, get_buffer()->remove_tag(spellcheck_error_tag, word.first, word.second); } }, false); - + get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator& iter, const Glib::RefPtr<Gtk::TextBuffer::Mark>& mark) { if(spellcheck_checker==nullptr) return; - + if(mark->get_name()=="insert") { if(SelectionDialog::get()) SelectionDialog::get()->hide(); @@ -180,24 +180,24 @@ Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path, }, 500); } }); - + get_buffer()->signal_mark_set().connect([](const Gtk::TextBuffer::iterator& iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark>& mark) { if(mark->get_name()=="insert") { if(SelectionDialog::get()) SelectionDialog::get()->hide(); } }); - + signal_focus_out_event().connect([this](GdkEventFocus* event) { delayed_spellcheck_suggestions_connection.disconnect(); return false; }); - + signal_leave_notify_event().connect([this](GdkEventCrossing*) { delayed_spellcheck_suggestions_connection.disconnect(); return false; }); - + signal_tag_added_connection=get_buffer()->get_tag_table()->signal_tag_added().connect([this](const Glib::RefPtr<Gtk::TextTag> &tag) { if(tag->property_name()=="gtksourceview:context-classes:comment") comment_tag=tag; @@ -219,10 +219,10 @@ Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path, Source::SpellCheckView::~SpellCheckView() { delayed_spellcheck_suggestions_connection.disconnect(); delayed_spellcheck_error_clear.disconnect(); - + if(spellcheck_checker!=nullptr) delete_aspell_speller(spellcheck_checker); - + signal_tag_added_connection.disconnect(); signal_tag_removed_connection.disconnect(); } @@ -257,7 +257,7 @@ void Source::SpellCheckView::spellcheck(const Gtk::TextIter& start, const Gtk::T if(is_word_iter(iter)) { auto word=get_word(iter); spellcheck_word(word.first, word.second); - iter=word.second; + iter=word.second; } iter.forward_char(); } @@ -273,7 +273,7 @@ void Source::SpellCheckView::spellcheck() { while(iter!=get_buffer()->end()) { if(!get_source_buffer()->iter_forward_to_context_class_toggle(iter, "no-spell-check")) iter=get_buffer()->end(); - + spell_check=!spell_check; if(spell_check) begin_spellcheck_iter=iter; @@ -292,7 +292,7 @@ void Source::SpellCheckView::spellcheck() { iter1=get_buffer()->end(); if(!get_source_buffer()->iter_forward_to_context_class_toggle(iter2, "comment")) iter2=get_buffer()->end(); - + if(iter2<iter1) iter=iter2; else @@ -447,7 +447,7 @@ bool Source::SpellCheckView::is_word_iter(const Gtk::TextIter& iter) { std::pair<Gtk::TextIter, Gtk::TextIter> Source::SpellCheckView::get_word(Gtk::TextIter iter) { auto start=iter; auto end=iter; - + while(is_word_iter(iter)) { start=iter; if(!iter.backward_char()) @@ -457,7 +457,7 @@ std::pair<Gtk::TextIter, Gtk::TextIter> Source::SpellCheckView::get_word(Gtk::Te if(!end.forward_char()) break; } - + return {start, end}; } @@ -470,7 +470,7 @@ void Source::SpellCheckView::spellcheck_word(Gtk::TextIter start, Gtk::TextIter end.backward_char(); } } - + auto word=get_buffer()->get_text(start, end); if(word.size()>0) { auto correct = aspell_speller_check(spellcheck_checker, word.data(), word.bytes()); @@ -483,16 +483,16 @@ void Source::SpellCheckView::spellcheck_word(Gtk::TextIter start, Gtk::TextIter std::vector<std::string> Source::SpellCheckView::get_spellcheck_suggestions(const Gtk::TextIter& start, const Gtk::TextIter& end) { auto word_with_error=get_buffer()->get_text(start, end); - + const AspellWordList *suggestions = aspell_speller_suggest(spellcheck_checker, word_with_error.data(), word_with_error.bytes()); AspellStringEnumeration *elements = aspell_word_list_elements(suggestions); - + std::vector<std::string> words; const char *word; while ((word = aspell_string_enumeration_next(elements))!= nullptr) { words.emplace_back(word); } delete_aspell_string_enumeration(elements); - + return words; } diff --git a/src/source_spellcheck.h b/src/source_spellcheck.h index eb8e9493..a9b491dd 100644 --- a/src/source_spellcheck.h +++ b/src/source_spellcheck.h @@ -7,28 +7,28 @@ namespace Source { public: SpellCheckView(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &language); ~SpellCheckView() override; - + void configure() override; void hide_dialogs() override; - + void spellcheck(); void remove_spellcheck_errors(); void goto_next_spellcheck_error(); - + protected: bool is_code_iter(const Gtk::TextIter &iter); bool spellcheck_all=false; guint last_keyval=0; - + Glib::RefPtr<Gtk::TextTag> comment_tag; Glib::RefPtr<Gtk::TextTag> string_tag; Glib::RefPtr<Gtk::TextTag> no_spell_check_tag; private: Glib::RefPtr<Gtk::TextTag> spellcheck_error_tag; - + sigc::connection signal_tag_added_connection; sigc::connection signal_tag_removed_connection; - + static AspellConfig* spellcheck_config; AspellCanHaveError *spellcheck_possible_err; AspellSpeller *spellcheck_checker; @@ -38,7 +38,7 @@ namespace Source { std::vector<std::string> get_spellcheck_suggestions(const Gtk::TextIter& start, const Gtk::TextIter& end); sigc::connection delayed_spellcheck_suggestions_connection; sigc::connection delayed_spellcheck_error_clear; - + void spellcheck(const Gtk::TextIter& start, const Gtk::TextIter& end); }; } diff --git a/src/terminal.cc b/src/terminal.cc index 3d885fb4..d018e9bf 100644 --- a/src/terminal.cc +++ b/src/terminal.cc @@ -10,18 +10,18 @@ Terminal::Terminal() { set_editable(false); - + bold_tag=get_buffer()->create_tag(); bold_tag->property_weight()=Pango::WEIGHT_ULTRAHEAVY; - + link_tag=get_buffer()->create_tag(); link_tag->property_underline()=Pango::Underline::UNDERLINE_SINGLE; - + link_mouse_cursor=Gdk::Cursor::create(Gdk::CursorType::HAND1); default_mouse_cursor=Gdk::Cursor::create(Gdk::CursorType::XTERM); } -int Terminal::process(const std::string &command, const boost::filesystem::path &path, bool use_pipes) { +int Terminal::process(const std::string &command, const boost::filesystem::path &path, bool use_pipes) { std::unique_ptr<TinyProcessLib::Process> process; if(use_pipes) process=std::make_unique<TinyProcessLib::Process>(command, path.string(), [this](const char* bytes, size_t n) { @@ -31,12 +31,12 @@ int Terminal::process(const std::string &command, const boost::filesystem::path }); else process=std::make_unique<TinyProcessLib::Process>(command, path.string()); - + if(process->get_id()<=0) { async_print("Error: failed to run command: " + command + "\n", true); return -1; } - + return process->get_exit_status(); } @@ -56,12 +56,12 @@ int Terminal::process(std::istream &stdin_stream, std::ostream &stdout_stream, c else async_print(std::string(bytes, n), true); }, true); - + if(process.get_id()<=0) { async_print("Error: failed to run command: " + command + "\n", true); return -1; } - + char buffer[131072]; for(;;) { stdin_stream.readsome(buffer, 131072); @@ -73,7 +73,7 @@ int Terminal::process(std::istream &stdin_stream, std::ostream &stdout_stream, c } } process.close_stdin(); - + return process.get_exit_status(); } @@ -100,9 +100,9 @@ void Terminal::async_process(const std::string &command, const boost::filesystem processes.emplace_back(process); processes_lock.unlock(); } - + auto exit_status=process->get_exit_status(); - + processes_lock.lock(); for(auto it=processes.begin();it!=processes.end();it++) { if((*it)->get_id()==pid) { @@ -112,7 +112,7 @@ void Terminal::async_process(const std::string &command, const boost::filesystem } stdin_buffer.clear(); processes_lock.unlock(); - + if(callback) callback(exit_status); }); @@ -142,7 +142,7 @@ bool Terminal::on_motion_notify_event(GdkEventMotion *event) { get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->set_cursor(link_mouse_cursor); else get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->set_cursor(default_mouse_cursor); - + // Workaround for drag-and-drop crash on MacOS // TODO 2018: check if this bug has been fixed #ifdef __APPLE__ @@ -159,7 +159,7 @@ bool Terminal::on_motion_notify_event(GdkEventMotion *event) { #else return Gtk::TextView::on_motion_notify_event(event); #endif - + return Gtk::TextView::on_motion_notify_event(event); } @@ -260,14 +260,14 @@ size_t Terminal::print(const std::string &message, bool bold){ #else Glib::ustring umessage=message; #endif - + Glib::ustring::iterator iter; while(!umessage.validate(iter)) { auto next_char_iter=iter; next_char_iter++; umessage.replace(iter, next_char_iter, "?"); } - + auto start_mark=get_buffer()->create_mark(get_buffer()->get_iter_at_line(get_buffer()->end().get_line())); if(bold) get_buffer()->insert_with_tag(get_buffer()->end(), umessage, bold_tag); @@ -276,15 +276,15 @@ size_t Terminal::print(const std::string &message, bool bold){ auto start_iter=start_mark->get_iter(); get_buffer()->delete_mark(start_mark); auto end_iter=get_buffer()->get_insert()->get_iter(); - + apply_link_tags(start_iter, end_iter); - + if(get_buffer()->get_line_count()>Config::get().terminal.history_size) { int lines=get_buffer()->get_line_count()-Config::get().terminal.history_size; get_buffer()->erase(get_buffer()->begin(), get_buffer()->get_iter_at_line(lines)); deleted_lines+=static_cast<size_t>(lines); } - + return static_cast<size_t>(get_buffer()->end().get_line())+deleted_lines; } @@ -298,7 +298,7 @@ void Terminal::async_print(size_t line_nr, const std::string &message) { dispatcher.post([this, line_nr, message] { if(line_nr<deleted_lines) return; - + Glib::ustring umessage=message; Glib::ustring::iterator iter; while(!umessage.validate(iter)) { @@ -306,7 +306,7 @@ void Terminal::async_print(size_t line_nr, const std::string &message) { 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); @@ -315,7 +315,7 @@ void Terminal::async_print(size_t line_nr, const std::string &message) { void Terminal::configure() { link_tag->property_foreground_rgba()=get_style_context()->get_color(Gtk::StateFlags::STATE_FLAG_LINK); - + if(Config::get().terminal.font.size()>0) { override_font(Pango::FontDescription(Config::get().terminal.font)); } @@ -352,7 +352,7 @@ bool Terminal::on_button_press_event(GdkEventButton* button_event) { boost::filesystem::path path=std::get<2>(link); std::string line=std::get<3>(link); std::string index=std::get<4>(link); - + if(!path.empty() && *path.begin()=="~") { // boost::filesystem does not recognize ~ boost::filesystem::path corrected_path; corrected_path=filesystem::get_home_path(); @@ -364,7 +364,7 @@ bool Terminal::on_button_press_event(GdkEventButton* button_event) { path=corrected_path; } } - + if(path.is_relative()) { if(Project::current) { auto absolute_path=Project::current->build->get_default_path()/path; diff --git a/src/terminal.h b/src/terminal.h index 53a4df4f..9ce767df 100644 --- a/src/terminal.h +++ b/src/terminal.h @@ -15,19 +15,19 @@ class Terminal : public Gtk::TextView { static Terminal singleton; return singleton; } - + int process(const std::string &command, const boost::filesystem::path &path="", bool use_pipes=true); int process(std::istream &stdin_stream, std::ostream &stdout_stream, const std::string &command, const boost::filesystem::path &path="", std::ostream *stderr_stream=nullptr); void async_process(const std::string &command, const boost::filesystem::path &path="", const std::function<void(int exit_status)> &callback=nullptr, bool quiet=false); void kill_last_async_process(bool force=false); void kill_async_processes(bool force=false); - + size_t print(const std::string &message, bool bold=false); void async_print(const std::string &message, bool bold=false); void async_print(size_t line_nr, const std::string &message); - + void configure(); - + void clear(); protected: bool on_motion_notify_event (GdkEventMotion* motion_event) override; @@ -40,7 +40,7 @@ class Terminal : public Gtk::TextView { Glib::RefPtr<Gdk::Cursor> link_mouse_cursor; Glib::RefPtr<Gdk::Cursor> default_mouse_cursor; size_t deleted_lines=0; - + std::tuple<size_t, size_t, std::string, std::string, std::string> find_link(const std::string &line); void apply_link_tags(const Gtk::TextIter &start_iter, const Gtk::TextIter &end_iter); diff --git a/src/tooltips.cc b/src/tooltips.cc index 841016cb..6f588024 100644 --- a/src/tooltips.cc +++ b/src/tooltips.cc @@ -37,30 +37,30 @@ void Tooltip::update() { void Tooltip::show(bool disregard_drawn, const std::function<void()> &on_motion) { Tooltips::shown_tooltips.emplace(this); - + if(!window) { //init window window=std::make_unique<Gtk::Window>(Gtk::WindowType::WINDOW_POPUP); - + auto g_application=g_application_get_default(); auto gio_application=Glib::wrap(g_application, true); auto application=Glib::RefPtr<Gtk::Application>::cast_static(gio_application); window->set_transient_for(*application->get_active_window()); - + window->set_type_hint(Gdk::WindowTypeHint::WINDOW_TYPE_HINT_TOOLTIP); - + window->set_events(Gdk::POINTER_MOTION_MASK); window->property_decorated()=false; window->set_accept_focus(false); window->set_skip_taskbar_hint(true); window->set_default_size(0, 0); - + window->signal_motion_notify_event().connect([on_motion](GdkEventMotion *event) { if(on_motion) on_motion(); return false; }); - + window->get_style_context()->add_class("juci_tooltip_window"); auto visual = window->get_screen()->get_rgba_visual(); if(visual) @@ -72,11 +72,11 @@ void Tooltip::show(bool disregard_drawn, const std::function<void()> &on_motion) text_buffer=create_tooltip_buffer(); wrap_lines(); - + auto tooltip_text_view=Gtk::manage(new Gtk::TextView(text_buffer)); tooltip_text_view->get_style_context()->add_class("juci_tooltip_text_view"); tooltip_text_view->set_editable(false); - + #if GTK_VERSION_GE(3, 20) box->add(*tooltip_text_view); #else @@ -90,7 +90,7 @@ void Tooltip::show(bool disregard_drawn, const std::function<void()> &on_motion) layout->get_pixel_size(size.first, size.second); size.first+=6; // 2xpadding size.second+=8; // 2xpadding + 2 - + window->signal_realize().connect([this] { if(!text_view) { auto &dialog=SelectionDialog::get(); @@ -107,7 +107,7 @@ void Tooltip::show(bool disregard_drawn, const std::function<void()> &on_motion) window->move(rectangle.get_x(), rectangle.get_y()); }); } - + int root_x=0, root_y=0; if(text_view) { //Adjust if tooltip is left of text_view @@ -116,7 +116,7 @@ void Tooltip::show(bool disregard_drawn, const std::function<void()> &on_motion) int visible_x, visible_y; text_view->buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, visible_rect.get_x(), visible_rect.get_y(), visible_x, visible_y); auto activation_rectangle_x=std::max(activation_rectangle.get_x(), visible_x); - + text_view->get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->get_root_coords(activation_rectangle_x, activation_rectangle.get_y(), root_x, root_y); root_x-=3; // -1xpadding if(root_y<size.second) @@ -126,7 +126,7 @@ void Tooltip::show(bool disregard_drawn, const std::function<void()> &on_motion) rectangle.set_y(std::max(0, root_y-size.second)); rectangle.set_width(size.first); rectangle.set_height(size.second); - + if(!disregard_drawn) { if(Tooltips::drawn_tooltips_rectangle.get_width()!=0) { if(rectangle.intersects(Tooltips::drawn_tooltips_rectangle)) { @@ -141,7 +141,7 @@ void Tooltip::show(bool disregard_drawn, const std::function<void()> &on_motion) else Tooltips::drawn_tooltips_rectangle=rectangle; } - + if(window->get_realized()) window->move(rectangle.get_x(), rectangle.get_y()); window->show_all(); @@ -180,9 +180,9 @@ void Tooltip::hide(const std::pair<int, int> &last_mouse_pos, const std::pair<in void Tooltip::wrap_lines() { if(!text_buffer) return; - + auto iter=text_buffer->begin(); - + while(iter) { auto last_space=text_buffer->end(); bool end=false; @@ -212,7 +212,7 @@ void Tooltip::wrap_lines() { last_space.forward_char(); text_buffer->erase(last_space_p, last_space); text_buffer->insert(mark->get_iter(), "\n"); - + iter=mark->get_iter(); iter.forward_char(); diff --git a/src/tooltips.h b/src/tooltips.h index 26f88965..fc43ee72 100644 --- a/src/tooltips.h +++ b/src/tooltips.h @@ -10,25 +10,25 @@ class Tooltip { Tooltip(std::function<Glib::RefPtr<Gtk::TextBuffer>()> create_tooltip_buffer_, Gtk::TextView *text_view, Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark_, Glib::RefPtr<Gtk::TextBuffer::Mark> end_mark_); Tooltip(std::function<Glib::RefPtr<Gtk::TextBuffer>()> create_tooltip_buffer_) : Tooltip(std::move(create_tooltip_buffer_), nullptr, Glib::RefPtr<Gtk::TextBuffer::Mark>(), Glib::RefPtr<Gtk::TextBuffer::Mark>()) {} ~Tooltip(); - + void update(); void show(bool disregard_drawn=false, const std::function<void()> &on_motion=nullptr); void hide(const std::pair<int, int> &last_mouse_pos = {-1, -1}, const std::pair<int, int> &mouse_pos = {-1, -1}); - + Gdk::Rectangle activation_rectangle; Glib::RefPtr<Gtk::TextBuffer::Mark> start_mark; Glib::RefPtr<Gtk::TextBuffer::Mark> end_mark; - + Glib::RefPtr<Gtk::TextBuffer> text_buffer; private: std::unique_ptr<Gtk::Window> window; void wrap_lines(); - + std::function<Glib::RefPtr<Gtk::TextBuffer>()> create_tooltip_buffer; Gtk::TextView *text_view; std::pair<int, int> size; Gdk::Rectangle rectangle; - + bool shown=false; }; @@ -37,19 +37,19 @@ class Tooltips { static std::set<Tooltip*> shown_tooltips; static Gdk::Rectangle drawn_tooltips_rectangle; static void init() {drawn_tooltips_rectangle=Gdk::Rectangle();} - + void show(const Gdk::Rectangle& rectangle, bool disregard_drawn=false); void show(bool disregard_drawn=false); void hide(const std::pair<int, int> &last_mouse_pos = {-1, -1}, const std::pair<int, int> &mouse_pos = {-1, -1}); void clear() {tooltip_list.clear();}; - + template<typename... Ts> void emplace_back(Ts&&... params) { tooltip_list.emplace_back(std::forward<Ts>(params)...); } - + std::function<void()> on_motion; - + private: std::list<Tooltip> tooltip_list; }; diff --git a/src/usages_clang.cc b/src/usages_clang.cc index fd50f372..ac1a007a 100644 --- a/src/usages_clang.cc +++ b/src/usages_clang.cc @@ -660,7 +660,7 @@ std::pair<Usages::Clang::PathSet, Usages::Clang::PathSet> Usages::Clang::find_po auto path_all_includes = get_all_includes(path_with_spelling, paths_includes); if((path_all_includes.find(path) != path_all_includes.end() || path_with_spelling == path)) { potential_paths.emplace(path_with_spelling); - + for(auto &include : path_all_includes) all_includes.emplace(include); } @@ -670,7 +670,7 @@ std::pair<Usages::Clang::PathSet, Usages::Clang::PathSet> Usages::Clang::find_po if(first) { for(auto &path_with_spelling : paths_with_spelling) { potential_paths.emplace(path_with_spelling); - + auto path_all_includes = get_all_includes(path_with_spelling, paths_includes); for(auto &include : path_all_includes) all_includes.emplace(include); diff --git a/src/window.cc b/src/window.cc index 83f4a889..9e0fb579 100644 --- a/src/window.cc +++ b/src/window.cc @@ -13,10 +13,10 @@ Window::Window() { Gsv::init(); - + set_title("juCi++"); set_events(Gdk::POINTER_MOTION_MASK|Gdk::FOCUS_CHANGE_MASK|Gdk::SCROLL_MASK|Gdk::LEAVE_NOTIFY_MASK); - + auto provider = Gtk::CssProvider::create(); auto screen = get_screen(); std::string border_radius_style; @@ -35,14 +35,14 @@ Window::Window() { .juci_tooltip_box {)"+border_radius_style+R"(padding: 3px;} )"); get_style_context()->add_provider_for_screen(screen, provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); - + set_menu_actions(); configure(); Menu::get().toggle_menu_items(); - + Menu::get().right_click_line_menu->attach_to_widget(*this); Menu::get().right_click_selected_menu->attach_to_widget(*this); - + EntryBox::get().signal_hide().connect([]() { if(auto view=Notebook::get().get_current_view()) view->grab_focus(); @@ -57,16 +57,16 @@ Window::Window() { } Menu::get().toggle_menu_items(); - + Directories::get().select(view->file_path); - + if(view->full_reparse_needed) view->full_reparse(); else if(view->soft_reparse_needed) view->soft_reparse(); - + Notebook::get().update_status(view); - + #ifdef JUCI_ENABLE_DEBUG if(Project::debugging) Project::debug_update_stop(); @@ -89,11 +89,11 @@ Window::Window() { Notebook::get().update_status(view); else { Notebook::get().clear_status(); - + Menu::get().toggle_menu_items(); } }; - + signal_focus_out_event().connect([](GdkEventFocus *event) { if(auto view=Notebook::get().get_current_view()) { view->hide_tooltips(); @@ -101,7 +101,7 @@ Window::Window() { } return false; }); - + signal_hide().connect([]{ while(!Source::View::non_deleted_views.empty()) { while(Gtk::Main::events_pending()) @@ -110,21 +110,21 @@ Window::Window() { // TODO 2022 (after Debian Stretch LTS has ended, see issue #354): remove: Project::current=nullptr; }); - + Gtk::Settings::get_default()->connect_property_changed("gtk-theme-name", [] { Directories::get().update(); if(auto view=Notebook::get().get_current_view()) Notebook::get().update_status(view); }); - + about.signal_response().connect([this](int d){ about.hide(); }); - + about.set_logo_icon_name("juci"); about.set_version(Config::get().window.version); about.set_authors({"(in order of appearance)", - "Ted Johan Kristoffersen", + "Ted Johan Kristoffersen", "Jørgen Lien Sellæg", "Geir Morten Larsen", "Ole Christian Eidheim"}); @@ -148,7 +148,7 @@ void Window::configure() { css_provider_theme=Gtk::CssProvider::get_named(Config::get().window.theme_name, Config::get().window.theme_variant); //TODO: add check if theme exists, or else write error to terminal Gtk::StyleContext::add_provider_for_screen(screen, css_provider_theme, GTK_STYLE_PROVIDER_PRIORITY_SETTINGS); - + auto style_scheme_manager=Source::StyleSchemeManager::get_default(); if(css_provider_tooltips) Gtk::StyleContext::remove_provider_for_screen(screen, css_provider_tooltips); @@ -173,7 +173,7 @@ void Window::configure() { ".juci_tooltip_text_view *:not(:selected) {color: "+foreground_value+";background-color: "+background_value+";}"); #endif get_style_context()->add_provider_for_screen(screen, css_provider_tooltips, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); - + Menu::get().set_keys(); Terminal::get().configure(); Directories::get().update(); @@ -183,7 +183,7 @@ void Window::configure() { void Window::set_menu_actions() { auto &menu = Menu::get(); - + menu.add_action("about", [this]() { about.show(); about.present(); @@ -194,7 +194,7 @@ void Window::set_menu_actions() { menu.add_action("quit", [this]() { close(); }); - + menu.add_action("file_new_file", []() { boost::filesystem::path path = Dialog::new_file(Notebook::get().get_current_folder()); if(path!="") { @@ -295,7 +295,7 @@ void Window::set_menu_actions() { Terminal::get().print("Error: Could not create project "+project_path.string()+"\n", true); } }); - + menu.add_action("file_open_file", []() { auto folder_path=Notebook::get().get_current_folder(); if(auto view=Notebook::get().get_current_view()) { @@ -311,7 +311,7 @@ void Window::set_menu_actions() { if (path!="" && boost::filesystem::exists(path)) Directories::get().open(path); }); - + menu.add_action("file_reload_file", []() { if(auto view=Notebook::get().get_current_view()) { if(boost::filesystem::exists(view->file_path)) { @@ -326,12 +326,12 @@ void Window::set_menu_actions() { Terminal::get().print("Error: "+view->file_path.string()+" does not exist\n", true); return; } - + if(view->load()) view->full_reparse(); } }); - + menu.add_action("file_save", [this]() { if(auto view=Notebook::get().get_current_view()) { if(Notebook::get().save_current()) { @@ -363,15 +363,15 @@ void Window::set_menu_actions() { } } }); - + menu.add_action("file_print", [this]() { if(auto view=Notebook::get().get_current_view()) { auto print_operation=Gtk::PrintOperation::create(); auto print_compositor=Gsv::PrintCompositor::create(*view); - + print_operation->set_job_name(view->file_path.filename().string()); print_compositor->set_wrap_mode(Gtk::WrapMode::WRAP_WORD_CHAR); - + print_operation->signal_begin_print().connect([print_operation, print_compositor](const Glib::RefPtr<Gtk::PrintContext>& print_context) { while(!print_compositor->paginate(print_context)); print_operation->set_n_pages(print_compositor->get_n_pages()); @@ -379,11 +379,11 @@ void Window::set_menu_actions() { print_operation->signal_draw_page().connect([print_compositor](const Glib::RefPtr<Gtk::PrintContext>& print_context, int page_nr) { print_compositor->draw_page(print_context, page_nr); }); - + print_operation->run(Gtk::PRINT_OPERATION_ACTION_PRINT_DIALOG, *this); } }); - + menu.add_action("edit_undo", []() { if(auto view=Notebook::get().get_current_view()) { auto undo_manager = view->get_source_buffer()->get_undo_manager(); @@ -408,7 +408,7 @@ void Window::set_menu_actions() { } } }); - + menu.add_action("edit_cut", [this]() { // Return if a shown tooltip has selected text for(auto tooltip: Tooltips::shown_tooltips) { @@ -416,7 +416,7 @@ void Window::set_menu_actions() { if(buffer && buffer->get_has_selection()) return; } - + auto widget=get_focus(); if(auto entry=dynamic_cast<Gtk::Entry*>(widget)) entry->cut_clipboard(); @@ -452,7 +452,7 @@ void Window::set_menu_actions() { return; } } - + auto widget=get_focus(); if(auto entry=dynamic_cast<Gtk::Entry*>(widget)) entry->copy_clipboard(); @@ -487,11 +487,11 @@ void Window::set_menu_actions() { view->get_buffer()->paste_clipboard(Gtk::Clipboard::get()); } }); - + menu.add_action("edit_find", [this]() { search_and_replace_entry(); }); - + menu.add_action("source_spellcheck", []() { if(auto view=Notebook::get().get_current_view()) { view->remove_spellcheck_errors(); @@ -506,7 +506,7 @@ void Window::set_menu_actions() { if(auto view=Notebook::get().get_current_view()) view->goto_next_spellcheck_error(); }); - + menu.add_action("source_git_next_diff", []() { if(auto view=Notebook::get().get_current_view()) view->git_goto_next_diff(); @@ -533,7 +533,7 @@ void Window::set_menu_actions() { } } }); - + menu.add_action("source_indentation_set_buffer_tab", [this]() { set_tab_entry(); }); @@ -546,7 +546,7 @@ void Window::set_menu_actions() { view->hide_tooltips(); } }); - + menu.add_action("source_goto_line", [this]() { goto_line_entry(); }); @@ -559,7 +559,7 @@ void Window::set_menu_actions() { return; if(Notebook::get().current_cursor_location==static_cast<size_t>(-1)) Notebook::get().current_cursor_location=Notebook::get().cursor_locations.size()-1; - + auto cursor_location=&Notebook::get().cursor_locations.at(Notebook::get().current_cursor_location); // Move to current position if current position's view is not current view // (in case one is looking at a new file but has not yet placed the cursor within the file) @@ -570,7 +570,7 @@ void Window::set_menu_actions() { return; if(Notebook::get().current_cursor_location==0) return; - + --Notebook::get().current_cursor_location; cursor_location=&Notebook::get().cursor_locations.at(Notebook::get().current_cursor_location); if(Notebook::get().get_current_view()!=cursor_location->view) @@ -587,7 +587,7 @@ void Window::set_menu_actions() { Notebook::get().current_cursor_location=Notebook::get().cursor_locations.size()-1; if(Notebook::get().current_cursor_location==Notebook::get().cursor_locations.size()-1) return; - + ++Notebook::get().current_cursor_location; auto &cursor_location=Notebook::get().cursor_locations.at(Notebook::get().current_cursor_location); if(Notebook::get().get_current_view()!=cursor_location.view) @@ -596,7 +596,7 @@ void Window::set_menu_actions() { cursor_location.view->get_buffer()->place_cursor(cursor_location.mark->get_iter()); cursor_location.view->scroll_to_cursor_delayed(cursor_location.view, true, false); }); - + menu.add_action("source_show_completion", [] { if(auto view=Notebook::get().get_current_view()) { if(view->non_interactive_completion) @@ -605,12 +605,12 @@ void Window::set_menu_actions() { g_signal_emit_by_name(view->gobj(), "show-completion"); } }); - + menu.add_action("source_find_symbol", []() { auto project=Project::create(); project->show_symbols(); }); - + menu.add_action("source_find_file", []() { auto view = Notebook::get().get_current_view(); @@ -635,18 +635,18 @@ void Window::set_menu_actions() { default_path = build->get_default_path(); debug_path = build->get_debug_path(); } - + if(view) { auto dialog_iter=view->get_iter_for_dialog(); SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); } else SelectionDialog::create(true, true); - + std::unordered_set<std::string> buffer_paths; for(auto view: Notebook::get().get_views()) buffer_paths.emplace(view->file_path.string()); - + std::vector<boost::filesystem::path> paths; // populate with all files in search_path for (boost::filesystem::recursive_directory_iterator iter(search_path), end; iter != end; iter++) { @@ -657,7 +657,7 @@ void Window::set_menu_actions() { iter.no_push(); continue; } - + // remove project base path auto row_str = filesystem::get_relative_path(path, search_path).string(); if(buffer_paths.count(path.string())) @@ -665,12 +665,12 @@ void Window::set_menu_actions() { paths.emplace_back(path); SelectionDialog::get()->add_row(row_str); } - + if(paths.empty()) { Info::get().print("No files found in current project"); return; } - + SelectionDialog::get()->on_select=[paths=std::move(paths)](unsigned int index, const std::string &text, bool hide_window) { if(index>=paths.size()) return; @@ -678,13 +678,13 @@ void Window::set_menu_actions() { if (auto view=Notebook::get().get_current_view()) view->hide_tooltips(); }; - + if(view) view->hide_tooltips(); SelectionDialog::get()->show(); - + }); - + menu.add_action("source_comments_toggle", []() { if(auto view=Notebook::get().get_current_view()) { if(view->toggle_comments) { @@ -738,7 +738,7 @@ void Window::set_menu_actions() { query=documentation_search->second.queries.find("@empty"); if(query==documentation_search->second.queries.end()) query=documentation_search->second.queries.find("@any"); - + if(query!=documentation_search->second.queries.end()) url=query->second+token_query; } @@ -760,7 +760,7 @@ void Window::set_menu_actions() { } } }); - + menu.add_action("source_goto_declaration", []() { if(auto view=Notebook::get().get_current_view()) { if(view->get_declaration_location) { @@ -815,7 +815,7 @@ void Window::set_menu_actions() { rows.emplace_back(location); SelectionDialog::get()->add_row(row); } - + if(rows.size()==0) return; else if(rows.size()==1) { @@ -867,7 +867,7 @@ void Window::set_menu_actions() { auto dialog_iter=view->get_iter_for_dialog(); SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); std::vector<Source::Offset> rows; - + auto iter=view->get_buffer()->get_insert()->get_iter(); for(auto &usage: usages) { std::string row; @@ -880,14 +880,14 @@ void Window::set_menu_actions() { row+=std::to_string(usage.first.line+1)+": "+usage.second; rows.emplace_back(usage.first); SelectionDialog::get()->add_row(row); - + //Set dialog cursor to the last row if the textview cursor is at the same line if(current_page && iter.get_line()==static_cast<int>(usage.first.line) && iter.get_line_index()>=static_cast<int>(usage.first.index)) { SelectionDialog::get()->set_cursor_at_last_row(); } } - + if(rows.size()==0) return; SelectionDialog::get()->on_select=[rows=std::move(rows)](unsigned int index, const std::string &text, bool hide_window) { @@ -942,7 +942,7 @@ void Window::set_menu_actions() { }); menu.add_action("source_implement_method", []() { const static std::string button_text="Insert Method Implementation"; - + if(auto view=Notebook::get().get_current_view()) { if(view->get_method) { auto iter=view->get_buffer()->get_insert()->get_iter(); @@ -954,7 +954,7 @@ void Window::set_menu_actions() { auto method=view->get_method(); if(method.empty()) return; - + EntryBox::get().clear(); EntryBox::get().labels.emplace_back(); EntryBox::get().labels.back().set_text(method); @@ -968,7 +968,7 @@ void Window::set_menu_actions() { } } }); - + menu.add_action("source_goto_next_diagnostic", []() { if(auto view=Notebook::get().get_current_view()) { if(view->goto_next_diagnostic) { @@ -1010,13 +1010,13 @@ void Window::set_menu_actions() { } } }); - + menu.add_action("project_set_run_arguments", []() { auto project=Project::create(); auto run_arguments=project->get_run_arguments(); if(run_arguments.second.empty()) return; - + EntryBox::get().clear(); EntryBox::get().labels.emplace_back(); auto label_it=EntryBox::get().labels.begin(); @@ -1040,12 +1040,12 @@ void Window::set_menu_actions() { Info::get().print("Compile or debug in progress"); return; } - + Project::current=Project::create(); - + if(Config::get().project.save_on_compile_or_run) Project::save_files(Project::current->build->get_project_path()); - + Project::current->compile_and_run(); }); menu.add_action("project_compile", []() { @@ -1053,12 +1053,12 @@ void Window::set_menu_actions() { Info::get().print("Compile or debug in progress"); return; } - + Project::current=Project::create(); - + if(Config::get().project.save_on_compile_or_run) Project::save_files(Project::current->build->get_project_path()); - + Project::current->compile(); }); menu.add_action("project_recreate_build", []() { @@ -1066,12 +1066,12 @@ void Window::set_menu_actions() { Info::get().print("Compile or debug in progress"); return; } - + Project::current=Project::create(); Project::current->recreate_build(); }); - + menu.add_action("project_run_command", [this]() { EntryBox::get().clear(); EntryBox::get().labels.emplace_back(); @@ -1085,7 +1085,7 @@ void Window::set_menu_actions() { last_run_command=content; auto run_path=Notebook::get().get_current_folder(); Terminal::get().async_print("Running: "+content+'\n'); - + Terminal::get().async_process(content, run_path, [content](int exit_status){ Terminal::get().async_print(content+" returned: "+std::to_string(exit_status)+'\n'); }); @@ -1099,21 +1099,21 @@ void Window::set_menu_actions() { }); EntryBox::get().show(); }); - + menu.add_action("project_kill_last_running", []() { Terminal::get().kill_last_async_process(); }); menu.add_action("project_force_kill_last_running", []() { Terminal::get().kill_last_async_process(true); }); - + #ifdef JUCI_ENABLE_DEBUG menu.add_action("debug_set_run_arguments", []() { auto project=Project::create(); auto run_arguments=project->debug_get_run_arguments(); if(run_arguments.second.empty()) return; - + EntryBox::get().clear(); EntryBox::get().labels.emplace_back(); auto label_it=EntryBox::get().labels.begin(); @@ -1127,7 +1127,7 @@ void Window::set_menu_actions() { }, 50); auto entry_it=EntryBox::get().entries.begin(); entry_it->set_placeholder_text("Debug: Set Run Arguments"); - + if(auto options=project->debug_get_options()) { EntryBox::get().buttons.emplace_back("", [options]() { options->show_all(); @@ -1137,7 +1137,7 @@ void Window::set_menu_actions() { EntryBox::get().buttons.back().set_tooltip_text("Additional Options"); options->set_relative_to(EntryBox::get().buttons.back()); } - + EntryBox::get().buttons.emplace_back("Debug: set run arguments", [entry_it](){ entry_it->activate(); }); @@ -1152,12 +1152,12 @@ void Window::set_menu_actions() { Project::current->debug_continue(); return; } - + Project::current=Project::create(); - + if(Config::get().project.save_on_compile_or_run) Project::save_files(Project::current->build->get_project_path()); - + Project::current->debug_start(); }); menu.add_action("debug_stop", []() { @@ -1224,10 +1224,10 @@ void Window::set_menu_actions() { } } }); - + Project::debug_update_status(""); #endif - + menu.add_action("window_next_tab", []() { Notebook::get().next(); }); @@ -1253,11 +1253,11 @@ void Window::set_menu_actions() { menu.add_action("window_clear_terminal", [] { Terminal::get().clear(); }); - + menu.toggle_menu_items=[] { auto &menu = Menu::get(); auto view=Notebook::get().get_current_view(); - + menu.actions["file_reload_file"]->set_enabled(view); menu.actions["source_spellcheck"]->set_enabled(view); menu.actions["source_spellcheck_clear"]->set_enabled(view); @@ -1267,7 +1267,7 @@ void Window::set_menu_actions() { menu.actions["source_indentation_set_buffer_tab"]->set_enabled(view); menu.actions["source_goto_line"]->set_enabled(view); menu.actions["source_center_cursor"]->set_enabled(view); - + menu.actions["source_indentation_auto_indent_buffer"]->set_enabled(view && view->format_style); menu.actions["source_comments_toggle"]->set_enabled(view && view->toggle_comments); menu.actions["source_comments_add_documentation"]->set_enabled(view && view->get_documentation_template); @@ -1291,27 +1291,27 @@ void Window::set_menu_actions() { void Window::add_widgets() { auto directories_scrolled_window=Gtk::manage(new Gtk::ScrolledWindow()); directories_scrolled_window->add(Directories::get()); - + auto notebook_vbox=Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL)); notebook_vbox->pack_start(Notebook::get()); notebook_vbox->pack_end(EntryBox::get(), Gtk::PACK_SHRINK); - + auto terminal_scrolled_window=Gtk::manage(new Gtk::ScrolledWindow()); terminal_scrolled_window->add(Terminal::get()); - + int width, height; get_default_size(width, height); - + auto notebook_and_terminal_vpaned=Gtk::manage(new Gtk::Paned(Gtk::Orientation::ORIENTATION_VERTICAL)); notebook_and_terminal_vpaned->set_position(static_cast<int>(0.75*height)); notebook_and_terminal_vpaned->pack1(*notebook_vbox, Gtk::SHRINK); notebook_and_terminal_vpaned->pack2(*terminal_scrolled_window, Gtk::SHRINK); - + auto hpaned=Gtk::manage(new Gtk::Paned()); hpaned->set_position(static_cast<int>(0.2*width)); hpaned->pack1(*directories_scrolled_window, Gtk::SHRINK); hpaned->pack2(*notebook_and_terminal_vpaned, Gtk::SHRINK); - + auto status_hbox=Gtk::manage(new Gtk::Box()); status_hbox->set_homogeneous(true); status_hbox->pack_start(*Gtk::manage(new Gtk::Box())); @@ -1321,7 +1321,7 @@ void Window::add_widgets() { status_right_overlay->add(*status_right_hbox); status_right_overlay->add_overlay(Notebook::get().status_diagnostics); status_hbox->pack_end(*status_right_overlay); - + auto status_overlay=Gtk::manage(new Gtk::Overlay()); status_overlay->add(*status_hbox); auto status_file_info_hbox=Gtk::manage(new Gtk::Box); @@ -1330,11 +1330,11 @@ void Window::add_widgets() { status_file_info_hbox->pack_start(Notebook::get().status_location, Gtk::PACK_SHRINK); status_overlay->add_overlay(*status_file_info_hbox); status_overlay->add_overlay(Project::debug_status_label()); - + auto vbox=Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL)); vbox->pack_start(*hpaned); vbox->pack_start(*status_overlay, Gtk::PACK_SHRINK); - + auto overlay_vbox=Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL)); auto overlay_hbox=Gtk::manage(new Gtk::Box()); overlay_vbox->set_hexpand(false); @@ -1343,23 +1343,23 @@ void Window::add_widgets() { overlay_hbox->set_hexpand(false); overlay_hbox->set_halign(Gtk::Align::ALIGN_END); overlay_hbox->pack_end(*overlay_vbox, Gtk::PACK_SHRINK, 20); - + auto overlay=Gtk::manage(new Gtk::Overlay()); overlay->add(*vbox); overlay->add_overlay(*overlay_hbox); overlay->set_overlay_pass_through(*overlay_hbox, true); add(*overlay); - + show_all_children(); Info::get().hide(); - + //Scroll to end of terminal whenever info is printed Terminal::get().signal_size_allocate().connect([terminal_scrolled_window](Gtk::Allocation& allocation){ auto adjustment=terminal_scrolled_window->get_vadjustment(); adjustment->set_value(adjustment->get_upper()-adjustment->get_page_size()); Terminal::get().queue_draw(); }); - + EntryBox::get().signal_show().connect([hpaned, notebook_and_terminal_vpaned, notebook_vbox](){ hpaned->set_focus_chain({notebook_and_terminal_vpaned}); notebook_and_terminal_vpaned->set_focus_chain({notebook_vbox}); @@ -1413,7 +1413,7 @@ bool Window::on_key_press_event(GdkEventKey *event) { bool Window::on_delete_event(GdkEventAny *event) { save_session(); - + for(size_t c=Notebook::get().size()-1;c!=static_cast<size_t>(-1);--c) { if(!Notebook::get().close(c)) return true; @@ -1487,7 +1487,7 @@ void Window::search_and_replace_entry() { replace_entry_it->signal_changed().connect([this, replace_entry_it](){ last_replace=replace_entry_it->get_text(); }); - + EntryBox::get().buttons.emplace_back("↑", [](){ if(auto view=Notebook::get().get_current_view()) view->search_backward(); @@ -1509,7 +1509,7 @@ void Window::search_and_replace_entry() { view->replace_all(replace_entry_it->get_text()); }); EntryBox::get().buttons.back().set_tooltip_text("Replace All"); - + EntryBox::get().toggle_buttons.emplace_back("Aa"); EntryBox::get().toggle_buttons.back().set_tooltip_text("Match Case"); EntryBox::get().toggle_buttons.back().set_active(case_sensitive_search); @@ -1541,25 +1541,25 @@ void Window::set_tab_entry() { EntryBox::get().clear(); if(auto view=Notebook::get().get_current_view()) { auto tab_char_and_size=view->get_tab_char_and_size(); - + EntryBox::get().labels.emplace_back(); auto label_it=EntryBox::get().labels.begin(); - + EntryBox::get().entries.emplace_back(std::to_string(tab_char_and_size.second)); auto entry_tab_size_it=EntryBox::get().entries.begin(); entry_tab_size_it->set_placeholder_text("Tab size"); - + char tab_char=tab_char_and_size.first; std::string tab_char_string; if(tab_char==' ') tab_char_string="space"; else if(tab_char=='\t') tab_char_string="tab"; - + EntryBox::get().entries.emplace_back(tab_char_string); auto entry_tab_char_it=EntryBox::get().entries.rbegin(); entry_tab_char_it->set_placeholder_text("Tab char"); - + const auto activate_function=[entry_tab_char_it, entry_tab_size_it, label_it](const std::string& content){ if(auto view=Notebook::get().get_current_view()) { char tab_char=0; @@ -1584,14 +1584,14 @@ void Window::set_tab_entry() { } } }; - + entry_tab_char_it->on_activate=activate_function; entry_tab_size_it->on_activate=activate_function; - + EntryBox::get().buttons.emplace_back("Set tab in current buffer", [entry_tab_char_it](){ entry_tab_char_it->activate(); }); - + EntryBox::get().show(); } } @@ -1605,7 +1605,7 @@ void Window::goto_line_entry() { view->place_cursor_at_line_index(stoi(content)-1, 0); view->scroll_to_cursor_delayed(view, true, false); } - catch(const std::exception &e) {} + catch(const std::exception &e) {} EntryBox::get().hide(); } }); @@ -1648,7 +1648,7 @@ void Window::save_session() { try { boost::property_tree::ptree root_pt; root_pt.put("folder", Directories::get().path.string()); - + boost::property_tree::ptree files_pt; for(auto ¬ebook_view: Notebook::get().get_notebook_views()) { boost::property_tree::ptree file_pt; @@ -1660,7 +1660,7 @@ void Window::save_session() { files_pt.push_back(std::make_pair("", file_pt)); } root_pt.add_child("files", files_pt); - + boost::property_tree::ptree current_file_pt; if(auto view=Notebook::get().get_current_view()) { current_file_pt.put("path", view->file_path.string()); @@ -1672,7 +1672,7 @@ void Window::save_session() { if(auto view=Notebook::get().get_current_view()) current_path=view->file_path.string(); root_pt.put("current_file", current_path); - + boost::property_tree::ptree run_arguments_pt; for(auto &run_argument: Project::run_arguments) { if(run_argument.second.empty()) @@ -1686,7 +1686,7 @@ void Window::save_session() { } } root_pt.add_child("run_arguments", run_arguments_pt); - + boost::property_tree::ptree debug_run_arguments_pt; for(auto &debug_run_argument: Project::debug_run_arguments) { if(debug_run_argument.second.arguments.empty() && !debug_run_argument.second.remote_enabled && debug_run_argument.second.remote_host_port.empty()) @@ -1702,14 +1702,14 @@ void Window::save_session() { } } root_pt.add_child("debug_run_arguments", debug_run_arguments_pt); - + int width, height; get_size(width, height); boost::property_tree::ptree window_pt; window_pt.put("width", width); window_pt.put("height", height); root_pt.add_child("window", window_pt); - + boost::property_tree::write_json((Config::get().home_juci_path/"last_session.json").string(), root_pt); } catch(...) {} @@ -1723,7 +1723,7 @@ void Window::load_session(std::vector<boost::filesystem::path> &directories, std auto folder=root_pt.get<std::string>("folder"); if(!folder.empty() && boost::filesystem::exists(folder) && boost::filesystem::is_directory(folder)) directories.emplace_back(folder); - + for(auto &file_pt: root_pt.get_child("files")) { auto file=file_pt.second.get<std::string>("path"); auto notebook=file_pt.second.get<size_t>("notebook"); @@ -1734,17 +1734,17 @@ void Window::load_session(std::vector<boost::filesystem::path> &directories, std file_offsets.emplace_back(line, line_offset); } } - + current_file=root_pt.get<std::string>("current_file"); } - + for(auto &run_argument: root_pt.get_child(("run_arguments"))) { auto path=run_argument.second.get<std::string>("path"); boost::system::error_code ec; if(boost::filesystem::exists(path, ec) && boost::filesystem::is_directory(path, ec)) Project::run_arguments.emplace(path, run_argument.second.get<std::string>("arguments")); } - + for(auto &debug_run_argument: root_pt.get_child(("debug_run_arguments"))) { auto path=debug_run_argument.second.get<std::string>("path"); boost::system::error_code ec; @@ -1753,7 +1753,7 @@ void Window::load_session(std::vector<boost::filesystem::path> &directories, std debug_run_argument.second.get<bool>("remote_enabled"), debug_run_argument.second.get<std::string>("remote_host_port")}); } - + auto window_pt=root_pt.get_child("window"); set_default_size(window_pt.get<int>("width"), window_pt.get<int>("height")); } From 0cae94776692f1301f2bd222de066e553d5864de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=88=E9=A1=BA=20=E5=88=98?= <yshliu0321@icloud.com> Date: Fri, 25 May 2018 15:08:52 +0800 Subject: [PATCH 11/12] Refactoring --- 3rd_party/json.h | 8838 ++++++++++++++++++++++++ {src/rapidxml => 3rd_party}/rapidxml.h | 0 CMakeLists.txt | 1 + src/CMakeLists.txt | 106 +- src/cmake.cc | 107 +- src/cmake.h | 15 - src/compile_commands.cc | 110 +- src/compile_commands.h | 4 +- src/makefile_base.cc | 66 +- src/makefile_base.h | 32 +- src/meson.cc | 68 +- src/meson.h | 16 - src/project_build.cc | 64 +- src/project_build.h | 15 +- 14 files changed, 9088 insertions(+), 354 deletions(-) create mode 100644 3rd_party/json.h rename {src/rapidxml => 3rd_party}/rapidxml.h (100%) delete mode 100644 src/cmake.h delete mode 100644 src/meson.h diff --git a/3rd_party/json.h b/3rd_party/json.h new file mode 100644 index 00000000..b631877e --- /dev/null +++ b/3rd_party/json.h @@ -0,0 +1,8838 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ +| | |__ | | | | | | version 3.1.2 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License <http://opensource.org/licenses/MIT>. +Copyright (c) 2013-2018 Niels Lohmann <http://nlohmann.me>. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#pragma once + +#define NLOHMANN_JSON_VERSION_MAJOR 3 +#define NLOHMANN_JSON_VERSION_MINOR 1 +#define NLOHMANN_JSON_VERSION_PATCH 2 + +#include <algorithm> +#include <cassert> +#include <ciso646> +#include <cstddef> +#include <functional> +#include <initializer_list> +#include <iosfwd> +#include <iterator> +#include <numeric> +#include <string> +#include <utility> + +#ifndef NLOHMANN_JSON_FWD_HPP +#define NLOHMANN_JSON_FWD_HPP + +#include <cstdint> +#include <map> +#include <memory> +#include <vector> + +namespace nlohmann { +template<typename = void, typename = void> +struct adl_serializer; + +template<template<typename U, typename V, typename... Args> class ObjectType = +std::map, + template<typename U, typename... Args> class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template<typename U> class AllocatorType = std::allocator, + template<typename T, typename SFINAE = void> class JSONSerializer = + adl_serializer> +class basic_json; + +template<typename BasicJsonType> +class json_pointer; + +using json = basic_json<>; +} + +#endif + +#if defined(__clang__) +#if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version" + #endif +#elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) +#if (__GNUC__ * 10000+__GNUC_MINOR__ * 100+__GNUC_PATCHLEVEL__) < 40900 +#error "unsupported GCC version" +#endif +#endif + +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + +#if defined(__clang__) +#pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdocumentation" +#endif + +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) +#define JSON_THROW(exception) throw exception +#define JSON_TRY try +#define JSON_CATCH(exception) catch(exception) +#else +#define JSON_THROW(exception) std::abort() + #define JSON_TRY if(true) + #define JSON_CATCH(exception) if(false) +#endif + +#if defined(JSON_THROW_USER) + +#undef JSON_THROW +#define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + +#undef JSON_TRY +#define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + +#undef JSON_CATCH +#define JSON_CATCH JSON_CATCH_USER +#endif + +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) +#define JSON_LIKELY(x) __builtin_expect(!!(x), 1) +#define JSON_UNLIKELY(x) __builtin_expect(!!(x), 0) +#else +#define JSON_LIKELY(x) x +#define JSON_UNLIKELY(x) x +#endif + +#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) + +#define JSON_HAS_CPP_17 +#define JSON_HAS_CPP_14 +#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) +#define JSON_HAS_CPP_14 +#endif + +#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ + template<template<typename, typename, typename...> class ObjectType, \ + template<typename, typename...> class ArrayType, \ + class StringType, class BooleanType, class NumberIntegerType, \ + class NumberUnsignedType, class NumberFloatType, \ + template<typename> class AllocatorType, \ + template<typename, typename = void> class JSONSerializer> + +#define NLOHMANN_BASIC_JSON_TPL \ + basic_json<ObjectType, ArrayType, StringType, BooleanType, \ + NumberIntegerType, NumberUnsignedType, NumberFloatType, \ + AllocatorType, JSONSerializer> + +#define NLOHMANN_JSON_HAS_HELPER(type) \ + template<typename T> struct has_##type { \ + private: \ + template<typename U, typename = typename U::type> \ + static int detect(U &&); \ + static void detect(...); \ + public: \ + static constexpr bool value = \ + std::is_integral<decltype(detect(std::declval<T>()))>::value; \ + } + +#include <limits> +#include <type_traits> + +namespace nlohmann { +namespace detail { +template<typename> +struct is_basic_json : std::false_type { +}; + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +struct is_basic_json<NLOHMANN_BASIC_JSON_TPL > : std::true_type { +}; + +template<bool B, typename T = void> +using enable_if_t = typename std::enable_if<B, T>::type; + +template<typename T> +using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type; + +template<std::size_t... Ints> +struct index_sequence { + using type = index_sequence; + using value_type = std::size_t; + + static constexpr std::size_t size() noexcept { + return sizeof...(Ints); + } +}; + +template<class Sequence1, class Sequence2> +struct merge_and_renumber; + +template<std::size_t... I1, std::size_t... I2> +struct merge_and_renumber<index_sequence<I1...>, index_sequence<I2...>> + : index_sequence<I1..., (sizeof...(I1)+I2)...> { +}; + +template<std::size_t N> +struct make_index_sequence + : merge_and_renumber<typename make_index_sequence<N / 2>::type, + typename make_index_sequence<N-N / 2>::type> { +}; + +template<> +struct make_index_sequence<0> : index_sequence<> { +}; +template<> +struct make_index_sequence<1> : index_sequence<0> { +}; + +template<typename... Ts> +using index_sequence_for = make_index_sequence<sizeof...(Ts)>; + +template<class...> +struct conjunction : std::true_type { +}; +template<class B1> +struct conjunction<B1> : B1 { +}; +template<class B1, class... Bn> +struct conjunction<B1, Bn...> : std::conditional<bool(B1::value), conjunction<Bn...>, B1>::type { +}; + +template<class B> +struct negation : std::integral_constant<bool, not B::value> { +}; + +template<unsigned N> +struct priority_tag : priority_tag<N-1> { +}; +template<> +struct priority_tag<0> { +}; + +template<typename T, typename = void> +struct is_complete_type : std::false_type { +}; + +template<typename T> +struct is_complete_type<T, decltype(void(sizeof(T)))> : std::true_type { +}; + +NLOHMANN_JSON_HAS_HELPER(mapped_type); + +NLOHMANN_JSON_HAS_HELPER(key_type); + +NLOHMANN_JSON_HAS_HELPER(value_type); + +NLOHMANN_JSON_HAS_HELPER(iterator); + +template<bool B, class RealType, class CompatibleObjectType> +struct is_compatible_object_type_impl : std::false_type { +}; + +template<class RealType, class CompatibleObjectType> +struct is_compatible_object_type_impl<true, RealType, CompatibleObjectType> { + static constexpr auto value = + std::is_constructible<typename RealType::key_type, typename CompatibleObjectType::key_type>::value and + std::is_constructible<typename RealType::mapped_type, typename CompatibleObjectType::mapped_type>::value; +}; + +template<class BasicJsonType, class CompatibleObjectType> +struct is_compatible_object_type { + static auto constexpr value = is_compatible_object_type_impl< + conjunction<negation<std::is_same<void, CompatibleObjectType>>, + has_mapped_type<CompatibleObjectType>, + has_key_type<CompatibleObjectType>>::value, + typename BasicJsonType::object_t, CompatibleObjectType>::value; +}; + +template<typename BasicJsonType, typename T> +struct is_basic_json_nested_type { + static auto constexpr value = std::is_same<T, typename BasicJsonType::iterator>::value or + std::is_same<T, typename BasicJsonType::const_iterator>::value or + std::is_same<T, typename BasicJsonType::reverse_iterator>::value or + std::is_same<T, typename BasicJsonType::const_reverse_iterator>::value; +}; + +template<class BasicJsonType, class CompatibleArrayType> +struct is_compatible_array_type { + static auto constexpr value = + conjunction<negation<std::is_same<void, CompatibleArrayType>>, + negation<is_compatible_object_type< + BasicJsonType, CompatibleArrayType>>, + negation<std::is_constructible<typename BasicJsonType::string_t, + CompatibleArrayType>>, + negation<is_basic_json_nested_type<BasicJsonType, CompatibleArrayType>>, + has_value_type<CompatibleArrayType>, + has_iterator<CompatibleArrayType>>::value; +}; + +template<bool, typename, typename> +struct is_compatible_integer_type_impl : std::false_type { +}; + +template<typename RealIntegerType, typename CompatibleNumberIntegerType> +struct is_compatible_integer_type_impl<true, RealIntegerType, CompatibleNumberIntegerType> { + using RealLimits = std::numeric_limits<RealIntegerType>; + using CompatibleLimits = std::numeric_limits<CompatibleNumberIntegerType>; + + static constexpr auto value = + std::is_constructible<RealIntegerType, CompatibleNumberIntegerType>::value and + CompatibleLimits::is_integer and + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template<typename RealIntegerType, typename CompatibleNumberIntegerType> +struct is_compatible_integer_type { + static constexpr auto value = + is_compatible_integer_type_impl< + std::is_integral<CompatibleNumberIntegerType>::value and + not std::is_same<bool, CompatibleNumberIntegerType>::value, + RealIntegerType, CompatibleNumberIntegerType>::value; +}; + +template<typename BasicJsonType, typename T> +struct has_from_json { +private: + template<typename U, typename = enable_if_t<std::is_same<void, decltype(uncvref_t<U>::from_json( + std::declval<BasicJsonType>(), std::declval<T &>()))>::value>> + static int detect(U &&); + + static void detect(...); + +public: + static constexpr bool value = std::is_integral<decltype( + detect(std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value; +}; + +template<typename BasicJsonType, typename T> +struct has_non_default_from_json { +private: + template< + typename U, + typename = enable_if_t<std::is_same< + T, decltype(uncvref_t<U>::from_json(std::declval<BasicJsonType>()))>::value >> + static int detect(U &&); + + static void detect(...); + +public: + static constexpr bool value = std::is_integral<decltype(detect( + std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value; +}; + +template<typename BasicJsonType, typename T> +struct has_to_json { +private: + template<typename U, typename = decltype(uncvref_t<U>::to_json( + std::declval<BasicJsonType &>(), std::declval<T>()))> + static int detect(U &&); + + static void detect(...); + +public: + static constexpr bool value = std::is_integral<decltype(detect( + std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value; +}; + +template<typename BasicJsonType, typename CompatibleCompleteType> +struct is_compatible_complete_type { + static constexpr bool value = + not std::is_base_of<std::istream, CompatibleCompleteType>::value and + not is_basic_json<CompatibleCompleteType>::value and + not is_basic_json_nested_type<BasicJsonType, CompatibleCompleteType>::value and + has_to_json<BasicJsonType, CompatibleCompleteType>::value; +}; + +template<typename BasicJsonType, typename CompatibleType> +struct is_compatible_type + : conjunction<is_complete_type<CompatibleType>, + is_compatible_complete_type<BasicJsonType, CompatibleType>> { +}; + +template<typename T> +struct static_const { + static constexpr T value{}; +}; + +template<typename T> +constexpr T static_const<T>::value; +} +} + +#include <exception> +#include <stdexcept> + +namespace nlohmann { +namespace detail { +class exception : public std::exception { +public: + const char *what() const noexcept override { + return m.what(); + } + + const int id; + +protected: + exception(int id_, const char *what_arg) : id(id_), m(what_arg) {} + + static std::string name(const std::string &ename, int id_) { + return "[json.exception."+ename+"."+std::to_string(id_)+"] "; + } + +private: + std::runtime_error m; +}; + +class parse_error : public exception { +public: + static parse_error create(int id_, std::size_t byte_, const std::string &what_arg) { + std::string w = exception::name("parse_error", id_)+"parse error"+ + (byte_ != 0 ? (" at "+std::to_string(byte_)) : "")+ + ": "+what_arg; + return parse_error(id_, byte_, w.c_str()); + } + + const std::size_t byte; + +private: + parse_error(int id_, std::size_t byte_, const char *what_arg) + : exception(id_, what_arg), byte(byte_) {} +}; + +class invalid_iterator : public exception { +public: + static invalid_iterator create(int id_, const std::string &what_arg) { + std::string w = exception::name("invalid_iterator", id_)+what_arg; + return invalid_iterator(id_, w.c_str()); + } + +private: + invalid_iterator(int id_, const char *what_arg) + : exception(id_, what_arg) {} +}; + +class type_error : public exception { +public: + static type_error create(int id_, const std::string &what_arg) { + std::string w = exception::name("type_error", id_)+what_arg; + return type_error(id_, w.c_str()); + } + +private: + type_error(int id_, const char *what_arg) : exception(id_, what_arg) {} +}; + +class out_of_range : public exception { +public: + static out_of_range create(int id_, const std::string &what_arg) { + std::string w = exception::name("out_of_range", id_)+what_arg; + return out_of_range(id_, w.c_str()); + } + +private: + out_of_range(int id_, const char *what_arg) : exception(id_, what_arg) {} +}; + +class other_error : public exception { +public: + static other_error create(int id_, const std::string &what_arg) { + std::string w = exception::name("other_error", id_)+what_arg; + return other_error(id_, w.c_str()); + } + +private: + other_error(int id_, const char *what_arg) : exception(id_, what_arg) {} +}; +} +} + +#include <array> + +namespace nlohmann { +namespace detail { +enum class value_t : std::uint8_t { + null, + object, + array, + string, + boolean, + number_integer, + number_unsigned, + number_float, + discarded +}; + +inline bool operator<(const value_t lhs, const value_t rhs) noexcept { + static constexpr std::array<std::uint8_t, 8> order = {{ + 0, 3, 4, 5, + 1, 2, 2, 2 + } + }; + + const auto l_index = static_cast<std::size_t>(lhs); + const auto r_index = static_cast<std::size_t>(rhs); + return l_index < order.size() and r_index < order.size() and order[l_index] < order[r_index]; +} +} +} + +#include <forward_list> +#include <tuple> +#include <valarray> + +namespace nlohmann { +namespace detail { +template<typename BasicJsonType, typename ArithmeticType, + enable_if_t<std::is_arithmetic<ArithmeticType>::value and + not std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value, + int> = 0> +void get_arithmetic_value(const BasicJsonType &j, ArithmeticType &val) { + switch(static_cast<value_t>(j)) { + case value_t::number_unsigned: { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t *>()); + break; + } + case value_t::number_integer: { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t *>()); + break; + } + case value_t::number_float: { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t *>()); + break; + } + + default: + JSON_THROW(type_error::create(302, "type must be number, but is "+std::string(j.type_name()))); + } +} + +template<typename BasicJsonType> +void from_json(const BasicJsonType &j, typename BasicJsonType::boolean_t &b) { + if(JSON_UNLIKELY(not j.is_boolean())) { + JSON_THROW(type_error::create(302, "type must be boolean, but is "+std::string(j.type_name()))); + } + b = *j.template get_ptr<const typename BasicJsonType::boolean_t *>(); +} + +template<typename BasicJsonType> +void from_json(const BasicJsonType &j, typename BasicJsonType::string_t &s) { + if(JSON_UNLIKELY(not j.is_string())) { + JSON_THROW(type_error::create(302, "type must be string, but is "+std::string(j.type_name()))); + } + s = *j.template get_ptr<const typename BasicJsonType::string_t *>(); +} + +template<typename BasicJsonType> +void from_json(const BasicJsonType &j, typename BasicJsonType::number_float_t &val) { + get_arithmetic_value(j, val); +} + +template<typename BasicJsonType> +void from_json(const BasicJsonType &j, typename BasicJsonType::number_unsigned_t &val) { + get_arithmetic_value(j, val); +} + +template<typename BasicJsonType> +void from_json(const BasicJsonType &j, typename BasicJsonType::number_integer_t &val) { + get_arithmetic_value(j, val); +} + +template<typename BasicJsonType, typename EnumType, + enable_if_t<std::is_enum<EnumType>::value, int> = 0> +void from_json(const BasicJsonType &j, EnumType &e) { + typename std::underlying_type<EnumType>::type val; + get_arithmetic_value(j, val); + e = static_cast<EnumType>(val); +} + +template<typename BasicJsonType> +void from_json(const BasicJsonType &j, typename BasicJsonType::array_t &arr) { + if(JSON_UNLIKELY(not j.is_array())) { + JSON_THROW(type_error::create(302, "type must be array, but is "+std::string(j.type_name()))); + } + arr = *j.template get_ptr<const typename BasicJsonType::array_t *>(); +} + +template<typename BasicJsonType, typename T, typename Allocator, + enable_if_t<std::is_convertible<BasicJsonType, T>::value, int> = 0> +void from_json(const BasicJsonType &j, std::forward_list<T, Allocator> &l) { + if(JSON_UNLIKELY(not j.is_array())) { + JSON_THROW(type_error::create(302, "type must be array, but is "+std::string(j.type_name()))); + } + std::transform(j.rbegin(), j.rend(), + std::front_inserter(l), [](const BasicJsonType &i) { + return i.template get<T>(); + }); +} + +template<typename BasicJsonType, typename T, + enable_if_t<std::is_convertible<BasicJsonType, T>::value, int> = 0> +void from_json(const BasicJsonType &j, std::valarray<T> &l) { + if(JSON_UNLIKELY(not j.is_array())) { + JSON_THROW(type_error::create(302, "type must be array, but is "+std::string(j.type_name()))); + } + l.resize(j.size()); + std::copy(j.m_value.array->begin(), j.m_value.array->end(), std::begin(l)); +} + +template<typename BasicJsonType, typename CompatibleArrayType> +void from_json_array_impl(const BasicJsonType &j, CompatibleArrayType &arr, priority_tag<0>) { + using std::end; + + std::transform(j.begin(), j.end(), + std::inserter(arr, end(arr)), [](const BasicJsonType &i) { + return i.template get<typename CompatibleArrayType::value_type>(); + }); +} + +template<typename BasicJsonType, typename CompatibleArrayType> +auto from_json_array_impl(const BasicJsonType &j, CompatibleArrayType &arr, priority_tag<1>) +-> decltype( +arr.reserve(std::declval<typename CompatibleArrayType::size_type>()), + void()) { + using std::end; + + arr.reserve(j.size()); + std::transform(j.begin(), j.end(), + std::inserter(arr, end(arr)), [](const BasicJsonType &i) { + return i.template get<typename CompatibleArrayType::value_type>(); + }); +} + +template<typename BasicJsonType, typename T, std::size_t N> +void from_json_array_impl(const BasicJsonType &j, std::array<T, N> &arr, priority_tag<2>) { + for(std::size_t i = 0; i < N; ++i) { + arr[i] = j.at(i).template get<T>(); + } +} + +template< + typename BasicJsonType, typename CompatibleArrayType, + enable_if_t< + is_compatible_array_type<BasicJsonType, CompatibleArrayType>::value and + not std::is_same<typename BasicJsonType::array_t, + CompatibleArrayType>::value and + std::is_constructible< + BasicJsonType, typename CompatibleArrayType::value_type>::value, + int> = 0> +void from_json(const BasicJsonType &j, CompatibleArrayType &arr) { + if(JSON_UNLIKELY(not j.is_array())) { + JSON_THROW(type_error::create(302, "type must be array, but is "+ + std::string(j.type_name()))); + } + + from_json_array_impl(j, arr, priority_tag<2>{}); +} + +template<typename BasicJsonType, typename CompatibleObjectType, + enable_if_t<is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value, int> = 0> +void from_json(const BasicJsonType &j, CompatibleObjectType &obj) { + if(JSON_UNLIKELY(not j.is_object())) { + JSON_THROW(type_error::create(302, "type must be object, but is "+std::string(j.type_name()))); + } + + auto inner_object = j.template get_ptr<const typename BasicJsonType::object_t *>(); + using value_type = typename CompatibleObjectType::value_type; + std::transform( + inner_object->begin(), inner_object->end(), + std::inserter(obj, obj.begin()), + [](typename BasicJsonType::object_t::value_type const &p) { + return value_type(p.first, p.second.template get<typename CompatibleObjectType::mapped_type>()); + }); +} + +template<typename BasicJsonType, typename ArithmeticType, + enable_if_t< + std::is_arithmetic<ArithmeticType>::value and + not std::is_same<ArithmeticType, typename BasicJsonType::number_unsigned_t>::value and + not std::is_same<ArithmeticType, typename BasicJsonType::number_integer_t>::value and + not std::is_same<ArithmeticType, typename BasicJsonType::number_float_t>::value and + not std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value, + int> = 0> +void from_json(const BasicJsonType &j, ArithmeticType &val) { + switch(static_cast<value_t>(j)) { + case value_t::number_unsigned: { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t *>()); + break; + } + case value_t::number_integer: { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t *>()); + break; + } + case value_t::number_float: { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t *>()); + break; + } + case value_t::boolean: { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::boolean_t *>()); + break; + } + + default: + JSON_THROW(type_error::create(302, "type must be number, but is "+std::string(j.type_name()))); + } +} + +template<typename BasicJsonType, typename A1, typename A2> +void from_json(const BasicJsonType &j, std::pair<A1, A2> &p) { + p = {j.at(0).template get<A1>(), j.at(1).template get<A2>()}; +} + +template<typename BasicJsonType, typename Tuple, std::size_t... Idx> +void from_json_tuple_impl(const BasicJsonType &j, Tuple &t, index_sequence<Idx...>) { + t = std::make_tuple(j.at(Idx).template get<typename std::tuple_element<Idx, Tuple>::type>()...); +} + +template<typename BasicJsonType, typename... Args> +void from_json(const BasicJsonType &j, std::tuple<Args...> &t) { + from_json_tuple_impl(j, t, index_sequence_for<Args...>{}); +} + +struct from_json_fn { +private: + template<typename BasicJsonType, typename T> + auto call(const BasicJsonType &j, T &val, priority_tag<1>) const + noexcept(noexcept(from_json(j, val))) + -> decltype(from_json(j, val), void()) { + return from_json(j, val); + } + + template<typename BasicJsonType, typename T> + void call(const BasicJsonType &, T &, priority_tag<0>) const noexcept { + static_assert(sizeof(BasicJsonType) == 0, + "could not find from_json() method in T's namespace"); +#ifdef _MSC_VER + + using decayed = uncvref_t<T>; + static_assert(sizeof(typename decayed::force_msvc_stacktrace) == 0, + "forcing MSVC stacktrace to show which T we're talking about."); +#endif + } + +public: + template<typename BasicJsonType, typename T> + void operator()(const BasicJsonType &j, T &val) const + noexcept(noexcept(std::declval<from_json_fn>().call(j, val, priority_tag<1>{}))) { + return call(j, val, priority_tag<1>{}); + } +}; +} + +namespace { +constexpr const auto &from_json = detail::static_const<detail::from_json_fn>::value; +} +} + +namespace nlohmann { +namespace detail { +template<value_t> +struct external_constructor; + +template<> +struct external_constructor<value_t::boolean> { + template<typename BasicJsonType> + static void construct(BasicJsonType &j, typename BasicJsonType::boolean_t b) noexcept { + j.m_type = value_t::boolean; + j.m_value = b; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor<value_t::string> { + template<typename BasicJsonType> + static void construct(BasicJsonType &j, const typename BasicJsonType::string_t &s) { + j.m_type = value_t::string; + j.m_value = s; + j.assert_invariant(); + } + + template<typename BasicJsonType> + static void construct(BasicJsonType &j, typename BasicJsonType::string_t &&s) { + j.m_type = value_t::string; + j.m_value = std::move(s); + j.assert_invariant(); + } +}; + +template<> +struct external_constructor<value_t::number_float> { + template<typename BasicJsonType> + static void construct(BasicJsonType &j, typename BasicJsonType::number_float_t val) noexcept { + j.m_type = value_t::number_float; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor<value_t::number_unsigned> { + template<typename BasicJsonType> + static void construct(BasicJsonType &j, typename BasicJsonType::number_unsigned_t val) noexcept { + j.m_type = value_t::number_unsigned; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor<value_t::number_integer> { + template<typename BasicJsonType> + static void construct(BasicJsonType &j, typename BasicJsonType::number_integer_t val) noexcept { + j.m_type = value_t::number_integer; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor<value_t::array> { + template<typename BasicJsonType> + static void construct(BasicJsonType &j, const typename BasicJsonType::array_t &arr) { + j.m_type = value_t::array; + j.m_value = arr; + j.assert_invariant(); + } + + template<typename BasicJsonType> + static void construct(BasicJsonType &j, typename BasicJsonType::array_t &&arr) { + j.m_type = value_t::array; + j.m_value = std::move(arr); + j.assert_invariant(); + } + + template<typename BasicJsonType, typename CompatibleArrayType, + enable_if_t<not std::is_same<CompatibleArrayType, typename BasicJsonType::array_t>::value, + int> = 0> + static void construct(BasicJsonType &j, const CompatibleArrayType &arr) { + using std::begin; + using std::end; + j.m_type = value_t::array; + j.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr)); + j.assert_invariant(); + } + + template<typename BasicJsonType> + static void construct(BasicJsonType &j, const std::vector<bool> &arr) { + j.m_type = value_t::array; + j.m_value = value_t::array; + j.m_value.array->reserve(arr.size()); + for(const bool x : arr) { + j.m_value.array->push_back(x); + } + j.assert_invariant(); + } + + template<typename BasicJsonType, typename T, + enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0> + static void construct(BasicJsonType &j, const std::valarray<T> &arr) { + j.m_type = value_t::array; + j.m_value = value_t::array; + j.m_value.array->resize(arr.size()); + std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin()); + j.assert_invariant(); + } +}; + +template<> +struct external_constructor<value_t::object> { + template<typename BasicJsonType> + static void construct(BasicJsonType &j, const typename BasicJsonType::object_t &obj) { + j.m_type = value_t::object; + j.m_value = obj; + j.assert_invariant(); + } + + template<typename BasicJsonType> + static void construct(BasicJsonType &j, typename BasicJsonType::object_t &&obj) { + j.m_type = value_t::object; + j.m_value = std::move(obj); + j.assert_invariant(); + } + + template<typename BasicJsonType, typename CompatibleObjectType, + enable_if_t<not std::is_same<CompatibleObjectType, typename BasicJsonType::object_t>::value, int> = 0> + static void construct(BasicJsonType &j, const CompatibleObjectType &obj) { + using std::begin; + using std::end; + + j.m_type = value_t::object; + j.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj)); + j.assert_invariant(); + } +}; + +template<typename BasicJsonType, typename T, + enable_if_t<std::is_same<T, typename BasicJsonType::boolean_t>::value, int> = 0> +void to_json(BasicJsonType &j, T b) noexcept { + external_constructor<value_t::boolean>::construct(j, b); +} + +template<typename BasicJsonType, typename CompatibleString, + enable_if_t<std::is_constructible<typename BasicJsonType::string_t, CompatibleString>::value, int> = 0> +void to_json(BasicJsonType &j, const CompatibleString &s) { + external_constructor<value_t::string>::construct(j, s); +} + +template<typename BasicJsonType> +void to_json(BasicJsonType &j, typename BasicJsonType::string_t &&s) { + external_constructor<value_t::string>::construct(j, std::move(s)); +} + +template<typename BasicJsonType, typename FloatType, + enable_if_t<std::is_floating_point<FloatType>::value, int> = 0> +void to_json(BasicJsonType &j, FloatType val) noexcept { + external_constructor<value_t::number_float>::construct(j, static_cast<typename BasicJsonType::number_float_t>(val)); +} + +template<typename BasicJsonType, typename CompatibleNumberUnsignedType, + enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_unsigned_t, CompatibleNumberUnsignedType>::value, int> = 0> +void to_json(BasicJsonType &j, CompatibleNumberUnsignedType val) noexcept { + external_constructor<value_t::number_unsigned>::construct(j, + static_cast<typename BasicJsonType::number_unsigned_t>(val)); +} + +template<typename BasicJsonType, typename CompatibleNumberIntegerType, + enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType>::value, int> = 0> +void to_json(BasicJsonType &j, CompatibleNumberIntegerType val) noexcept { + external_constructor<value_t::number_integer>::construct(j, + static_cast<typename BasicJsonType::number_integer_t>(val)); +} + +template<typename BasicJsonType, typename EnumType, + enable_if_t<std::is_enum<EnumType>::value, int> = 0> +void to_json(BasicJsonType &j, EnumType e) noexcept { + using underlying_type = typename std::underlying_type<EnumType>::type; + external_constructor<value_t::number_integer>::construct(j, static_cast<underlying_type>(e)); +} + +template<typename BasicJsonType> +void to_json(BasicJsonType &j, const std::vector<bool> &e) { + external_constructor<value_t::array>::construct(j, e); +} + +template<typename BasicJsonType, typename CompatibleArrayType, + enable_if_t<is_compatible_array_type<BasicJsonType, CompatibleArrayType>::value or + std::is_same<typename BasicJsonType::array_t, CompatibleArrayType>::value, + int> = 0> +void to_json(BasicJsonType &j, const CompatibleArrayType &arr) { + external_constructor<value_t::array>::construct(j, arr); +} + +template<typename BasicJsonType, typename T, + enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0> +void to_json(BasicJsonType &j, std::valarray<T> arr) { + external_constructor<value_t::array>::construct(j, std::move(arr)); +} + +template<typename BasicJsonType> +void to_json(BasicJsonType &j, typename BasicJsonType::array_t &&arr) { + external_constructor<value_t::array>::construct(j, std::move(arr)); +} + +template<typename BasicJsonType, typename CompatibleObjectType, + enable_if_t<is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value, int> = 0> +void to_json(BasicJsonType &j, const CompatibleObjectType &obj) { + external_constructor<value_t::object>::construct(j, obj); +} + +template<typename BasicJsonType> +void to_json(BasicJsonType &j, typename BasicJsonType::object_t &&obj) { + external_constructor<value_t::object>::construct(j, std::move(obj)); +} + +template<typename BasicJsonType, typename T, std::size_t N, + enable_if_t<not std::is_constructible<typename BasicJsonType::string_t, T (&)[N]>::value, int> = 0> +void to_json(BasicJsonType &j, T (&arr)[N]) { + external_constructor<value_t::array>::construct(j, arr); +} + +template<typename BasicJsonType, typename... Args> +void to_json(BasicJsonType &j, const std::pair<Args...> &p) { + j = {p.first, p.second}; +} + +template<typename BasicJsonType, typename Tuple, std::size_t... Idx> +void to_json_tuple_impl(BasicJsonType &j, const Tuple &t, index_sequence<Idx...>) { + j = {std::get<Idx>(t)...}; +} + +template<typename BasicJsonType, typename... Args> +void to_json(BasicJsonType &j, const std::tuple<Args...> &t) { + to_json_tuple_impl(j, t, index_sequence_for<Args...>{}); +} + +struct to_json_fn { +private: + template<typename BasicJsonType, typename T> + auto call(BasicJsonType &j, T &&val, priority_tag<1>) const noexcept(noexcept(to_json(j, std::forward<T>(val)))) + -> decltype(to_json(j, std::forward<T>(val)), void()) { + return to_json(j, std::forward<T>(val)); + } + + template<typename BasicJsonType, typename T> + void call(BasicJsonType &, T &&, priority_tag<0>) const noexcept { + static_assert(sizeof(BasicJsonType) == 0, + "could not find to_json() method in T's namespace"); + +#ifdef _MSC_VER + + using decayed = uncvref_t<T>; + static_assert(sizeof(typename decayed::force_msvc_stacktrace) == 0, + "forcing MSVC stacktrace to show which T we're talking about."); +#endif + } + +public: + template<typename BasicJsonType, typename T> + void operator()(BasicJsonType &j, T &&val) const + noexcept(noexcept(std::declval<to_json_fn>().call(j, std::forward<T>(val), priority_tag<1>{}))) { + return call(j, std::forward<T>(val), priority_tag<1>{}); + } +}; +} + +namespace { +constexpr const auto &to_json = detail::static_const<detail::to_json_fn>::value; +} +} + +#include <cstring> +#include <ios> +#include <istream> + +namespace nlohmann { +namespace detail { +struct input_adapter_protocol { + virtual std::char_traits<char>::int_type get_character() = 0; + + virtual void unget_character() = 0; + + virtual ~input_adapter_protocol() = default; +}; + +using input_adapter_t = std::shared_ptr<input_adapter_protocol>; + +class input_stream_adapter : public input_adapter_protocol { +public: + ~input_stream_adapter() override { + is.clear(); + } + + explicit input_stream_adapter(std::istream &i) + : is(i), sb(*i.rdbuf()) { + std::char_traits<char>::int_type c; + if((c = get_character()) == 0xEF) { + if((c = get_character()) == 0xBB) { + if((c = get_character()) == 0xBF) { + return; + } else if(c != std::char_traits<char>::eof()) { + is.unget(); + } + is.putback('\xBB'); + } else if(c != std::char_traits<char>::eof()) { + is.unget(); + } + is.putback('\xEF'); + } else if(c != std::char_traits<char>::eof()) { + is.unget(); + } + } + + input_stream_adapter(const input_stream_adapter &) = delete; + + input_stream_adapter &operator=(input_stream_adapter &) = delete; + + std::char_traits<char>::int_type get_character() override { + return sb.sbumpc(); + } + + void unget_character() override { + sb.sungetc(); + } + +private: + std::istream &is; + std::streambuf &sb; +}; + +class input_buffer_adapter : public input_adapter_protocol { +public: + input_buffer_adapter(const char *b, const std::size_t l) + : cursor(b), limit(b+l), start(b) { + if(l >= 3 and b[0] == '\xEF' and b[1] == '\xBB' and b[2] == '\xBF') { + cursor += 3; + } + } + + input_buffer_adapter(const input_buffer_adapter &) = delete; + + input_buffer_adapter &operator=(input_buffer_adapter &) = delete; + + std::char_traits<char>::int_type get_character() noexcept override { + if(JSON_LIKELY(cursor < limit)) { + return std::char_traits<char>::to_int_type(*(cursor++)); + } + + return std::char_traits<char>::eof(); + } + + void unget_character() noexcept override { + if(JSON_LIKELY(cursor > start)) { + --cursor; + } + } + +private: + const char *cursor; + + const char *limit; + + const char *start; +}; + +class input_adapter { +public: + input_adapter(std::istream &i) + : ia(std::make_shared<input_stream_adapter>(i)) {} + + input_adapter(std::istream &&i) + : ia(std::make_shared<input_stream_adapter>(i)) {} + + template<typename CharT, + typename std::enable_if< + std::is_pointer<CharT>::value and + std::is_integral<typename std::remove_pointer<CharT>::type>::value and + sizeof(typename std::remove_pointer<CharT>::type) == 1, + int>::type = 0> + input_adapter(CharT b, std::size_t l) + : ia(std::make_shared<input_buffer_adapter>(reinterpret_cast<const char *>(b), l)) {} + + template<typename CharT, + typename std::enable_if< + std::is_pointer<CharT>::value and + std::is_integral<typename std::remove_pointer<CharT>::type>::value and + sizeof(typename std::remove_pointer<CharT>::type) == 1, + int>::type = 0> + input_adapter(CharT b) + : input_adapter(reinterpret_cast<const char *>(b), + std::strlen(reinterpret_cast<const char *>(b))) {} + + template<class IteratorType, + typename std::enable_if< + std::is_same<typename std::iterator_traits<IteratorType>::iterator_category, std::random_access_iterator_tag>::value, + int>::type = 0> + input_adapter(IteratorType first, IteratorType last) { + assert(std::accumulate( + first, last, std::pair<bool, int>(true, 0), + [&first](std::pair<bool, int> res, decltype(*first) val) { + res.first &= (val == *(std::next(std::addressof(*first), res.second++))); + return res; + }).first); + + static_assert( + sizeof(typename std::iterator_traits<IteratorType>::value_type) == 1, + "each element in the iterator range must have the size of 1 byte"); + + const auto len = static_cast<size_t>(std::distance(first, last)); + if(JSON_LIKELY(len > 0)) { + ia = std::make_shared<input_buffer_adapter>(reinterpret_cast<const char *>(&(*first)), len); + } else { + ia = std::make_shared<input_buffer_adapter>(nullptr, len); + } + } + + template<class T, std::size_t N> + input_adapter(T (&array)[N]) + : input_adapter(std::begin(array), std::end(array)) {} + + template<class ContiguousContainer, typename + std::enable_if<not std::is_pointer<ContiguousContainer>::value and + std::is_base_of<std::random_access_iterator_tag, typename std::iterator_traits<decltype(std::begin( + std::declval<ContiguousContainer const>()))>::iterator_category>::value, + int>::type = 0> + input_adapter(const ContiguousContainer &c) + : input_adapter(std::begin(c), std::end(c)) {} + + operator input_adapter_t() { + return ia; + } + +private: + input_adapter_t ia = nullptr; +}; +} +} + +#include <clocale> +#include <cstdlib> +#include <iomanip> +#include <sstream> + +namespace nlohmann { +namespace detail { +template<typename BasicJsonType> +class lexer { + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + +public: + enum class token_type { + uninitialized, + literal_true, + literal_false, + literal_null, + value_string, + value_unsigned, + value_integer, + value_float, + begin_array, + begin_object, + end_array, + end_object, + name_separator, + value_separator, + parse_error, + end_of_input, + literal_or_value + }; + + static const char *token_type_name(const token_type t) noexcept { + switch(t) { + case token_type::uninitialized: + return "<uninitialized>"; + case token_type::literal_true: + return "true literal"; + case token_type::literal_false: + return "false literal"; + case token_type::literal_null: + return "null literal"; + case token_type::value_string: + return "string literal"; + case lexer::token_type::value_unsigned: + case lexer::token_type::value_integer: + case lexer::token_type::value_float: + return "number literal"; + case token_type::begin_array: + return "'['"; + case token_type::begin_object: + return "'{'"; + case token_type::end_array: + return "']'"; + case token_type::end_object: + return "'}'"; + case token_type::name_separator: + return "':'"; + case token_type::value_separator: + return "','"; + case token_type::parse_error: + return "<parse error>"; + case token_type::end_of_input: + return "end of input"; + case token_type::literal_or_value: + return "'[', '{', or a literal"; + default: + return "unknown token"; + } + } + + explicit lexer(detail::input_adapter_t adapter) + : ia(std::move(adapter)), decimal_point_char(get_decimal_point()) {} + + lexer(const lexer &) = delete; + + lexer &operator=(lexer &) = delete; + +private: + static char get_decimal_point() noexcept { + const auto loc = localeconv(); + assert(loc != nullptr); + return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); + } + + int get_codepoint() { + assert(current == 'u'); + int codepoint = 0; + + const auto factors = {12, 8, 4, 0}; + for(const auto factor : factors) { + get(); + + if(current >= '0' and current <= '9') { + codepoint += ((current-0x30) << factor); + } else if(current >= 'A' and current <= 'F') { + codepoint += ((current-0x37) << factor); + } else if(current >= 'a' and current <= 'f') { + codepoint += ((current-0x57) << factor); + } else { + return -1; + } + } + + assert(0x0000 <= codepoint and codepoint <= 0xFFFF); + return codepoint; + } + + bool next_byte_in_range(std::initializer_list<int> ranges) { + assert(ranges.size() == 2 or ranges.size() == 4 or ranges.size() == 6); + add(current); + + for(auto range = ranges.begin(); range != ranges.end(); ++range) { + get(); + if(JSON_LIKELY(*range <= current and current <= *(++range))) { + add(current); + } else { + error_message = "invalid string: ill-formed UTF-8 byte"; + return false; + } + } + + return true; + } + + token_type scan_string() { + reset(); + + assert(current == '\"'); + + while(true) { + switch(get()) { + case std::char_traits<char>::eof(): { + error_message = "invalid string: missing closing quote"; + return token_type::parse_error; + } + + case '\"': { + return token_type::value_string; + } + + case '\\': { + switch(get()) { + case '\"': + add('\"'); + break; + + case '\\': + add('\\'); + break; + + case '/': + add('/'); + break; + + case 'b': + add('\b'); + break; + + case 'f': + add('\f'); + break; + + case 'n': + add('\n'); + break; + + case 'r': + add('\r'); + break; + + case 't': + add('\t'); + break; + + case 'u': { + const int codepoint1 = get_codepoint(); + int codepoint = codepoint1; + + if(JSON_UNLIKELY(codepoint1 == -1)) { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } + + if(0xD800 <= codepoint1 and codepoint1 <= 0xDBFF) { + if(JSON_LIKELY(get() == '\\' and get() == 'u')) { + const int codepoint2 = get_codepoint(); + + if(JSON_UNLIKELY(codepoint2 == -1)) { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } + + if(JSON_LIKELY(0xDC00 <= codepoint2 and codepoint2 <= 0xDFFF)) { + codepoint = + + (codepoint1 << 10) + + +codepoint2 + + -0x35FDC00; + } else { + error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } else { + error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } else { + if(JSON_UNLIKELY(0xDC00 <= codepoint1 and codepoint1 <= 0xDFFF)) { + error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF"; + return token_type::parse_error; + } + } + + assert(0x00 <= codepoint and codepoint <= 0x10FFFF); + + if(codepoint < 0x80) { + add(codepoint); + } else if(codepoint <= 0x7FF) { + add(0xC0 | (codepoint >> 6)); + add(0x80 | (codepoint & 0x3F)); + } else if(codepoint <= 0xFFFF) { + add(0xE0 | (codepoint >> 12)); + add(0x80 | ((codepoint >> 6) & 0x3F)); + add(0x80 | (codepoint & 0x3F)); + } else { + add(0xF0 | (codepoint >> 18)); + add(0x80 | ((codepoint >> 12) & 0x3F)); + add(0x80 | ((codepoint >> 6) & 0x3F)); + add(0x80 | (codepoint & 0x3F)); + } + + break; + } + + default: + error_message = "invalid string: forbidden character after backslash"; + return token_type::parse_error; + } + + break; + } + + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: { + error_message = "invalid string: control character must be escaped"; + return token_type::parse_error; + } + + case 0x20: + case 0x21: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5D: + case 0x5E: + case 0x5F: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: { + add(current); + break; + } + + case 0xC2: + case 0xC3: + case 0xC4: + case 0xC5: + case 0xC6: + case 0xC7: + case 0xC8: + case 0xC9: + case 0xCA: + case 0xCB: + case 0xCC: + case 0xCD: + case 0xCE: + case 0xCF: + case 0xD0: + case 0xD1: + case 0xD2: + case 0xD3: + case 0xD4: + case 0xD5: + case 0xD6: + case 0xD7: + case 0xD8: + case 0xD9: + case 0xDA: + case 0xDB: + case 0xDC: + case 0xDD: + case 0xDE: + case 0xDF: { + if(JSON_UNLIKELY(not next_byte_in_range({0x80, 0xBF}))) { + return token_type::parse_error; + } + break; + } + + case 0xE0: { + if(JSON_UNLIKELY(not(next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) { + return token_type::parse_error; + } + break; + } + + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xEE: + case 0xEF: { + if(JSON_UNLIKELY(not(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) { + return token_type::parse_error; + } + break; + } + + case 0xED: { + if(JSON_UNLIKELY(not(next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) { + return token_type::parse_error; + } + break; + } + + case 0xF0: { + if(JSON_UNLIKELY(not(next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) { + return token_type::parse_error; + } + break; + } + + case 0xF1: + case 0xF2: + case 0xF3: { + if(JSON_UNLIKELY(not(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) { + return token_type::parse_error; + } + break; + } + + case 0xF4: { + if(JSON_UNLIKELY(not(next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) { + return token_type::parse_error; + } + break; + } + + default: { + error_message = "invalid string: ill-formed UTF-8 byte"; + return token_type::parse_error; + } + } + } + } + + static void strtof(float &f, const char *str, char **endptr) noexcept { + f = std::strtof(str, endptr); + } + + static void strtof(double &f, const char *str, char **endptr) noexcept { + f = std::strtod(str, endptr); + } + + static void strtof(long double &f, const char *str, char **endptr) noexcept { + f = std::strtold(str, endptr); + } + + token_type scan_number() { + reset(); + + token_type number_type = token_type::value_unsigned; + + switch(current) { + case '-': { + add(current); + goto scan_number_minus; + } + + case '0': { + add(current); + goto scan_number_zero; + } + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + add(current); + goto scan_number_any1; + } + + default: { + assert(false); + } + } + + scan_number_minus: + + number_type = token_type::value_integer; + switch(get()) { + case '0': { + add(current); + goto scan_number_zero; + } + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + add(current); + goto scan_number_any1; + } + + default: { + error_message = "invalid number; expected digit after '-'"; + return token_type::parse_error; + } + } + + scan_number_zero: + + switch(get()) { + case '.': { + add(decimal_point_char); + goto scan_number_decimal1; + } + + case 'e': + case 'E': { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + + scan_number_any1: + + switch(get()) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + add(current); + goto scan_number_any1; + } + + case '.': { + add(decimal_point_char); + goto scan_number_decimal1; + } + + case 'e': + case 'E': { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + + scan_number_decimal1: + + number_type = token_type::value_float; + switch(get()) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + add(current); + goto scan_number_decimal2; + } + + default: { + error_message = "invalid number; expected digit after '.'"; + return token_type::parse_error; + } + } + + scan_number_decimal2: + + switch(get()) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + add(current); + goto scan_number_decimal2; + } + + case 'e': + case 'E': { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + + scan_number_exponent: + + number_type = token_type::value_float; + switch(get()) { + case '+': + case '-': { + add(current); + goto scan_number_sign; + } + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + add(current); + goto scan_number_any2; + } + + default: { + error_message = + "invalid number; expected '+', '-', or digit after exponent"; + return token_type::parse_error; + } + } + + scan_number_sign: + + switch(get()) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + add(current); + goto scan_number_any2; + } + + default: { + error_message = "invalid number; expected digit after exponent sign"; + return token_type::parse_error; + } + } + + scan_number_any2: + + switch(get()) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + add(current); + goto scan_number_any2; + } + + default: + goto scan_number_done; + } + + scan_number_done: + + unget(); + + char *endptr = nullptr; + errno = 0; + + if(number_type == token_type::value_unsigned) { + const auto x = std::strtoull(token_buffer.data(), &endptr, 10); + + assert(endptr == token_buffer.data()+token_buffer.size()); + + if(errno == 0) { + value_unsigned = static_cast<number_unsigned_t>(x); + if(value_unsigned == x) { + return token_type::value_unsigned; + } + } + } else if(number_type == token_type::value_integer) { + const auto x = std::strtoll(token_buffer.data(), &endptr, 10); + + assert(endptr == token_buffer.data()+token_buffer.size()); + + if(errno == 0) { + value_integer = static_cast<number_integer_t>(x); + if(value_integer == x) { + return token_type::value_integer; + } + } + } + + strtof(value_float, token_buffer.data(), &endptr); + + assert(endptr == token_buffer.data()+token_buffer.size()); + + return token_type::value_float; + } + + token_type scan_literal(const char *literal_text, const std::size_t length, + token_type return_type) { + assert(current == literal_text[0]); + for(std::size_t i = 1; i < length; ++i) { + if(JSON_UNLIKELY(get() != literal_text[i])) { + error_message = "invalid literal"; + return token_type::parse_error; + } + } + return return_type; + } + + void reset() noexcept { + token_buffer.clear(); + token_string.clear(); + token_string.push_back(std::char_traits<char>::to_char_type(current)); + } + + std::char_traits<char>::int_type get() { + ++chars_read; + current = ia->get_character(); + if(JSON_LIKELY(current != std::char_traits<char>::eof())) { + token_string.push_back(std::char_traits<char>::to_char_type(current)); + } + return current; + } + + void unget() { + --chars_read; + if(JSON_LIKELY(current != std::char_traits<char>::eof())) { + ia->unget_character(); + assert(token_string.size() != 0); + token_string.pop_back(); + } + } + + void add(int c) { + token_buffer.push_back(std::char_traits<char>::to_char_type(c)); + } + +public: + constexpr number_integer_t get_number_integer() const noexcept { + return value_integer; + } + + constexpr number_unsigned_t get_number_unsigned() const noexcept { + return value_unsigned; + } + + constexpr number_float_t get_number_float() const noexcept { + return value_float; + } + + string_t &&move_string() { + return std::move(token_buffer); + } + + constexpr std::size_t get_position() const noexcept { + return chars_read; + } + + std::string get_token_string() const { + std::string result; + for(const auto c : token_string) { + if('\x00' <= c and c <= '\x1F') { + std::stringstream ss; + ss << "<U+" << std::setw(4) << std::uppercase << std::setfill('0') + << std::hex << static_cast<int>(c) << ">"; + result += ss.str(); + } else { + result.push_back(c); + } + } + + return result; + } + + constexpr const char *get_error_message() const noexcept { + return error_message; + } + + token_type scan() { + do { + get(); + } while(current == ' ' or current == '\t' or current == '\n' or current == '\r'); + + switch(current) { + case '[': + return token_type::begin_array; + case ']': + return token_type::end_array; + case '{': + return token_type::begin_object; + case '}': + return token_type::end_object; + case ':': + return token_type::name_separator; + case ',': + return token_type::value_separator; + + case 't': + return scan_literal("true", 4, token_type::literal_true); + case 'f': + return scan_literal("false", 5, token_type::literal_false); + case 'n': + return scan_literal("null", 4, token_type::literal_null); + + case '\"': + return scan_string(); + + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return scan_number(); + + case '\0': + case std::char_traits<char>::eof(): + return token_type::end_of_input; + + default: + error_message = "invalid literal"; + return token_type::parse_error; + } + } + +private: + detail::input_adapter_t ia = nullptr; + + std::char_traits<char>::int_type current = std::char_traits<char>::eof(); + + std::size_t chars_read = 0; + + std::vector<char> token_string{}; + + string_t token_buffer{}; + + const char *error_message = ""; + + number_integer_t value_integer = 0; + number_unsigned_t value_unsigned = 0; + number_float_t value_float = 0; + + const char decimal_point_char = '.'; +}; +} +} + +#include <cmath> + +namespace nlohmann { +namespace detail { +template<typename BasicJsonType> +class parser { + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using lexer_t = lexer<BasicJsonType>; + using token_type = typename lexer_t::token_type; + +public: + enum class parse_event_t : uint8_t { + object_start, + + object_end, + + array_start, + + array_end, + + key, + + value + }; + + using parser_callback_t = + std::function<bool(int depth, parse_event_t event, BasicJsonType &parsed)>; + + explicit parser(detail::input_adapter_t adapter, + const parser_callback_t cb = nullptr, + const bool allow_exceptions_ = true) + : callback(cb), m_lexer(adapter), allow_exceptions(allow_exceptions_) {} + + void parse(const bool strict, BasicJsonType &result) { + get_token(); + + parse_internal(true, result); + result.assert_invariant(); + + if(strict) { + get_token(); + expect(token_type::end_of_input); + } + + if(errored) { + result = value_t::discarded; + return; + } + + if(result.is_discarded()) { + result = nullptr; + } + } + + bool accept(const bool strict = true) { + get_token(); + + if(not accept_internal()) { + return false; + } + + return not strict or (get_token() == token_type::end_of_input); + } + +private: + void parse_internal(bool keep, BasicJsonType &result) { + assert(not errored); + + if(not result.is_discarded()) { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + + switch(last_token) { + case token_type::begin_object: { + if(keep) { + if(callback) { + keep = callback(depth++, parse_event_t::object_start, result); + } + + if(not callback or keep) { + result.m_type = value_t::object; + result.m_value = value_t::object; + } + } + + get_token(); + + if(last_token == token_type::end_object) { + if(keep and callback and not callback(--depth, parse_event_t::object_end, result)) { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } + + string_t key; + BasicJsonType value; + while(true) { + if(not expect(token_type::value_string)) { + return; + } + key = m_lexer.move_string(); + + bool keep_tag = false; + if(keep) { + if(callback) { + BasicJsonType k(key); + keep_tag = callback(depth, parse_event_t::key, k); + } else { + keep_tag = true; + } + } + + get_token(); + if(not expect(token_type::name_separator)) { + return; + } + + get_token(); + value.m_value.destroy(value.m_type); + value.m_type = value_t::discarded; + parse_internal(keep, value); + + if(JSON_UNLIKELY(errored)) { + return; + } + + if(keep and keep_tag and not value.is_discarded()) { + result.m_value.object->emplace(std::move(key), std::move(value)); + } + + get_token(); + if(last_token == token_type::value_separator) { + get_token(); + continue; + } + + if(not expect(token_type::end_object)) { + return; + } + break; + } + + if(keep and callback and not callback(--depth, parse_event_t::object_end, result)) { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } + + case token_type::begin_array: { + if(keep) { + if(callback) { + keep = callback(depth++, parse_event_t::array_start, result); + } + + if(not callback or keep) { + result.m_type = value_t::array; + result.m_value = value_t::array; + } + } + + get_token(); + + if(last_token == token_type::end_array) { + if(callback and not callback(--depth, parse_event_t::array_end, result)) { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } + + BasicJsonType value; + while(true) { + value.m_value.destroy(value.m_type); + value.m_type = value_t::discarded; + parse_internal(keep, value); + + if(JSON_UNLIKELY(errored)) { + return; + } + + if(keep and not value.is_discarded()) { + result.m_value.array->push_back(std::move(value)); + } + + get_token(); + if(last_token == token_type::value_separator) { + get_token(); + continue; + } + + if(not expect(token_type::end_array)) { + return; + } + break; + } + + if(keep and callback and not callback(--depth, parse_event_t::array_end, result)) { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } + + case token_type::literal_null: { + result.m_type = value_t::null; + break; + } + + case token_type::value_string: { + result.m_type = value_t::string; + result.m_value = m_lexer.move_string(); + break; + } + + case token_type::literal_true: { + result.m_type = value_t::boolean; + result.m_value = true; + break; + } + + case token_type::literal_false: { + result.m_type = value_t::boolean; + result.m_value = false; + break; + } + + case token_type::value_unsigned: { + result.m_type = value_t::number_unsigned; + result.m_value = m_lexer.get_number_unsigned(); + break; + } + + case token_type::value_integer: { + result.m_type = value_t::number_integer; + result.m_value = m_lexer.get_number_integer(); + break; + } + + case token_type::value_float: { + result.m_type = value_t::number_float; + result.m_value = m_lexer.get_number_float(); + + if(JSON_UNLIKELY(not std::isfinite(result.m_value.number_float))) { + if(allow_exceptions) { + JSON_THROW(out_of_range::create(406, "number overflow parsing '"+ + m_lexer.get_token_string()+"'")); + } + expect(token_type::uninitialized); + } + break; + } + + case token_type::parse_error: { + if(not expect(token_type::uninitialized)) { + return; + } + break; + } + + default: { + if(not expect(token_type::literal_or_value)) { + return; + } + break; + } + } + + if(keep and callback and not callback(depth, parse_event_t::value, result)) { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + } + + bool accept_internal() { + switch(last_token) { + case token_type::begin_object: { + get_token(); + + if(last_token == token_type::end_object) { + return true; + } + + while(true) { + if(last_token != token_type::value_string) { + return false; + } + + get_token(); + if(last_token != token_type::name_separator) { + return false; + } + + get_token(); + if(not accept_internal()) { + return false; + } + + get_token(); + if(last_token == token_type::value_separator) { + get_token(); + continue; + } + + return (last_token == token_type::end_object); + } + } + + case token_type::begin_array: { + get_token(); + + if(last_token == token_type::end_array) { + return true; + } + + while(true) { + if(not accept_internal()) { + return false; + } + + get_token(); + if(last_token == token_type::value_separator) { + get_token(); + continue; + } + + return (last_token == token_type::end_array); + } + } + + case token_type::value_float: { + return std::isfinite(m_lexer.get_number_float()); + } + + case token_type::literal_false: + case token_type::literal_null: + case token_type::literal_true: + case token_type::value_integer: + case token_type::value_string: + case token_type::value_unsigned: + return true; + + default: + return false; + } + } + + token_type get_token() { + return (last_token = m_lexer.scan()); + } + + bool expect(token_type t) { + if(JSON_UNLIKELY(t != last_token)) { + errored = true; + expected = t; + if(allow_exceptions) { + throw_exception(); + } else { + return false; + } + } + + return true; + } + + [[noreturn]] void throw_exception() const { + std::string error_msg = "syntax error - "; + if(last_token == token_type::parse_error) { + error_msg += std::string(m_lexer.get_error_message())+"; last read: '"+ + m_lexer.get_token_string()+"'"; + } else { + error_msg += "unexpected "+std::string(lexer_t::token_type_name(last_token)); + } + + if(expected != token_type::uninitialized) { + error_msg += "; expected "+std::string(lexer_t::token_type_name(expected)); + } + + JSON_THROW(parse_error::create(101, m_lexer.get_position(), error_msg)); + } + +private: + int depth = 0; + + const parser_callback_t callback = nullptr; + + token_type last_token = token_type::uninitialized; + + lexer_t m_lexer; + + bool errored = false; + + token_type expected = token_type::uninitialized; + + const bool allow_exceptions = true; +}; +} +} + +namespace nlohmann { +namespace detail { +class primitive_iterator_t { +private: + using difference_type = std::ptrdiff_t; + static constexpr difference_type begin_value = 0; + static constexpr difference_type end_value = begin_value+1; + + difference_type m_it = (std::numeric_limits<std::ptrdiff_t>::min)(); + +public: + constexpr difference_type get_value() const noexcept { + return m_it; + } + + void set_begin() noexcept { + m_it = begin_value; + } + + void set_end() noexcept { + m_it = end_value; + } + + constexpr bool is_begin() const noexcept { + return m_it == begin_value; + } + + constexpr bool is_end() const noexcept { + return m_it == end_value; + } + + friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { + return lhs.m_it == rhs.m_it; + } + + friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { + return lhs.m_it < rhs.m_it; + } + + primitive_iterator_t operator+(difference_type n) noexcept { + auto result = *this; + result += n; + return result; + } + + friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { + return lhs.m_it-rhs.m_it; + } + + primitive_iterator_t &operator++() noexcept { + ++m_it; + return *this; + } + + primitive_iterator_t const operator++(int) noexcept { + auto result = *this; + m_it++; + return result; + } + + primitive_iterator_t &operator--() noexcept { + --m_it; + return *this; + } + + primitive_iterator_t const operator--(int) noexcept { + auto result = *this; + m_it--; + return result; + } + + primitive_iterator_t &operator+=(difference_type n) noexcept { + m_it += n; + return *this; + } + + primitive_iterator_t &operator-=(difference_type n) noexcept { + m_it -= n; + return *this; + } +}; +} +} + +namespace nlohmann { +namespace detail { +template<typename BasicJsonType> +struct internal_iterator { + typename BasicJsonType::object_t::iterator object_iterator{}; + + typename BasicJsonType::array_t::iterator array_iterator{}; + + primitive_iterator_t primitive_iterator{}; +}; +} +} + +namespace nlohmann { +namespace detail { +template<typename IteratorType> +class iteration_proxy; + +template<typename BasicJsonType> +class iter_impl { + friend iter_impl<typename std::conditional<std::is_const<BasicJsonType>::value, typename std::remove_const<BasicJsonType>::type, const BasicJsonType>::type>; + friend BasicJsonType; + friend iteration_proxy<iter_impl>; + + using object_t = typename BasicJsonType::object_t; + using array_t = typename BasicJsonType::array_t; + + static_assert(is_basic_json<typename std::remove_const<BasicJsonType>::type>::value, + "iter_impl only accepts (const) basic_json"); + +public: + using iterator_category = std::bidirectional_iterator_tag; + + using value_type = typename BasicJsonType::value_type; + + using difference_type = typename BasicJsonType::difference_type; + + using pointer = typename std::conditional<std::is_const<BasicJsonType>::value, + typename BasicJsonType::const_pointer, + typename BasicJsonType::pointer>::type; + + using reference = + typename std::conditional<std::is_const<BasicJsonType>::value, + typename BasicJsonType::const_reference, + typename BasicJsonType::reference>::type; + + iter_impl() = default; + + explicit iter_impl(pointer object) noexcept : m_object(object) { + assert(m_object != nullptr); + + switch(m_object->m_type) { + case value_t::object: { + m_it.object_iterator = typename object_t::iterator(); + break; + } + + case value_t::array: { + m_it.array_iterator = typename array_t::iterator(); + break; + } + + default: { + m_it.primitive_iterator = primitive_iterator_t(); + break; + } + } + } + + iter_impl(const iter_impl<typename std::remove_const<BasicJsonType>::type> &other) noexcept + : m_object(other.m_object), m_it(other.m_it) {} + + iter_impl &operator=(const iter_impl<typename std::remove_const<BasicJsonType>::type> &other) noexcept { + m_object = other.m_object; + m_it = other.m_it; + return *this; + } + +private: + void set_begin() noexcept { + assert(m_object != nullptr); + + switch(m_object->m_type) { + case value_t::object: { + m_it.object_iterator = m_object->m_value.object->begin(); + break; + } + + case value_t::array: { + m_it.array_iterator = m_object->m_value.array->begin(); + break; + } + + case value_t::null: { + m_it.primitive_iterator.set_end(); + break; + } + + default: { + m_it.primitive_iterator.set_begin(); + break; + } + } + } + + void set_end() noexcept { + assert(m_object != nullptr); + + switch(m_object->m_type) { + case value_t::object: { + m_it.object_iterator = m_object->m_value.object->end(); + break; + } + + case value_t::array: { + m_it.array_iterator = m_object->m_value.array->end(); + break; + } + + default: { + m_it.primitive_iterator.set_end(); + break; + } + } + } + +public: + reference operator*() const { + assert(m_object != nullptr); + + switch(m_object->m_type) { + case value_t::object: { + assert(m_it.object_iterator != m_object->m_value.object->end()); + return m_it.object_iterator->second; + } + + case value_t::array: { + assert(m_it.array_iterator != m_object->m_value.array->end()); + return *m_it.array_iterator; + } + + case value_t::null: + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + + default: { + if(JSON_LIKELY(m_it.primitive_iterator.is_begin())) { + return *m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + pointer operator->() const { + assert(m_object != nullptr); + + switch(m_object->m_type) { + case value_t::object: { + assert(m_it.object_iterator != m_object->m_value.object->end()); + return &(m_it.object_iterator->second); + } + + case value_t::array: { + assert(m_it.array_iterator != m_object->m_value.array->end()); + return &*m_it.array_iterator; + } + + default: { + if(JSON_LIKELY(m_it.primitive_iterator.is_begin())) { + return m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + iter_impl const operator++(int) { + auto result = *this; + ++(*this); + return result; + } + + iter_impl &operator++() { + assert(m_object != nullptr); + + switch(m_object->m_type) { + case value_t::object: { + std::advance(m_it.object_iterator, 1); + break; + } + + case value_t::array: { + std::advance(m_it.array_iterator, 1); + break; + } + + default: { + ++m_it.primitive_iterator; + break; + } + } + + return *this; + } + + iter_impl const operator--(int) { + auto result = *this; + --(*this); + return result; + } + + iter_impl &operator--() { + assert(m_object != nullptr); + + switch(m_object->m_type) { + case value_t::object: { + std::advance(m_it.object_iterator, -1); + break; + } + + case value_t::array: { + std::advance(m_it.array_iterator, -1); + break; + } + + default: { + --m_it.primitive_iterator; + break; + } + } + + return *this; + } + + bool operator==(const iter_impl &other) const { + if(JSON_UNLIKELY(m_object != other.m_object)) { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); + } + + assert(m_object != nullptr); + + switch(m_object->m_type) { + case value_t::object: + return (m_it.object_iterator == other.m_it.object_iterator); + + case value_t::array: + return (m_it.array_iterator == other.m_it.array_iterator); + + default: + return (m_it.primitive_iterator == other.m_it.primitive_iterator); + } + } + + bool operator!=(const iter_impl &other) const { + return not operator==(other); + } + + bool operator<(const iter_impl &other) const { + if(JSON_UNLIKELY(m_object != other.m_object)) { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); + } + + assert(m_object != nullptr); + + switch(m_object->m_type) { + case value_t::object: + JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators")); + + case value_t::array: + return (m_it.array_iterator < other.m_it.array_iterator); + + default: + return (m_it.primitive_iterator < other.m_it.primitive_iterator); + } + } + + bool operator<=(const iter_impl &other) const { + return not other.operator<(*this); + } + + bool operator>(const iter_impl &other) const { + return not operator<=(other); + } + + bool operator>=(const iter_impl &other) const { + return not operator<(other); + } + + iter_impl &operator+=(difference_type i) { + assert(m_object != nullptr); + + switch(m_object->m_type) { + case value_t::object: + JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); + + case value_t::array: { + std::advance(m_it.array_iterator, i); + break; + } + + default: { + m_it.primitive_iterator += i; + break; + } + } + + return *this; + } + + iter_impl &operator-=(difference_type i) { + return operator+=(-i); + } + + iter_impl operator+(difference_type i) const { + auto result = *this; + result += i; + return result; + } + + friend iter_impl operator+(difference_type i, const iter_impl &it) { + auto result = it; + result += i; + return result; + } + + iter_impl operator-(difference_type i) const { + auto result = *this; + result -= i; + return result; + } + + difference_type operator-(const iter_impl &other) const { + assert(m_object != nullptr); + + switch(m_object->m_type) { + case value_t::object: + JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); + + case value_t::array: + return m_it.array_iterator-other.m_it.array_iterator; + + default: + return m_it.primitive_iterator-other.m_it.primitive_iterator; + } + } + + reference operator[](difference_type n) const { + assert(m_object != nullptr); + + switch(m_object->m_type) { + case value_t::object: + JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators")); + + case value_t::array: + return *std::next(m_it.array_iterator, n); + + case value_t::null: + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + + default: { + if(JSON_LIKELY(m_it.primitive_iterator.get_value() == -n)) { + return *m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + typename object_t::key_type key() const { + assert(m_object != nullptr); + + if(JSON_LIKELY(m_object->is_object())) { + return m_it.object_iterator->first; + } + + JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators")); + } + + reference value() const { + return operator*(); + } + +private: + pointer m_object = nullptr; + + internal_iterator<typename std::remove_const<BasicJsonType>::type> m_it; +}; +} +} + +namespace nlohmann { +namespace detail { +template<typename IteratorType> +class iteration_proxy { +private: + class iteration_proxy_internal { + private: + IteratorType anchor; + + std::size_t array_index = 0; + + public: + explicit iteration_proxy_internal(IteratorType it) noexcept : anchor(it) {} + + iteration_proxy_internal &operator*() { + return *this; + } + + iteration_proxy_internal &operator++() { + ++anchor; + ++array_index; + + return *this; + } + + bool operator!=(const iteration_proxy_internal &o) const noexcept { + return anchor != o.anchor; + } + + std::string key() const { + assert(anchor.m_object != nullptr); + + switch(anchor.m_object->type()) { + case value_t::array: + return std::to_string(array_index); + + case value_t::object: + return anchor.key(); + + default: + return ""; + } + } + + typename IteratorType::reference value() const { + return anchor.value(); + } + }; + + typename IteratorType::reference container; + +public: + explicit iteration_proxy(typename IteratorType::reference cont) noexcept + : container(cont) {} + + iteration_proxy_internal begin() noexcept { + return iteration_proxy_internal(container.begin()); + } + + iteration_proxy_internal end() noexcept { + return iteration_proxy_internal(container.end()); + } +}; +} +} + +namespace nlohmann { +namespace detail { +template<typename Base> +class json_reverse_iterator : public std::reverse_iterator<Base> { +public: + using difference_type = std::ptrdiff_t; + + using base_iterator = std::reverse_iterator<Base>; + + using reference = typename Base::reference; + + json_reverse_iterator(const typename base_iterator::iterator_type &it) noexcept + : base_iterator(it) {} + + json_reverse_iterator(const base_iterator &it) noexcept : base_iterator(it) {} + + json_reverse_iterator const operator++(int) { + return static_cast<json_reverse_iterator>(base_iterator::operator++(1)); + } + + json_reverse_iterator &operator++() { + return static_cast<json_reverse_iterator &>(base_iterator::operator++()); + } + + json_reverse_iterator const operator--(int) { + return static_cast<json_reverse_iterator>(base_iterator::operator--(1)); + } + + json_reverse_iterator &operator--() { + return static_cast<json_reverse_iterator &>(base_iterator::operator--()); + } + + json_reverse_iterator &operator+=(difference_type i) { + return static_cast<json_reverse_iterator &>(base_iterator::operator+=(i)); + } + + json_reverse_iterator operator+(difference_type i) const { + return static_cast<json_reverse_iterator>(base_iterator::operator+(i)); + } + + json_reverse_iterator operator-(difference_type i) const { + return static_cast<json_reverse_iterator>(base_iterator::operator-(i)); + } + + difference_type operator-(const json_reverse_iterator &other) const { + return base_iterator(*this)-base_iterator(other); + } + + reference operator[](difference_type n) const { + return *(this->operator+(n)); + } + + auto key() const -> decltype(std::declval<Base>().key()) { + auto it = --this->base(); + return it.key(); + } + + reference value() const { + auto it = --this->base(); + return it.operator*(); + } +}; +} +} + +#include <ostream> + +namespace nlohmann { +namespace detail { +template<typename CharType> +struct output_adapter_protocol { + virtual void write_character(CharType c) = 0; + + virtual void write_characters(const CharType *s, std::size_t length) = 0; + + virtual ~output_adapter_protocol() = default; +}; + +template<typename CharType> +using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>; + +template<typename CharType> +class output_vector_adapter : public output_adapter_protocol<CharType> { +public: + explicit output_vector_adapter(std::vector<CharType> &vec) : v(vec) {} + + void write_character(CharType c) override { + v.push_back(c); + } + + void write_characters(const CharType *s, std::size_t length) override { + std::copy(s, s+length, std::back_inserter(v)); + } + +private: + std::vector<CharType> &v; +}; + +template<typename CharType> +class output_stream_adapter : public output_adapter_protocol<CharType> { +public: + explicit output_stream_adapter(std::basic_ostream<CharType> &s) : stream(s) {} + + void write_character(CharType c) override { + stream.put(c); + } + + void write_characters(const CharType *s, std::size_t length) override { + stream.write(s, static_cast<std::streamsize>(length)); + } + +private: + std::basic_ostream<CharType> &stream; +}; + +template<typename CharType, typename StringType = std::basic_string<CharType>> +class output_string_adapter : public output_adapter_protocol<CharType> { +public: + explicit output_string_adapter(StringType &s) : str(s) {} + + void write_character(CharType c) override { + str.push_back(c); + } + + void write_characters(const CharType *s, std::size_t length) override { + str.append(s, length); + } + +private: + StringType &str; +}; + +template<typename CharType, typename StringType = std::basic_string<CharType>> +class output_adapter { +public: + output_adapter(std::vector<CharType> &vec) + : oa(std::make_shared<output_vector_adapter<CharType>>(vec)) {} + + output_adapter(std::basic_ostream<CharType> &s) + : oa(std::make_shared<output_stream_adapter<CharType>>(s)) {} + + output_adapter(StringType &s) + : oa(std::make_shared<output_string_adapter<CharType, StringType>>(s)) {} + + operator output_adapter_t<CharType>() { + return oa; + } + +private: + output_adapter_t<CharType> oa = nullptr; +}; +} +} + +namespace nlohmann { +namespace detail { +template<typename BasicJsonType> +class binary_reader { + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using string_t = typename BasicJsonType::string_t; + +public: + explicit binary_reader(input_adapter_t adapter) : ia(std::move(adapter)) { + assert(ia); + } + + BasicJsonType parse_cbor(const bool strict) { + const auto res = parse_cbor_internal(); + if(strict) { + get(); + expect_eof(); + } + return res; + } + + BasicJsonType parse_msgpack(const bool strict) { + const auto res = parse_msgpack_internal(); + if(strict) { + get(); + expect_eof(); + } + return res; + } + + BasicJsonType parse_ubjson(const bool strict) { + const auto res = parse_ubjson_internal(); + if(strict) { + get_ignore_noop(); + expect_eof(); + } + return res; + } + + static constexpr bool little_endianess(int num = 1) noexcept { + return (*reinterpret_cast<char *>(&num) == 1); + } + +private: + BasicJsonType parse_cbor_internal(const bool get_char = true) { + switch(get_char ? get() : current) { + case std::char_traits<char>::eof(): + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + return static_cast<number_unsigned_t>(current); + + case 0x18: + return get_number<uint8_t>(); + + case 0x19: + return get_number<uint16_t>(); + + case 0x1A: + return get_number<uint32_t>(); + + case 0x1B: + return get_number<uint64_t>(); + + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + return static_cast<int8_t>(0x20-1-current); + + case 0x38: { + return static_cast<number_integer_t>(-1)-get_number<uint8_t>(); + } + + case 0x39: { + return static_cast<number_integer_t>(-1)-get_number<uint16_t>(); + } + + case 0x3A: { + return static_cast<number_integer_t>(-1)-get_number<uint32_t>(); + } + + case 0x3B: { + return static_cast<number_integer_t>(-1)- + static_cast<number_integer_t>(get_number<uint64_t>()); + } + + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7F: { + return get_cbor_string(); + } + + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: { + return get_cbor_array(current & 0x1F); + } + + case 0x98: { + return get_cbor_array(get_number<uint8_t>()); + } + + case 0x99: { + return get_cbor_array(get_number<uint16_t>()); + } + + case 0x9A: { + return get_cbor_array(get_number<uint32_t>()); + } + + case 0x9B: { + return get_cbor_array(get_number<uint64_t>()); + } + + case 0x9F: { + BasicJsonType result = value_t::array; + while(get() != 0xFF) { + result.push_back(parse_cbor_internal(false)); + } + return result; + } + + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: { + return get_cbor_object(current & 0x1F); + } + + case 0xB8: { + return get_cbor_object(get_number<uint8_t>()); + } + + case 0xB9: { + return get_cbor_object(get_number<uint16_t>()); + } + + case 0xBA: { + return get_cbor_object(get_number<uint32_t>()); + } + + case 0xBB: { + return get_cbor_object(get_number<uint64_t>()); + } + + case 0xBF: { + BasicJsonType result = value_t::object; + while(get() != 0xFF) { + auto key = get_cbor_string(); + result[key] = parse_cbor_internal(); + } + return result; + } + + case 0xF4: { + return false; + } + + case 0xF5: { + return true; + } + + case 0xF6: { + return value_t::null; + } + + case 0xF9: { + const int byte1 = get(); + unexpect_eof(); + const int byte2 = get(); + unexpect_eof(); + + const int half = (byte1 << 8)+byte2; + const int exp = (half >> 10) & 0x1F; + const int mant = half & 0x3FF; + double val; + if(exp == 0) { + val = std::ldexp(mant, -24); + } else if(exp != 31) { + val = std::ldexp(mant+1024, exp-25); + } else { + val = (mant == 0) ? std::numeric_limits<double>::infinity() + : std::numeric_limits<double>::quiet_NaN(); + } + return (half & 0x8000) != 0 ? -val : val; + } + + case 0xFA: { + return get_number<float>(); + } + + case 0xFB: { + return get_number<double>(); + } + + default: { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, "error reading CBOR; last byte: 0x"+ss.str())); + } + } + } + + BasicJsonType parse_msgpack_internal() { + switch(get()) { + case std::char_traits<char>::eof(): + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5C: + case 0x5D: + case 0x5E: + case 0x5F: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: + return static_cast<number_unsigned_t>(current); + + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: { + return get_msgpack_object(current & 0x0F); + } + + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + case 0x98: + case 0x99: + case 0x9A: + case 0x9B: + case 0x9C: + case 0x9D: + case 0x9E: + case 0x9F: { + return get_msgpack_array(current & 0x0F); + } + + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + case 0xB8: + case 0xB9: + case 0xBA: + case 0xBB: + case 0xBC: + case 0xBD: + case 0xBE: + case 0xBF: + return get_msgpack_string(); + + case 0xC0: + return value_t::null; + + case 0xC2: + return false; + + case 0xC3: + return true; + + case 0xCA: + return get_number<float>(); + + case 0xCB: + return get_number<double>(); + + case 0xCC: + return get_number<uint8_t>(); + + case 0xCD: + return get_number<uint16_t>(); + + case 0xCE: + return get_number<uint32_t>(); + + case 0xCF: + return get_number<uint64_t>(); + + case 0xD0: + return get_number<int8_t>(); + + case 0xD1: + return get_number<int16_t>(); + + case 0xD2: + return get_number<int32_t>(); + + case 0xD3: + return get_number<int64_t>(); + + case 0xD9: + case 0xDA: + case 0xDB: + return get_msgpack_string(); + + case 0xDC: { + return get_msgpack_array(get_number<uint16_t>()); + } + + case 0xDD: { + return get_msgpack_array(get_number<uint32_t>()); + } + + case 0xDE: { + return get_msgpack_object(get_number<uint16_t>()); + } + + case 0xDF: { + return get_msgpack_object(get_number<uint32_t>()); + } + + case 0xE0: + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xED: + case 0xEE: + case 0xEF: + case 0xF0: + case 0xF1: + case 0xF2: + case 0xF3: + case 0xF4: + case 0xF5: + case 0xF6: + case 0xF7: + case 0xF8: + case 0xF9: + case 0xFA: + case 0xFB: + case 0xFC: + case 0xFD: + case 0xFE: + case 0xFF: + return static_cast<int8_t>(current); + + default: { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, + "error reading MessagePack; last byte: 0x"+ss.str())); + } + } + } + + BasicJsonType parse_ubjson_internal(const bool get_char = true) { + return get_ubjson_value(get_char ? get_ignore_noop() : current); + } + + int get() { + ++chars_read; + return (current = ia->get_character()); + } + + int get_ignore_noop() { + do { + get(); + } while(current == 'N'); + + return current; + } + + template<typename NumberType> + NumberType get_number() { + std::array<uint8_t, sizeof(NumberType)> vec; + for(std::size_t i = 0; i < sizeof(NumberType); ++i) { + get(); + unexpect_eof(); + + if(is_little_endian) { + vec[sizeof(NumberType)-i-1] = static_cast<uint8_t>(current); + } else { + vec[i] = static_cast<uint8_t>(current); + } + } + + NumberType result; + std::memcpy(&result, vec.data(), sizeof(NumberType)); + return result; + } + + template<typename NumberType> + string_t get_string(const NumberType len) { + string_t result; + std::generate_n(std::back_inserter(result), len, [this]() { + get(); + unexpect_eof(); + return static_cast<char>(current); + }); + return result; + } + + string_t get_cbor_string() { + unexpect_eof(); + + switch(current) { + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: { + return get_string(current & 0x1F); + } + + case 0x78: { + return get_string(get_number<uint8_t>()); + } + + case 0x79: { + return get_string(get_number<uint16_t>()); + } + + case 0x7A: { + return get_string(get_number<uint32_t>()); + } + + case 0x7B: { + return get_string(get_number<uint64_t>()); + } + + case 0x7F: { + string_t result; + while(get() != 0xFF) { + result.append(get_cbor_string()); + } + return result; + } + + default: { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, "expected a CBOR string; last byte: 0x"+ss.str())); + } + } + } + + template<typename NumberType> + BasicJsonType get_cbor_array(const NumberType len) { + BasicJsonType result = value_t::array; + std::generate_n(std::back_inserter(*result.m_value.array), len, [this]() { + return parse_cbor_internal(); + }); + return result; + } + + template<typename NumberType> + BasicJsonType get_cbor_object(const NumberType len) { + BasicJsonType result = value_t::object; + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + len, [this]() { + get(); + auto key = get_cbor_string(); + auto val = parse_cbor_internal(); + return std::make_pair(std::move(key), std::move(val)); + }); + return result; + } + + string_t get_msgpack_string() { + unexpect_eof(); + + switch(current) { + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + case 0xB8: + case 0xB9: + case 0xBA: + case 0xBB: + case 0xBC: + case 0xBD: + case 0xBE: + case 0xBF: { + return get_string(current & 0x1F); + } + + case 0xD9: { + return get_string(get_number<uint8_t>()); + } + + case 0xDA: { + return get_string(get_number<uint16_t>()); + } + + case 0xDB: { + return get_string(get_number<uint32_t>()); + } + + default: { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, + "expected a MessagePack string; last byte: 0x"+ss.str())); + } + } + } + + template<typename NumberType> + BasicJsonType get_msgpack_array(const NumberType len) { + BasicJsonType result = value_t::array; + std::generate_n(std::back_inserter(*result.m_value.array), len, [this]() { + return parse_msgpack_internal(); + }); + return result; + } + + template<typename NumberType> + BasicJsonType get_msgpack_object(const NumberType len) { + BasicJsonType result = value_t::object; + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + len, [this]() { + get(); + auto key = get_msgpack_string(); + auto val = parse_msgpack_internal(); + return std::make_pair(std::move(key), std::move(val)); + }); + return result; + } + + string_t get_ubjson_string(const bool get_char = true) { + if(get_char) { + get(); + } + + unexpect_eof(); + + switch(current) { + case 'U': + return get_string(get_number<uint8_t>()); + case 'i': + return get_string(get_number<int8_t>()); + case 'I': + return get_string(get_number<int16_t>()); + case 'l': + return get_string(get_number<int32_t>()); + case 'L': + return get_string(get_number<int64_t>()); + default: + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, + "expected a UBJSON string; last byte: 0x"+ss.str())); + } + } + + std::pair<std::size_t, int> get_ubjson_size_type() { + std::size_t sz = string_t::npos; + int tc = 0; + + get_ignore_noop(); + + if(current == '$') { + tc = get(); + unexpect_eof(); + + get_ignore_noop(); + if(current != '#') { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, + "expected '#' after UBJSON type information; last byte: 0x"+ss.str())); + } + sz = parse_ubjson_internal(); + } else if(current == '#') { + sz = parse_ubjson_internal(); + } + + return std::make_pair(sz, tc); + } + + BasicJsonType get_ubjson_value(const int prefix) { + switch(prefix) { + case std::char_traits<char>::eof(): + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + + case 'T': + return true; + case 'F': + return false; + + case 'Z': + return nullptr; + + case 'U': + return get_number<uint8_t>(); + case 'i': + return get_number<int8_t>(); + case 'I': + return get_number<int16_t>(); + case 'l': + return get_number<int32_t>(); + case 'L': + return get_number<int64_t>(); + case 'd': + return get_number<float>(); + case 'D': + return get_number<double>(); + + case 'C': { + get(); + unexpect_eof(); + if(JSON_UNLIKELY(current > 127)) { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, + "byte after 'C' must be in range 0x00..0x7F; last byte: 0x"+ss.str())); + } + return string_t(1, static_cast<char>(current)); + } + + case 'S': + return get_ubjson_string(); + + case '[': + return get_ubjson_array(); + + case '{': + return get_ubjson_object(); + + default: + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, + "error reading UBJSON; last byte: 0x"+ss.str())); + } + } + + BasicJsonType get_ubjson_array() { + BasicJsonType result = value_t::array; + const auto size_and_type = get_ubjson_size_type(); + + if(size_and_type.first != string_t::npos) { + if(JSON_UNLIKELY(size_and_type.first > result.max_size())) { + JSON_THROW(out_of_range::create(408, + "excessive array size: "+std::to_string(size_and_type.first))); + } + + if(size_and_type.second != 0) { + if(size_and_type.second != 'N') { + std::generate_n(std::back_inserter(*result.m_value.array), + size_and_type.first, [this, size_and_type]() { + return get_ubjson_value(size_and_type.second); + }); + } + } else { + std::generate_n(std::back_inserter(*result.m_value.array), + size_and_type.first, [this]() { + return parse_ubjson_internal(); + }); + } + } else { + while(current != ']') { + result.push_back(parse_ubjson_internal(false)); + get_ignore_noop(); + } + } + + return result; + } + + BasicJsonType get_ubjson_object() { + BasicJsonType result = value_t::object; + const auto size_and_type = get_ubjson_size_type(); + + if(size_and_type.first != string_t::npos) { + if(JSON_UNLIKELY(size_and_type.first > result.max_size())) { + JSON_THROW(out_of_range::create(408, + "excessive object size: "+std::to_string(size_and_type.first))); + } + + if(size_and_type.second != 0) { + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + size_and_type.first, [this, size_and_type]() { + auto key = get_ubjson_string(); + auto val = get_ubjson_value(size_and_type.second); + return std::make_pair(std::move(key), std::move(val)); + }); + } else { + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + size_and_type.first, [this]() { + auto key = get_ubjson_string(); + auto val = parse_ubjson_internal(); + return std::make_pair(std::move(key), std::move(val)); + }); + } + } else { + while(current != '}') { + auto key = get_ubjson_string(false); + result[std::move(key)] = parse_ubjson_internal(); + get_ignore_noop(); + } + } + + return result; + } + + void expect_eof() const { + if(JSON_UNLIKELY(current != std::char_traits<char>::eof())) { + JSON_THROW(parse_error::create(110, chars_read, "expected end of input")); + } + } + + void unexpect_eof() const { + if(JSON_UNLIKELY(current == std::char_traits<char>::eof())) { + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + } + } + +private: + input_adapter_t ia = nullptr; + + int current = std::char_traits<char>::eof(); + + std::size_t chars_read = 0; + + const bool is_little_endian = little_endianess(); +}; +} +} + +namespace nlohmann { +namespace detail { +template<typename BasicJsonType, typename CharType> +class binary_writer { +public: + explicit binary_writer(output_adapter_t<CharType> adapter) : oa(adapter) { + assert(oa); + } + + void write_cbor(const BasicJsonType &j) { + switch(j.type()) { + case value_t::null: { + oa->write_character(static_cast<CharType>(0xF6)); + break; + } + + case value_t::boolean: { + oa->write_character(j.m_value.boolean + ? static_cast<CharType>(0xF5) + : static_cast<CharType>(0xF4)); + break; + } + + case value_t::number_integer: { + if(j.m_value.number_integer >= 0) { + if(j.m_value.number_integer <= 0x17) { + write_number(static_cast<uint8_t>(j.m_value.number_integer)); + } else if(j.m_value.number_integer <= (std::numeric_limits<uint8_t>::max) ()) { + oa->write_character(static_cast<CharType>(0x18)); + write_number(static_cast<uint8_t>(j.m_value.number_integer)); + } else if(j.m_value.number_integer <= (std::numeric_limits<uint16_t>::max) ()) { + oa->write_character(static_cast<CharType>(0x19)); + write_number(static_cast<uint16_t>(j.m_value.number_integer)); + } else if(j.m_value.number_integer <= (std::numeric_limits<uint32_t>::max) ()) { + oa->write_character(static_cast<CharType>(0x1A)); + write_number(static_cast<uint32_t>(j.m_value.number_integer)); + } else { + oa->write_character(static_cast<CharType>(0x1B)); + write_number(static_cast<uint64_t>(j.m_value.number_integer)); + } + } else { + const auto positive_number = -1-j.m_value.number_integer; + if(j.m_value.number_integer >= -24) { + write_number(static_cast<uint8_t>(0x20+positive_number)); + } else if(positive_number <= (std::numeric_limits<uint8_t>::max) ()) { + oa->write_character(static_cast<CharType>(0x38)); + write_number(static_cast<uint8_t>(positive_number)); + } else if(positive_number <= (std::numeric_limits<uint16_t>::max) ()) { + oa->write_character(static_cast<CharType>(0x39)); + write_number(static_cast<uint16_t>(positive_number)); + } else if(positive_number <= (std::numeric_limits<uint32_t>::max) ()) { + oa->write_character(static_cast<CharType>(0x3A)); + write_number(static_cast<uint32_t>(positive_number)); + } else { + oa->write_character(static_cast<CharType>(0x3B)); + write_number(static_cast<uint64_t>(positive_number)); + } + } + break; + } + + case value_t::number_unsigned: { + if(j.m_value.number_unsigned <= 0x17) { + write_number(static_cast<uint8_t>(j.m_value.number_unsigned)); + } else if(j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max) ()) { + oa->write_character(static_cast<CharType>(0x18)); + write_number(static_cast<uint8_t>(j.m_value.number_unsigned)); + } else if(j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max) ()) { + oa->write_character(static_cast<CharType>(0x19)); + write_number(static_cast<uint16_t>(j.m_value.number_unsigned)); + } else if(j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max) ()) { + oa->write_character(static_cast<CharType>(0x1A)); + write_number(static_cast<uint32_t>(j.m_value.number_unsigned)); + } else { + oa->write_character(static_cast<CharType>(0x1B)); + write_number(static_cast<uint64_t>(j.m_value.number_unsigned)); + } + break; + } + + case value_t::number_float: { + oa->write_character(static_cast<CharType>(0xFB)); + write_number(j.m_value.number_float); + break; + } + + case value_t::string: { + const auto N = j.m_value.string->size(); + if(N <= 0x17) { + write_number(static_cast<uint8_t>(0x60+N)); + } else if(N <= (std::numeric_limits<uint8_t>::max) ()) { + oa->write_character(static_cast<CharType>(0x78)); + write_number(static_cast<uint8_t>(N)); + } else if(N <= (std::numeric_limits<uint16_t>::max) ()) { + oa->write_character(static_cast<CharType>(0x79)); + write_number(static_cast<uint16_t>(N)); + } else if(N <= (std::numeric_limits<uint32_t>::max) ()) { + oa->write_character(static_cast<CharType>(0x7A)); + write_number(static_cast<uint32_t>(N)); + } else if(N <= (std::numeric_limits<uint64_t>::max) ()) { + oa->write_character(static_cast<CharType>(0x7B)); + write_number(static_cast<uint64_t>(N)); + } + + oa->write_characters( + reinterpret_cast<const CharType *>(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: { + const auto N = j.m_value.array->size(); + if(N <= 0x17) { + write_number(static_cast<uint8_t>(0x80+N)); + } else if(N <= (std::numeric_limits<uint8_t>::max) ()) { + oa->write_character(static_cast<CharType>(0x98)); + write_number(static_cast<uint8_t>(N)); + } else if(N <= (std::numeric_limits<uint16_t>::max) ()) { + oa->write_character(static_cast<CharType>(0x99)); + write_number(static_cast<uint16_t>(N)); + } else if(N <= (std::numeric_limits<uint32_t>::max) ()) { + oa->write_character(static_cast<CharType>(0x9A)); + write_number(static_cast<uint32_t>(N)); + } else if(N <= (std::numeric_limits<uint64_t>::max) ()) { + oa->write_character(static_cast<CharType>(0x9B)); + write_number(static_cast<uint64_t>(N)); + } + + for(const auto &el : *j.m_value.array) { + write_cbor(el); + } + break; + } + + case value_t::object: { + const auto N = j.m_value.object->size(); + if(N <= 0x17) { + write_number(static_cast<uint8_t>(0xA0+N)); + } else if(N <= (std::numeric_limits<uint8_t>::max) ()) { + oa->write_character(static_cast<CharType>(0xB8)); + write_number(static_cast<uint8_t>(N)); + } else if(N <= (std::numeric_limits<uint16_t>::max) ()) { + oa->write_character(static_cast<CharType>(0xB9)); + write_number(static_cast<uint16_t>(N)); + } else if(N <= (std::numeric_limits<uint32_t>::max) ()) { + oa->write_character(static_cast<CharType>(0xBA)); + write_number(static_cast<uint32_t>(N)); + } else if(N <= (std::numeric_limits<uint64_t>::max) ()) { + oa->write_character(static_cast<CharType>(0xBB)); + write_number(static_cast<uint64_t>(N)); + } + + for(const auto &el : *j.m_value.object) { + write_cbor(el.first); + write_cbor(el.second); + } + break; + } + + default: + break; + } + } + + void write_msgpack(const BasicJsonType &j) { + switch(j.type()) { + case value_t::null: { + oa->write_character(static_cast<CharType>(0xC0)); + break; + } + + case value_t::boolean: { + oa->write_character(j.m_value.boolean + ? static_cast<CharType>(0xC3) + : static_cast<CharType>(0xC2)); + break; + } + + case value_t::number_integer: { + if(j.m_value.number_integer >= 0) { + if(j.m_value.number_unsigned < 128) { + write_number(static_cast<uint8_t>(j.m_value.number_integer)); + } else if(j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max) ()) { + oa->write_character(static_cast<CharType>(0xCC)); + write_number(static_cast<uint8_t>(j.m_value.number_integer)); + } else if(j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max) ()) { + oa->write_character(static_cast<CharType>(0xCD)); + write_number(static_cast<uint16_t>(j.m_value.number_integer)); + } else if(j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max) ()) { + oa->write_character(static_cast<CharType>(0xCE)); + write_number(static_cast<uint32_t>(j.m_value.number_integer)); + } else if(j.m_value.number_unsigned <= (std::numeric_limits<uint64_t>::max) ()) { + oa->write_character(static_cast<CharType>(0xCF)); + write_number(static_cast<uint64_t>(j.m_value.number_integer)); + } + } else { + if(j.m_value.number_integer >= -32) { + write_number(static_cast<int8_t>(j.m_value.number_integer)); + } else if(j.m_value.number_integer >= (std::numeric_limits<int8_t>::min) () and + j.m_value.number_integer <= (std::numeric_limits<int8_t>::max) ()) { + oa->write_character(static_cast<CharType>(0xD0)); + write_number(static_cast<int8_t>(j.m_value.number_integer)); + } else if(j.m_value.number_integer >= (std::numeric_limits<int16_t>::min) () and + j.m_value.number_integer <= (std::numeric_limits<int16_t>::max) ()) { + oa->write_character(static_cast<CharType>(0xD1)); + write_number(static_cast<int16_t>(j.m_value.number_integer)); + } else if(j.m_value.number_integer >= (std::numeric_limits<int32_t>::min) () and + j.m_value.number_integer <= (std::numeric_limits<int32_t>::max) ()) { + oa->write_character(static_cast<CharType>(0xD2)); + write_number(static_cast<int32_t>(j.m_value.number_integer)); + } else if(j.m_value.number_integer >= (std::numeric_limits<int64_t>::min) () and + j.m_value.number_integer <= (std::numeric_limits<int64_t>::max) ()) { + oa->write_character(static_cast<CharType>(0xD3)); + write_number(static_cast<int64_t>(j.m_value.number_integer)); + } + } + break; + } + + case value_t::number_unsigned: { + if(j.m_value.number_unsigned < 128) { + write_number(static_cast<uint8_t>(j.m_value.number_integer)); + } else if(j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max) ()) { + oa->write_character(static_cast<CharType>(0xCC)); + write_number(static_cast<uint8_t>(j.m_value.number_integer)); + } else if(j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max) ()) { + oa->write_character(static_cast<CharType>(0xCD)); + write_number(static_cast<uint16_t>(j.m_value.number_integer)); + } else if(j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max) ()) { + oa->write_character(static_cast<CharType>(0xCE)); + write_number(static_cast<uint32_t>(j.m_value.number_integer)); + } else if(j.m_value.number_unsigned <= (std::numeric_limits<uint64_t>::max) ()) { + oa->write_character(static_cast<CharType>(0xCF)); + write_number(static_cast<uint64_t>(j.m_value.number_integer)); + } + break; + } + + case value_t::number_float: { + oa->write_character(static_cast<CharType>(0xCB)); + write_number(j.m_value.number_float); + break; + } + + case value_t::string: { + const auto N = j.m_value.string->size(); + if(N <= 31) { + write_number(static_cast<uint8_t>(0xA0 | N)); + } else if(N <= (std::numeric_limits<uint8_t>::max) ()) { + oa->write_character(static_cast<CharType>(0xD9)); + write_number(static_cast<uint8_t>(N)); + } else if(N <= (std::numeric_limits<uint16_t>::max) ()) { + oa->write_character(static_cast<CharType>(0xDA)); + write_number(static_cast<uint16_t>(N)); + } else if(N <= (std::numeric_limits<uint32_t>::max) ()) { + oa->write_character(static_cast<CharType>(0xDB)); + write_number(static_cast<uint32_t>(N)); + } + + oa->write_characters( + reinterpret_cast<const CharType *>(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: { + const auto N = j.m_value.array->size(); + if(N <= 15) { + write_number(static_cast<uint8_t>(0x90 | N)); + } else if(N <= (std::numeric_limits<uint16_t>::max) ()) { + oa->write_character(static_cast<CharType>(0xDC)); + write_number(static_cast<uint16_t>(N)); + } else if(N <= (std::numeric_limits<uint32_t>::max) ()) { + oa->write_character(static_cast<CharType>(0xDD)); + write_number(static_cast<uint32_t>(N)); + } + + for(const auto &el : *j.m_value.array) { + write_msgpack(el); + } + break; + } + + case value_t::object: { + const auto N = j.m_value.object->size(); + if(N <= 15) { + write_number(static_cast<uint8_t>(0x80 | (N & 0xF))); + } else if(N <= (std::numeric_limits<uint16_t>::max) ()) { + oa->write_character(static_cast<CharType>(0xDE)); + write_number(static_cast<uint16_t>(N)); + } else if(N <= (std::numeric_limits<uint32_t>::max) ()) { + oa->write_character(static_cast<CharType>(0xDF)); + write_number(static_cast<uint32_t>(N)); + } + + for(const auto &el : *j.m_value.object) { + write_msgpack(el.first); + write_msgpack(el.second); + } + break; + } + + default: + break; + } + } + + void write_ubjson(const BasicJsonType &j, const bool use_count, + const bool use_type, const bool add_prefix = true) { + switch(j.type()) { + case value_t::null: { + if(add_prefix) { + oa->write_character(static_cast<CharType>('Z')); + } + break; + } + + case value_t::boolean: { + if(add_prefix) + oa->write_character(j.m_value.boolean + ? static_cast<CharType>('T') + : static_cast<CharType>('F')); + break; + } + + case value_t::number_integer: { + write_number_with_ubjson_prefix(j.m_value.number_integer, add_prefix); + break; + } + + case value_t::number_unsigned: { + write_number_with_ubjson_prefix(j.m_value.number_unsigned, add_prefix); + break; + } + + case value_t::number_float: { + write_number_with_ubjson_prefix(j.m_value.number_float, add_prefix); + break; + } + + case value_t::string: { + if(add_prefix) { + oa->write_character(static_cast<CharType>('S')); + } + write_number_with_ubjson_prefix(j.m_value.string->size(), true); + oa->write_characters( + reinterpret_cast<const CharType *>(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: { + if(add_prefix) { + oa->write_character(static_cast<CharType>('[')); + } + + bool prefix_required = true; + if(use_type and not j.m_value.array->empty()) { + assert(use_count); + const char first_prefix = ubjson_prefix(j.front()); + const bool same_prefix = std::all_of(j.begin()+1, j.end(), + [this, first_prefix](const BasicJsonType &v) { + return ubjson_prefix(v) == first_prefix; + }); + + if(same_prefix) { + prefix_required = false; + oa->write_character(static_cast<CharType>('$')); + oa->write_character(static_cast<CharType>(first_prefix)); + } + } + + if(use_count) { + oa->write_character(static_cast<CharType>('#')); + write_number_with_ubjson_prefix(j.m_value.array->size(), true); + } + + for(const auto &el : *j.m_value.array) { + write_ubjson(el, use_count, use_type, prefix_required); + } + + if(not use_count) { + oa->write_character(static_cast<CharType>(']')); + } + + break; + } + + case value_t::object: { + if(add_prefix) { + oa->write_character(static_cast<CharType>('{')); + } + + bool prefix_required = true; + if(use_type and not j.m_value.object->empty()) { + assert(use_count); + const char first_prefix = ubjson_prefix(j.front()); + const bool same_prefix = std::all_of(j.begin(), j.end(), + [this, first_prefix](const BasicJsonType &v) { + return ubjson_prefix(v) == first_prefix; + }); + + if(same_prefix) { + prefix_required = false; + oa->write_character(static_cast<CharType>('$')); + oa->write_character(static_cast<CharType>(first_prefix)); + } + } + + if(use_count) { + oa->write_character(static_cast<CharType>('#')); + write_number_with_ubjson_prefix(j.m_value.object->size(), true); + } + + for(const auto &el : *j.m_value.object) { + write_number_with_ubjson_prefix(el.first.size(), true); + oa->write_characters( + reinterpret_cast<const CharType *>(el.first.c_str()), + el.first.size()); + write_ubjson(el.second, use_count, use_type, prefix_required); + } + + if(not use_count) { + oa->write_character(static_cast<CharType>('}')); + } + + break; + } + + default: + break; + } + } + +private: + template<typename NumberType> + void write_number(const NumberType n) { + std::array<CharType, sizeof(NumberType)> vec; + std::memcpy(vec.data(), &n, sizeof(NumberType)); + + if(is_little_endian) { + std::reverse(vec.begin(), vec.end()); + } + + oa->write_characters(vec.data(), sizeof(NumberType)); + } + + template<typename NumberType, typename std::enable_if< + std::is_floating_point<NumberType>::value, int>::type = 0> + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix) { + if(add_prefix) { + oa->write_character(static_cast<CharType>('D')); + } + write_number(n); + } + + template<typename NumberType, typename std::enable_if< + std::is_unsigned<NumberType>::value, int>::type = 0> + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix) { + if(n <= static_cast<uint64_t>((std::numeric_limits<int8_t>::max) ())) { + if(add_prefix) { + oa->write_character(static_cast<CharType>('i')); + } + write_number(static_cast<uint8_t>(n)); + } else if(n <= (std::numeric_limits<uint8_t>::max) ()) { + if(add_prefix) { + oa->write_character(static_cast<CharType>('U')); + } + write_number(static_cast<uint8_t>(n)); + } else if(n <= static_cast<uint64_t>((std::numeric_limits<int16_t>::max) ())) { + if(add_prefix) { + oa->write_character(static_cast<CharType>('I')); + } + write_number(static_cast<int16_t>(n)); + } else if(n <= static_cast<uint64_t>((std::numeric_limits<int32_t>::max) ())) { + if(add_prefix) { + oa->write_character(static_cast<CharType>('l')); + } + write_number(static_cast<int32_t>(n)); + } else if(n <= static_cast<uint64_t>((std::numeric_limits<int64_t>::max) ())) { + if(add_prefix) { + oa->write_character(static_cast<CharType>('L')); + } + write_number(static_cast<int64_t>(n)); + } else { + JSON_THROW(out_of_range::create(407, "number overflow serializing "+std::to_string(n))); + } + } + + template<typename NumberType, typename std::enable_if< + std::is_signed<NumberType>::value and + not std::is_floating_point<NumberType>::value, int>::type = 0> + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix) { + if((std::numeric_limits<int8_t>::min) () <= n and n <= (std::numeric_limits<int8_t>::max) ()) { + if(add_prefix) { + oa->write_character(static_cast<CharType>('i')); + } + write_number(static_cast<int8_t>(n)); + } else if(static_cast<int64_t>((std::numeric_limits<uint8_t>::min) ()) <= n and + n <= static_cast<int64_t>((std::numeric_limits<uint8_t>::max) ())) { + if(add_prefix) { + oa->write_character(static_cast<CharType>('U')); + } + write_number(static_cast<uint8_t>(n)); + } else if((std::numeric_limits<int16_t>::min) () <= n and n <= (std::numeric_limits<int16_t>::max) ()) { + if(add_prefix) { + oa->write_character(static_cast<CharType>('I')); + } + write_number(static_cast<int16_t>(n)); + } else if((std::numeric_limits<int32_t>::min) () <= n and n <= (std::numeric_limits<int32_t>::max) ()) { + if(add_prefix) { + oa->write_character(static_cast<CharType>('l')); + } + write_number(static_cast<int32_t>(n)); + } else if((std::numeric_limits<int64_t>::min) () <= n and n <= (std::numeric_limits<int64_t>::max) ()) { + if(add_prefix) { + oa->write_character(static_cast<CharType>('L')); + } + write_number(static_cast<int64_t>(n)); + } else { + JSON_THROW(out_of_range::create(407, "number overflow serializing "+std::to_string(n))); + } + + } + + char ubjson_prefix(const BasicJsonType &j) const noexcept { + switch(j.type()) { + case value_t::null: + return 'Z'; + + case value_t::boolean: + return j.m_value.boolean ? 'T' : 'F'; + + case value_t::number_integer: { + if((std::numeric_limits<int8_t>::min) () <= j.m_value.number_integer and + j.m_value.number_integer <= (std::numeric_limits<int8_t>::max) ()) { + return 'i'; + } else if((std::numeric_limits<uint8_t>::min) () <= j.m_value.number_integer and + j.m_value.number_integer <= (std::numeric_limits<uint8_t>::max) ()) { + return 'U'; + } else if((std::numeric_limits<int16_t>::min) () <= j.m_value.number_integer and + j.m_value.number_integer <= (std::numeric_limits<int16_t>::max) ()) { + return 'I'; + } else if((std::numeric_limits<int32_t>::min) () <= j.m_value.number_integer and + j.m_value.number_integer <= (std::numeric_limits<int32_t>::max) ()) { + return 'l'; + } else { + return 'L'; + } + } + + case value_t::number_unsigned: { + if(j.m_value.number_unsigned <= (std::numeric_limits<int8_t>::max) ()) { + return 'i'; + } else if(j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max) ()) { + return 'U'; + } else if(j.m_value.number_unsigned <= (std::numeric_limits<int16_t>::max) ()) { + return 'I'; + } else if(j.m_value.number_unsigned <= (std::numeric_limits<int32_t>::max) ()) { + return 'l'; + } else { + return 'L'; + } + } + + case value_t::number_float: + return 'D'; + + case value_t::string: + return 'S'; + + case value_t::array: + return '['; + + case value_t::object: + return '{'; + + default: + return 'N'; + } + } + +private: + const bool is_little_endian = binary_reader<BasicJsonType>::little_endianess(); + + output_adapter_t<CharType> oa = nullptr; +}; +} +} + +#include <cstdio> + +namespace nlohmann { +namespace detail { +namespace dtoa_impl { +template<typename Target, typename Source> +Target reinterpret_bits(const Source source) { + static_assert(sizeof(Target) == sizeof(Source), "size mismatch"); + + Target target; + std::memcpy(&target, &source, sizeof(Source)); + return target; +} + +struct diyfp { + static constexpr int kPrecision = 64; + + uint64_t f; + int e; + + constexpr diyfp() noexcept : f(0), e(0) {} + + constexpr diyfp(uint64_t f_, int e_) noexcept : f(f_), e(e_) {} + + static diyfp sub(const diyfp &x, const diyfp &y) noexcept { + assert(x.e == y.e); + assert(x.f >= y.f); + + return diyfp(x.f-y.f, x.e); + } + + static diyfp mul(const diyfp &x, const diyfp &y) noexcept { + static_assert(kPrecision == 64, "internal error"); + + // + + // + + // + + // + + // + + const uint64_t u_lo = x.f & 0xFFFFFFFF; + const uint64_t u_hi = x.f >> 32; + const uint64_t v_lo = y.f & 0xFFFFFFFF; + const uint64_t v_hi = y.f >> 32; + + const uint64_t p0 = u_lo * v_lo; + const uint64_t p1 = u_lo * v_hi; + const uint64_t p2 = u_hi * v_lo; + const uint64_t p3 = u_hi * v_hi; + + const uint64_t p0_hi = p0 >> 32; + const uint64_t p1_lo = p1 & 0xFFFFFFFF; + const uint64_t p1_hi = p1 >> 32; + const uint64_t p2_lo = p2 & 0xFFFFFFFF; + const uint64_t p2_hi = p2 >> 32; + + uint64_t Q = p0_hi+p1_lo+p2_lo; + + // + + // + + Q += uint64_t{1} << (64-32-1); + + const uint64_t h = p3+p2_hi+p1_hi+(Q >> 32); + + return diyfp(h, x.e+y.e+64); + } + + static diyfp normalize(diyfp x) noexcept { + assert(x.f != 0); + + while((x.f >> 63) == 0) { + x.f <<= 1; + x.e--; + } + + return x; + } + + static diyfp normalize_to(const diyfp &x, const int target_exponent) noexcept { + const int delta = x.e-target_exponent; + + assert(delta >= 0); + assert(((x.f << delta) >> delta) == x.f); + + return diyfp(x.f << delta, target_exponent); + } +}; + +struct boundaries { + diyfp w; + diyfp minus; + diyfp plus; +}; + +template<typename FloatType> +boundaries compute_boundaries(FloatType value) { + assert(std::isfinite(value)); + assert(value > 0); + + // + + static_assert(std::numeric_limits<FloatType>::is_iec559, + "internal error: dtoa_short requires an IEEE-754 floating-point implementation"); + + constexpr int kPrecision = std::numeric_limits<FloatType>::digits; + constexpr int kBias = std::numeric_limits<FloatType>::max_exponent-1+(kPrecision-1); + constexpr int kMinExp = 1-kBias; + constexpr uint64_t kHiddenBit = uint64_t{1} << (kPrecision-1); + + using bits_type = typename std::conditional<kPrecision == 24, uint32_t, uint64_t>::type; + + const uint64_t bits = reinterpret_bits<bits_type>(value); + const uint64_t E = bits >> (kPrecision-1); + const uint64_t F = bits & (kHiddenBit-1); + + const bool is_denormal = (E == 0); + const diyfp v = is_denormal + ? diyfp(F, kMinExp) + : diyfp(F+kHiddenBit, static_cast<int>(E)-kBias); + + // + + // + + // + + // + + // + + // + + const bool lower_boundary_is_closer = (F == 0 and E > 1); + const diyfp m_plus = diyfp(2 * v.f+1, v.e-1); + const diyfp m_minus = lower_boundary_is_closer + ? diyfp(4 * v.f-1, v.e-2) + : diyfp(2 * v.f-1, v.e-1); + + const diyfp w_plus = diyfp::normalize(m_plus); + + const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e); + + return {diyfp::normalize(v), w_minus, w_plus}; +} + +// + +// + +// + +// + +// + +// + +// + +// + +// + +// + +// + +// + +// + +// + +// + +// + +// + +// + +// + +// + +constexpr int kAlpha = -60; +constexpr int kGamma = -32; + +struct cached_power { + uint64_t f; + int e; + int k; +}; + +inline cached_power get_cached_power_for_binary_exponent(int e) { + // + + // + + // + + // + + // + + // + + // + + // + + // + + // + + // + + // + + // + + constexpr int kCachedPowersSize = 79; + constexpr int kCachedPowersMinDecExp = -300; + constexpr int kCachedPowersDecStep = 8; + + static constexpr cached_power kCachedPowers[] = + { + {0xAB70FE17C79AC6CA, -1060, -300}, + {0xFF77B1FCBEBCDC4F, -1034, -292}, + {0xBE5691EF416BD60C, -1007, -284}, + {0x8DD01FAD907FFC3C, -980, -276}, + {0xD3515C2831559A83, -954, -268}, + {0x9D71AC8FADA6C9B5, -927, -260}, + {0xEA9C227723EE8BCB, -901, -252}, + {0xAECC49914078536D, -874, -244}, + {0x823C12795DB6CE57, -847, -236}, + {0xC21094364DFB5637, -821, -228}, + {0x9096EA6F3848984F, -794, -220}, + {0xD77485CB25823AC7, -768, -212}, + {0xA086CFCD97BF97F4, -741, -204}, + {0xEF340A98172AACE5, -715, -196}, + {0xB23867FB2A35B28E, -688, -188}, + {0x84C8D4DFD2C63F3B, -661, -180}, + {0xC5DD44271AD3CDBA, -635, -172}, + {0x936B9FCEBB25C996, -608, -164}, + {0xDBAC6C247D62A584, -582, -156}, + {0xA3AB66580D5FDAF6, -555, -148}, + {0xF3E2F893DEC3F126, -529, -140}, + {0xB5B5ADA8AAFF80B8, -502, -132}, + {0x87625F056C7C4A8B, -475, -124}, + {0xC9BCFF6034C13053, -449, -116}, + {0x964E858C91BA2655, -422, -108}, + {0xDFF9772470297EBD, -396, -100}, + {0xA6DFBD9FB8E5B88F, -369, -92}, + {0xF8A95FCF88747D94, -343, -84}, + {0xB94470938FA89BCF, -316, -76}, + {0x8A08F0F8BF0F156B, -289, -68}, + {0xCDB02555653131B6, -263, -60}, + {0x993FE2C6D07B7FAC, -236, -52}, + {0xE45C10C42A2B3B06, -210, -44}, + {0xAA242499697392D3, -183, -36}, + {0xFD87B5F28300CA0E, -157, -28}, + {0xBCE5086492111AEB, -130, -20}, + {0x8CBCCC096F5088CC, -103, -12}, + {0xD1B71758E219652C, -77, -4}, + {0x9C40000000000000, -50, 4}, + {0xE8D4A51000000000, -24, 12}, + {0xAD78EBC5AC620000, 3, 20}, + {0x813F3978F8940984, 30, 28}, + {0xC097CE7BC90715B3, 56, 36}, + {0x8F7E32CE7BEA5C70, 83, 44}, + {0xD5D238A4ABE98068, 109, 52}, + {0x9F4F2726179A2245, 136, 60}, + {0xED63A231D4C4FB27, 162, 68}, + {0xB0DE65388CC8ADA8, 189, 76}, + {0x83C7088E1AAB65DB, 216, 84}, + {0xC45D1DF942711D9A, 242, 92}, + {0x924D692CA61BE758, 269, 100}, + {0xDA01EE641A708DEA, 295, 108}, + {0xA26DA3999AEF774A, 322, 116}, + {0xF209787BB47D6B85, 348, 124}, + {0xB454E4A179DD1877, 375, 132}, + {0x865B86925B9BC5C2, 402, 140}, + {0xC83553C5C8965D3D, 428, 148}, + {0x952AB45CFA97A0B3, 455, 156}, + {0xDE469FBD99A05FE3, 481, 164}, + {0xA59BC234DB398C25, 508, 172}, + {0xF6C69A72A3989F5C, 534, 180}, + {0xB7DCBF5354E9BECE, 561, 188}, + {0x88FCF317F22241E2, 588, 196}, + {0xCC20CE9BD35C78A5, 614, 204}, + {0x98165AF37B2153DF, 641, 212}, + {0xE2A0B5DC971F303A, 667, 220}, + {0xA8D9D1535CE3B396, 694, 228}, + {0xFB9B7CD9A4A7443C, 720, 236}, + {0xBB764C4CA7A44410, 747, 244}, + {0x8BAB8EEFB6409C1A, 774, 252}, + {0xD01FEF10A657842C, 800, 260}, + {0x9B10A4E5E9913129, 827, 268}, + {0xE7109BFBA19C0C9D, 853, 276}, + {0xAC2820D9623BF429, 880, 284}, + {0x80444B5E7AA7CF85, 907, 292}, + {0xBF21E44003ACDD2D, 933, 300}, + {0x8E679C2F5E44FF8F, 960, 308}, + {0xD433179D9C8CB841, 986, 316}, + {0x9E19DB92B4E31BA9, 1013, 324}, + }; + + assert(e >= -1500); + assert(e <= 1500); + const int f = kAlpha-e-1; + const int k = (f * 78913) / (1 << 18)+(f > 0); + + const int index = (-kCachedPowersMinDecExp+k+(kCachedPowersDecStep-1)) / kCachedPowersDecStep; + assert(index >= 0); + assert(index < kCachedPowersSize); + static_cast<void>(kCachedPowersSize); + + const cached_power cached = kCachedPowers[index]; + assert(kAlpha <= cached.e+e+64); + assert(kGamma >= cached.e+e+64); + + return cached; +} + +inline int find_largest_pow10(const uint32_t n, uint32_t &pow10) { + if(n >= 1000000000) { + pow10 = 1000000000; + return 10; + } else if(n >= 100000000) { + pow10 = 100000000; + return 9; + } else if(n >= 10000000) { + pow10 = 10000000; + return 8; + } else if(n >= 1000000) { + pow10 = 1000000; + return 7; + } else if(n >= 100000) { + pow10 = 100000; + return 6; + } else if(n >= 10000) { + pow10 = 10000; + return 5; + } else if(n >= 1000) { + pow10 = 1000; + return 4; + } else if(n >= 100) { + pow10 = 100; + return 3; + } else if(n >= 10) { + pow10 = 10; + return 2; + } else { + pow10 = 1; + return 1; + } +} + +inline void grisu2_round(char *buf, int len, uint64_t dist, uint64_t delta, + uint64_t rest, uint64_t ten_k) { + assert(len >= 1); + assert(dist <= delta); + assert(rest <= delta); + assert(ten_k > 0); + + // + + // + + while(rest < dist + and delta-rest >= ten_k + and (rest+ten_k < dist or dist-rest > rest+ten_k-dist)) { + assert(buf[len-1] != '0'); + buf[len-1]--; + rest += ten_k; + } +} + +inline void grisu2_digit_gen(char *buffer, int &length, int &decimal_exponent, + diyfp M_minus, diyfp w, diyfp M_plus) { + static_assert(kAlpha >= -60, "internal error"); + static_assert(kGamma <= -32, "internal error"); + + // + + // + + assert(M_plus.e >= kAlpha); + assert(M_plus.e <= kGamma); + + uint64_t delta = diyfp::sub(M_plus, M_minus).f; + uint64_t dist = diyfp::sub(M_plus, w).f; + + // + + const diyfp one(uint64_t{1} << -M_plus.e, M_plus.e); + + uint32_t p1 = static_cast<uint32_t>(M_plus.f >> -one.e); + uint64_t p2 = M_plus.f & (one.f-1); + + // + + assert(p1 > 0); + + uint32_t pow10; + const int k = find_largest_pow10(p1, pow10); + + // + + // + + // + + // + + // + + // + + int n = k; + while(n > 0) { + // + const uint32_t d = p1 / pow10; + const uint32_t r = p1 % pow10; + // + + // + assert(d <= 9); + buffer[length++] = static_cast<char>('0'+d); + // + + // + p1 = r; + n--; + // + + // + + // + + // + + const uint64_t rest = (uint64_t{p1} << -one.e)+p2; + if(rest <= delta) { + decimal_exponent += n; + + // + + // + + // + const uint64_t ten_n = uint64_t{pow10} << -one.e; + grisu2_round(buffer, length, dist, delta, rest, ten_n); + + return; + } + + pow10 /= 10; + // + + } + + // + + // + + // + + // + + // + + // + + // + + // + + // + + // + + // + + // + + // + + // + + assert(p2 > delta); + + int m = 0; + for(;;) { + // + assert(p2 <= UINT64_MAX / 10); + p2 *= 10; + const uint64_t d = p2 >> -one.e; + const uint64_t r = p2 & (one.f-1); + // + + // + assert(d <= 9); + buffer[length++] = static_cast<char>('0'+d); + // + + // + p2 = r; + m++; + // + + // + + delta *= 10; + dist *= 10; + if(p2 <= delta) { + break; + } + } + + decimal_exponent -= m; + + // + + // + const uint64_t ten_m = one.f; + grisu2_round(buffer, length, dist, delta, p2, ten_m); + + // + + // + + // + +} + +inline void grisu2(char *buf, int &len, int &decimal_exponent, + diyfp m_minus, diyfp v, diyfp m_plus) { + assert(m_plus.e == m_minus.e); + assert(m_plus.e == v.e); + + // + + // + + const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e); + + const diyfp c_minus_k(cached.f, cached.e); + + const diyfp w = diyfp::mul(v, c_minus_k); + const diyfp w_minus = diyfp::mul(m_minus, c_minus_k); + const diyfp w_plus = diyfp::mul(m_plus, c_minus_k); + + // + + // + + // + + // + + // + + // + + const diyfp M_minus(w_minus.f+1, w_minus.e); + const diyfp M_plus(w_plus.f-1, w_plus.e); + + decimal_exponent = -cached.k; + + grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus); +} + +template<typename FloatType> +void grisu2(char *buf, int &len, int &decimal_exponent, FloatType value) { + static_assert(diyfp::kPrecision >= std::numeric_limits<FloatType>::digits+3, + "internal error: not enough precision"); + + assert(std::isfinite(value)); + assert(value > 0); + + // + + // + +#if 0 + const boundaries w = compute_boundaries(static_cast<double>(value)); +#else + const boundaries w = compute_boundaries(value); +#endif + + grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus); +} + +inline char *append_exponent(char *buf, int e) { + assert(e > -1000); + assert(e < 1000); + + if(e < 0) { + e = -e; + *buf++ = '-'; + } else { + *buf++ = '+'; + } + + uint32_t k = static_cast<uint32_t>(e); + if(k < 10) { + *buf++ = '0'; + *buf++ = static_cast<char>('0'+k); + } else if(k < 100) { + *buf++ = static_cast<char>('0'+k / 10); + k %= 10; + *buf++ = static_cast<char>('0'+k); + } else { + *buf++ = static_cast<char>('0'+k / 100); + k %= 100; + *buf++ = static_cast<char>('0'+k / 10); + k %= 10; + *buf++ = static_cast<char>('0'+k); + } + + return buf; +} + +inline char *format_buffer(char *buf, int len, int decimal_exponent, + int min_exp, int max_exp) { + assert(min_exp < 0); + assert(max_exp > 0); + + const int k = len; + const int n = len+decimal_exponent; + + if(k <= n and n <= max_exp) { + std::memset(buf+k, '0', static_cast<size_t>(n-k)); + + buf[n+0] = '.'; + buf[n+1] = '0'; + return buf+(n+2); + } + + if(0 < n and n <= max_exp) { + assert(k > n); + + std::memmove(buf+(n+1), buf+n, static_cast<size_t>(k-n)); + buf[n] = '.'; + return buf+(k+1); + } + + if(min_exp < n and n <= 0) { + std::memmove(buf+(2+ -n), buf, static_cast<size_t>(k)); + buf[0] = '0'; + buf[1] = '.'; + std::memset(buf+2, '0', static_cast<size_t>(-n)); + return buf+(2+(-n)+k); + } + + if(k == 1) { + buf += 1; + } else { + std::memmove(buf+2, buf+1, static_cast<size_t>(k-1)); + buf[1] = '.'; + buf += 1+k; + } + + *buf++ = 'e'; + return append_exponent(buf, n-1); +} + +} + +template<typename FloatType> +char *to_chars(char *first, char *last, FloatType value) { + static_cast<void>(last); + assert(std::isfinite(value)); + + if(std::signbit(value)) { + value = -value; + *first++ = '-'; + } + + if(value == 0) { + *first++ = '0'; + + *first++ = '.'; + *first++ = '0'; + return first; + } + + assert(last-first >= std::numeric_limits<FloatType>::max_digits10); + + int len = 0; + int decimal_exponent = 0; + dtoa_impl::grisu2(first, len, decimal_exponent, value); + + assert(len <= std::numeric_limits<FloatType>::max_digits10); + + constexpr int kMinExp = -4; + + constexpr int kMaxExp = std::numeric_limits<FloatType>::digits10; + + assert(last-first >= kMaxExp+2); + assert(last-first >= 2+(-kMinExp-1)+std::numeric_limits<FloatType>::max_digits10); + assert(last-first >= std::numeric_limits<FloatType>::max_digits10+6); + + return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp); +} + +} +} + +namespace nlohmann { +namespace detail { +template<typename BasicJsonType> +class serializer { + using string_t = typename BasicJsonType::string_t; + using number_float_t = typename BasicJsonType::number_float_t; + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + static constexpr uint8_t UTF8_ACCEPT = 0; + static constexpr uint8_t UTF8_REJECT = 1; + +public: + serializer(output_adapter_t<char> s, const char ichar) + : o(std::move(s)), loc(std::localeconv()), + thousands_sep(loc->thousands_sep == nullptr ? '\0' : *(loc->thousands_sep)), + decimal_point(loc->decimal_point == nullptr ? '\0' : *(loc->decimal_point)), + indent_char(ichar), indent_string(512, indent_char) {} + + serializer(const serializer &) = delete; + + serializer &operator=(const serializer &) = delete; + + void dump(const BasicJsonType &val, const bool pretty_print, + const bool ensure_ascii, + const unsigned int indent_step, + const unsigned int current_indent = 0) { + switch(val.m_type) { + case value_t::object: { + if(val.m_value.object->empty()) { + o->write_characters("{}", 2); + return; + } + + if(pretty_print) { + o->write_characters("{\n", 2); + + const auto new_indent = current_indent+indent_step; + if(JSON_UNLIKELY(indent_string.size() < new_indent)) { + indent_string.resize(indent_string.size() * 2, ' '); + } + + auto i = val.m_value.object->cbegin(); + for(std::size_t cnt = 0; cnt < val.m_value.object->size()-1; ++cnt, ++i) { + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character('}'); + } else { + o->write_character('{'); + + auto i = val.m_value.object->cbegin(); + for(std::size_t cnt = 0; cnt < val.m_value.object->size()-1; ++cnt, ++i) { + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + + o->write_character('}'); + } + + return; + } + + case value_t::array: { + if(val.m_value.array->empty()) { + o->write_characters("[]", 2); + return; + } + + if(pretty_print) { + o->write_characters("[\n", 2); + + const auto new_indent = current_indent+indent_step; + if(JSON_UNLIKELY(indent_string.size() < new_indent)) { + indent_string.resize(indent_string.size() * 2, ' '); + } + + for(auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend()-1; ++i) { + o->write_characters(indent_string.c_str(), new_indent); + dump(*i, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + assert(not val.m_value.array->empty()); + o->write_characters(indent_string.c_str(), new_indent); + dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character(']'); + } else { + o->write_character('['); + + for(auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend()-1; ++i) { + dump(*i, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + assert(not val.m_value.array->empty()); + dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent); + + o->write_character(']'); + } + + return; + } + + case value_t::string: { + o->write_character('\"'); + dump_escaped(*val.m_value.string, ensure_ascii); + o->write_character('\"'); + return; + } + + case value_t::boolean: { + if(val.m_value.boolean) { + o->write_characters("true", 4); + } else { + o->write_characters("false", 5); + } + return; + } + + case value_t::number_integer: { + dump_integer(val.m_value.number_integer); + return; + } + + case value_t::number_unsigned: { + dump_integer(val.m_value.number_unsigned); + return; + } + + case value_t::number_float: { + dump_float(val.m_value.number_float); + return; + } + + case value_t::discarded: { + o->write_characters("<discarded>", 11); + return; + } + + case value_t::null: { + o->write_characters("null", 4); + return; + } + } + } + +private: + void dump_escaped(const string_t &s, const bool ensure_ascii) { + uint32_t codepoint; + uint8_t state = UTF8_ACCEPT; + std::size_t bytes = 0; + + for(std::size_t i = 0; i < s.size(); ++i) { + const auto byte = static_cast<uint8_t>(s[i]); + + switch(decode(state, codepoint, byte)) { + case UTF8_ACCEPT: { + switch(codepoint) { + case 0x08: { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'b'; + break; + } + + case 0x09: { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 't'; + break; + } + + case 0x0A: { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'n'; + break; + } + + case 0x0C: { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'f'; + break; + } + + case 0x0D: { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'r'; + break; + } + + case 0x22: { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\"'; + break; + } + + case 0x5C: { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\\'; + break; + } + + default: { + if((codepoint <= 0x1F) or (ensure_ascii and (codepoint >= 0x7F))) { + if(codepoint <= 0xFFFF) { + std::snprintf(string_buffer.data()+bytes, 7, "\\u%04x", + static_cast<uint16_t>(codepoint)); + bytes += 6; + } else { + std::snprintf(string_buffer.data()+bytes, 13, "\\u%04x\\u%04x", + static_cast<uint16_t>(0xD7C0+(codepoint >> 10)), + static_cast<uint16_t>(0xDC00+(codepoint & 0x3FF))); + bytes += 12; + } + } else { + string_buffer[bytes++] = s[i]; + } + break; + } + } + + if(string_buffer.size()-bytes < 13) { + o->write_characters(string_buffer.data(), bytes); + bytes = 0; + } + break; + } + + case UTF8_REJECT: { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << static_cast<int>(byte); + JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index "+std::to_string(i)+": 0x"+ss.str())); + } + + default: { + if(not ensure_ascii) { + string_buffer[bytes++] = s[i]; + } + break; + } + } + } + + if(JSON_LIKELY(state == UTF8_ACCEPT)) { + if(bytes > 0) { + o->write_characters(string_buffer.data(), bytes); + } + } else { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex + << static_cast<int>(static_cast<uint8_t>(s.back())); + JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x"+ss.str())); + } + } + + template<typename NumberType, detail::enable_if_t< + std::is_same<NumberType, number_unsigned_t>::value or + std::is_same<NumberType, number_integer_t>::value, + int> = 0> + void dump_integer(NumberType x) { + if(x == 0) { + o->write_character('0'); + return; + } + + const bool is_negative = (x <= 0) and (x != 0); + std::size_t i = 0; + + while(x != 0) { + assert(i < number_buffer.size()-1); + + const auto digit = std::labs(static_cast<long>(x % 10)); + number_buffer[i++] = static_cast<char>('0'+digit); + x /= 10; + } + + if(is_negative) { + assert(i < number_buffer.size()-2); + number_buffer[i++] = '-'; + } + + std::reverse(number_buffer.begin(), number_buffer.begin()+i); + o->write_characters(number_buffer.data(), i); + } + + void dump_float(number_float_t x) { + if(not std::isfinite(x)) { + o->write_characters("null", 4); + return; + } + + // + + static constexpr bool is_ieee_single_or_double + = (std::numeric_limits<number_float_t>::is_iec559 and std::numeric_limits<number_float_t>::digits == 24 and + std::numeric_limits<number_float_t>::max_exponent == 128) or + (std::numeric_limits<number_float_t>::is_iec559 and std::numeric_limits<number_float_t>::digits == 53 and + std::numeric_limits<number_float_t>::max_exponent == 1024); + + dump_float(x, std::integral_constant<bool, is_ieee_single_or_double>()); + } + + void dump_float(number_float_t x, std::true_type) { + char *begin = number_buffer.data(); + char *end = ::nlohmann::detail::to_chars(begin, begin+number_buffer.size(), x); + + o->write_characters(begin, static_cast<size_t>(end-begin)); + } + + void dump_float(number_float_t x, std::false_type) { + static constexpr auto d = std::numeric_limits<number_float_t>::max_digits10; + + std::ptrdiff_t len = snprintf(number_buffer.data(), number_buffer.size(), "%.*g", d, x); + + assert(len > 0); + + assert(static_cast<std::size_t>(len) < number_buffer.size()); + + if(thousands_sep != '\0') { + const auto end = std::remove(number_buffer.begin(), + number_buffer.begin()+len, thousands_sep); + std::fill(end, number_buffer.end(), '\0'); + assert((end-number_buffer.begin()) <= len); + len = (end-number_buffer.begin()); + } + + if(decimal_point != '\0' and decimal_point != '.') { + const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point); + if(dec_pos != number_buffer.end()) { + *dec_pos = '.'; + } + } + + o->write_characters(number_buffer.data(), static_cast<std::size_t>(len)); + + const bool value_is_int_like = + std::none_of(number_buffer.begin(), number_buffer.begin()+len+1, + [](char c) { + return (c == '.' or c == 'e'); + }); + + if(value_is_int_like) { + o->write_characters(".0", 2); + } + } + + static uint8_t decode(uint8_t &state, uint32_t &codep, const uint8_t byte) noexcept { + static const std::array<uint8_t, 400> utf8d = + { + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, + 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, + 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, + 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, + 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + } + }; + + const uint8_t type = utf8d[byte]; + + codep = (state != UTF8_ACCEPT) + ? (byte & 0x3fu) | (codep << 6) + : static_cast<uint32_t>(0xff >> type) & (byte); + + state = utf8d[256u+state * 16u+type]; + return state; + } + +private: + output_adapter_t<char> o = nullptr; + + std::array<char, 64> number_buffer{{}}; + + const std::lconv *loc = nullptr; + + const char thousands_sep = '\0'; + + const char decimal_point = '\0'; + + std::array<char, 512> string_buffer{{}}; + + const char indent_char; + + string_t indent_string; +}; +} +} + +namespace nlohmann { +namespace detail { +template<typename BasicJsonType> +class json_ref { +public: + using value_type = BasicJsonType; + + json_ref(value_type &&value) + : owned_value(std::move(value)), value_ref(&owned_value), is_rvalue(true) {} + + json_ref(const value_type &value) + : value_ref(const_cast<value_type *>(&value)), is_rvalue(false) {} + + json_ref(std::initializer_list<json_ref> init) + : owned_value(init), value_ref(&owned_value), is_rvalue(true) {} + + template<class... Args> + json_ref(Args &&... args) + : owned_value(std::forward<Args>(args)...), value_ref(&owned_value), is_rvalue(true) {} + + json_ref(json_ref &&) = default; + + json_ref(const json_ref &) = delete; + + json_ref &operator=(const json_ref &) = delete; + + value_type moved_or_copied() const { + if(is_rvalue) { + return std::move(*value_ref); + } + return *value_ref; + } + + value_type const &operator*() const { + return *static_cast<value_type const *>(value_ref); + } + + value_type const *operator->() const { + return static_cast<value_type const *>(value_ref); + } + +private: + mutable value_type owned_value = nullptr; + value_type *value_ref = nullptr; + const bool is_rvalue; +}; +} +} + +namespace nlohmann { +template<typename BasicJsonType> +class json_pointer { + NLOHMANN_BASIC_JSON_TPL_DECLARATION + friend + class basic_json; + +public: + explicit json_pointer(const std::string &s = "") + : reference_tokens(split(s)) {} + + std::string to_string() const noexcept { + return std::accumulate(reference_tokens.begin(), reference_tokens.end(), + std::string{}, + [](const std::string &a, const std::string &b) { + return a+"/"+escape(b); + }); + } + + operator std::string() const { + return to_string(); + } + + static int array_index(const std::string &s) { + std::size_t processed_chars = 0; + const int res = std::stoi(s, &processed_chars); + + if(JSON_UNLIKELY(processed_chars != s.size())) { + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '"+s+"'")); + } + + return res; + } + +private: + std::string pop_back() { + if(JSON_UNLIKELY(is_root())) { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); + } + + auto last = reference_tokens.back(); + reference_tokens.pop_back(); + return last; + } + + bool is_root() const { + return reference_tokens.empty(); + } + + json_pointer top() const { + if(JSON_UNLIKELY(is_root())) { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); + } + + json_pointer result = *this; + result.reference_tokens = {reference_tokens[0]}; + return result; + } + + BasicJsonType &get_and_create(BasicJsonType &j) const { + using size_type = typename BasicJsonType::size_type; + auto result = &j; + + for(const auto &reference_token : reference_tokens) { + switch(result->m_type) { + case detail::value_t::null: { + if(reference_token == "0") { + result = &result->operator[](0); + } else { + result = &result->operator[](reference_token); + } + break; + } + + case detail::value_t::object: { + result = &result->operator[](reference_token); + break; + } + + case detail::value_t::array: { + JSON_TRY { + result = &result->operator[](static_cast<size_type>(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument &) { + JSON_THROW(detail::parse_error::create(109, 0, "array index '"+reference_token+"' is not a number")); + } + break; + } + + default: + JSON_THROW(detail::type_error::create(313, "invalid value to unflatten")); + } + } + + return *result; + } + + BasicJsonType &get_unchecked(BasicJsonType *ptr) const { + using size_type = typename BasicJsonType::size_type; + for(const auto &reference_token : reference_tokens) { + if(ptr->m_type == detail::value_t::null) { + const bool nums = + std::all_of(reference_token.begin(), reference_token.end(), + [](const char x) { + return (x >= '0' and x <= '9'); + }); + + *ptr = (nums or reference_token == "-") + ? detail::value_t::array + : detail::value_t::object; + } + + switch(ptr->m_type) { + case detail::value_t::object: { + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: { + if(JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '"+reference_token+ + "' must not begin with '0'")); + } + + if(reference_token == "-") { + ptr = &ptr->operator[](ptr->m_value.array->size()); + } else { + JSON_TRY { + ptr = &ptr->operator[]( + static_cast<size_type>(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument &) { + JSON_THROW(detail::parse_error::create(109, 0, "array index '"+reference_token+"' is not a number")); + } + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '"+reference_token+"'")); + } + } + + return *ptr; + } + + BasicJsonType &get_checked(BasicJsonType *ptr) const { + using size_type = typename BasicJsonType::size_type; + for(const auto &reference_token : reference_tokens) { + switch(ptr->m_type) { + case detail::value_t::object: { + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: { + if(JSON_UNLIKELY(reference_token == "-")) { + JSON_THROW(detail::out_of_range::create(402, + "array index '-' ("+std::to_string(ptr->m_value.array->size())+ + ") is out of range")); + } + + if(JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '"+reference_token+ + "' must not begin with '0'")); + } + + JSON_TRY { + ptr = &ptr->at(static_cast<size_type>(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument &) { + JSON_THROW(detail::parse_error::create(109, 0, "array index '"+reference_token+"' is not a number")); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '"+reference_token+"'")); + } + } + + return *ptr; + } + + const BasicJsonType &get_unchecked(const BasicJsonType *ptr) const { + using size_type = typename BasicJsonType::size_type; + for(const auto &reference_token : reference_tokens) { + switch(ptr->m_type) { + case detail::value_t::object: { + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: { + if(JSON_UNLIKELY(reference_token == "-")) { + JSON_THROW(detail::out_of_range::create(402, + "array index '-' ("+std::to_string(ptr->m_value.array->size())+ + ") is out of range")); + } + + if(JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '"+reference_token+ + "' must not begin with '0'")); + } + + JSON_TRY { + ptr = &ptr->operator[]( + static_cast<size_type>(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument &) { + JSON_THROW(detail::parse_error::create(109, 0, "array index '"+reference_token+"' is not a number")); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '"+reference_token+"'")); + } + } + + return *ptr; + } + + const BasicJsonType &get_checked(const BasicJsonType *ptr) const { + using size_type = typename BasicJsonType::size_type; + for(const auto &reference_token : reference_tokens) { + switch(ptr->m_type) { + case detail::value_t::object: { + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: { + if(JSON_UNLIKELY(reference_token == "-")) { + JSON_THROW(detail::out_of_range::create(402, + "array index '-' ("+std::to_string(ptr->m_value.array->size())+ + ") is out of range")); + } + + if(JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '"+reference_token+ + "' must not begin with '0'")); + } + + JSON_TRY { + ptr = &ptr->at(static_cast<size_type>(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument &) { + JSON_THROW(detail::parse_error::create(109, 0, "array index '"+reference_token+"' is not a number")); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '"+reference_token+"'")); + } + } + + return *ptr; + } + + static std::vector<std::string> split(const std::string &reference_string) { + std::vector<std::string> result; + + if(reference_string.empty()) { + return result; + } + + if(JSON_UNLIKELY(reference_string[0] != '/')) { + JSON_THROW(detail::parse_error::create(107, 1, + "JSON pointer must be empty or begin with '/' - was: '"+ + reference_string+"'")); + } + + for( + + std::size_t slash = reference_string.find_first_of('/', 1), + + start = 1; + + start != 0; + + start = slash+1, + + slash = reference_string.find_first_of('/', start)) { + auto reference_token = reference_string.substr(start, slash-start); + + for(std::size_t pos = reference_token.find_first_of('~'); + pos != std::string::npos; + pos = reference_token.find_first_of('~', pos+1)) { + assert(reference_token[pos] == '~'); + + if(JSON_UNLIKELY(pos == reference_token.size()-1 or + (reference_token[pos+1] != '0' and + reference_token[pos+1] != '1'))) { + JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'")); + } + } + + unescape(reference_token); + result.push_back(reference_token); + } + + return result; + } + + static void replace_substring(std::string &s, const std::string &f, + const std::string &t) { + assert(not f.empty()); + for(auto pos = s.find(f); + pos != std::string::npos; + s.replace(pos, f.size(), t), + pos = s.find(f, pos+t.size())) {} + } + + static std::string escape(std::string s) { + replace_substring(s, "~", "~0"); + replace_substring(s, "/", "~1"); + return s; + } + + static void unescape(std::string &s) { + replace_substring(s, "~1", "/"); + replace_substring(s, "~0", "~"); + } + + static void flatten(const std::string &reference_string, + const BasicJsonType &value, + BasicJsonType &result) { + switch(value.m_type) { + case detail::value_t::array: { + if(value.m_value.array->empty()) { + result[reference_string] = nullptr; + } else { + for(std::size_t i = 0; i < value.m_value.array->size(); ++i) { + flatten(reference_string+"/"+std::to_string(i), + value.m_value.array->operator[](i), result); + } + } + break; + } + + case detail::value_t::object: { + if(value.m_value.object->empty()) { + result[reference_string] = nullptr; + } else { + for(const auto &element : *value.m_value.object) { + flatten(reference_string+"/"+escape(element.first), element.second, result); + } + } + break; + } + + default: { + result[reference_string] = value; + break; + } + } + } + + static BasicJsonType + unflatten(const BasicJsonType &value) { + if(JSON_UNLIKELY(not value.is_object())) { + JSON_THROW(detail::type_error::create(314, "only objects can be unflattened")); + } + + BasicJsonType result; + + for(const auto &element : *value.m_value.object) { + if(JSON_UNLIKELY(not element.second.is_primitive())) { + JSON_THROW(detail::type_error::create(315, "values in object must be primitive")); + } + + json_pointer(element.first).get_and_create(result) = element.second; + } + + return result; + } + + friend bool operator==(json_pointer const &lhs, + json_pointer const &rhs) noexcept { + return (lhs.reference_tokens == rhs.reference_tokens); + } + + friend bool operator!=(json_pointer const &lhs, + json_pointer const &rhs) noexcept { + return not(lhs == rhs); + } + + std::vector<std::string> reference_tokens; +}; +} + +namespace nlohmann { +template<typename, typename> +struct adl_serializer { + template<typename BasicJsonType, typename ValueType> + static void from_json(BasicJsonType &&j, ValueType &val) noexcept( + noexcept(::nlohmann::from_json(std::forward<BasicJsonType>(j), val))) { + ::nlohmann::from_json(std::forward<BasicJsonType>(j), val); + } + + template<typename BasicJsonType, typename ValueType> + static void to_json(BasicJsonType &j, ValueType &&val) noexcept( + noexcept(::nlohmann::to_json(j, std::forward<ValueType>(val)))) { + ::nlohmann::to_json(j, std::forward<ValueType>(val)); + } +}; +} + +namespace nlohmann { +NLOHMANN_BASIC_JSON_TPL_DECLARATION +class basic_json { +private: + template<detail::value_t> friend + struct detail::external_constructor; + friend ::nlohmann::json_pointer<basic_json>; + friend ::nlohmann::detail::parser<basic_json>; + friend ::nlohmann::detail::serializer<basic_json>; + + template<typename BasicJsonType> + friend + class ::nlohmann::detail::iter_impl; + + template<typename BasicJsonType, typename CharType> + friend + class ::nlohmann::detail::binary_writer; + + template<typename BasicJsonType> + friend + class ::nlohmann::detail::binary_reader; + + using basic_json_t = NLOHMANN_BASIC_JSON_TPL; + + using lexer = ::nlohmann::detail::lexer<basic_json>; + using parser = ::nlohmann::detail::parser<basic_json>; + + using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t; + template<typename BasicJsonType> + using internal_iterator = ::nlohmann::detail::internal_iterator<BasicJsonType>; + template<typename BasicJsonType> + using iter_impl = ::nlohmann::detail::iter_impl<BasicJsonType>; + template<typename Iterator> + using iteration_proxy = ::nlohmann::detail::iteration_proxy<Iterator>; + template<typename Base> using json_reverse_iterator = ::nlohmann::detail::json_reverse_iterator<Base>; + + template<typename CharType> + using output_adapter_t = ::nlohmann::detail::output_adapter_t<CharType>; + + using binary_reader = ::nlohmann::detail::binary_reader<basic_json>; + template<typename CharType> using binary_writer = ::nlohmann::detail::binary_writer<basic_json, CharType>; + + using serializer = ::nlohmann::detail::serializer<basic_json>; + +public: + using value_t = detail::value_t; + + using json_pointer = ::nlohmann::json_pointer<basic_json>; + template<typename T, typename SFINAE> + using json_serializer = JSONSerializer<T, SFINAE>; + + using initializer_list_t = std::initializer_list<detail::json_ref<basic_json>>; + + using exception = detail::exception; + + using parse_error = detail::parse_error; + + using invalid_iterator = detail::invalid_iterator; + + using type_error = detail::type_error; + + using out_of_range = detail::out_of_range; + + using other_error = detail::other_error; + + using value_type = basic_json; + + using reference = value_type &; + + using const_reference = const value_type &; + + using difference_type = std::ptrdiff_t; + + using size_type = std::size_t; + + using allocator_type = AllocatorType<basic_json>; + + using pointer = typename std::allocator_traits<allocator_type>::pointer; + + using const_pointer = typename std::allocator_traits<allocator_type>::const_pointer; + + using iterator = iter_impl<basic_json>; + + using const_iterator = iter_impl<const basic_json>; + + using reverse_iterator = json_reverse_iterator<typename basic_json::iterator>; + + using const_reverse_iterator = json_reverse_iterator<typename basic_json::const_iterator>; + + static allocator_type get_allocator() { + return allocator_type(); + } + + static basic_json meta() { + basic_json result; + + result["copyright"] = "(C) 2013-2017 Niels Lohmann"; + result["name"] = "JSON for Modern C++"; + result["version"]["string"] = + std::to_string(NLOHMANN_JSON_VERSION_MAJOR)+"."+ + std::to_string(NLOHMANN_JSON_VERSION_MINOR)+"."+ + std::to_string(NLOHMANN_JSON_VERSION_PATCH); + result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR; + result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR; + result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH; + +#ifdef _WIN32 + result["platform"] = "win32"; +#elif defined __linux__ + result["platform"] = "linux"; +#elif defined __APPLE__ + result["platform"] = "apple"; +#elif defined __unix__ + result["platform"] = "unix"; +#else + result["platform"] = "unknown"; +#endif + +#if defined(__ICC) || defined(__INTEL_COMPILER) + result["compiler"] = {{"family", "icc"}, {"version", __INTEL_COMPILER}}; +#elif defined(__clang__) + result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}}; +#elif defined(__GNUC__) || defined(__GNUG__) + result["compiler"] = {{"family", "gcc"}, + {"version", std::to_string(__GNUC__)+"."+std::to_string(__GNUC_MINOR__)+"."+ + std::to_string(__GNUC_PATCHLEVEL__)}}; +#elif defined(__HP_cc) || defined(__HP_aCC) + result["compiler"] = "hp" +#elif defined(__IBMCPP__) + result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}}; +#elif defined(_MSC_VER) + result["compiler"] = {{"family", "msvc"}, {"version", _MSC_VER}}; +#elif defined(__PGI) + result["compiler"] = {{"family", "pgcpp"}, {"version", __PGI}}; +#elif defined(__SUNPRO_CC) + result["compiler"] = {{"family", "sunpro"}, {"version", __SUNPRO_CC}}; +#else + result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}}; +#endif + +#ifdef __cplusplus + result["compiler"]["c++"] = std::to_string(__cplusplus); +#else + result["compiler"]["c++"] = "unknown"; +#endif + return result; + } + +#if defined(JSON_HAS_CPP_14) + + using object_comparator_t = std::less<>; +#else + using object_comparator_t = std::less<StringType>; +#endif + + using object_t = ObjectType<StringType, + basic_json, + object_comparator_t, + AllocatorType<std::pair<const StringType, + basic_json>>>; + + using array_t = ArrayType<basic_json, AllocatorType<basic_json>>; + + using string_t = StringType; + + using boolean_t = BooleanType; + + using number_integer_t = NumberIntegerType; + + using number_unsigned_t = NumberUnsignedType; + + using number_float_t = NumberFloatType; + +private: + template<typename T, typename... Args> + static T *create(Args &&... args) { + AllocatorType<T> alloc; + using AllocatorTraits = std::allocator_traits<AllocatorType<T>>; + + auto deleter = [&](T *object) { + AllocatorTraits::deallocate(alloc, object, 1); + }; + std::unique_ptr<T, decltype(deleter)> object(AllocatorTraits::allocate(alloc, 1), deleter); + AllocatorTraits::construct(alloc, object.get(), std::forward<Args>(args)...); + assert(object != nullptr); + return object.release(); + } + + union json_value { + object_t *object; + + array_t *array; + + string_t *string; + + boolean_t boolean; + + number_integer_t number_integer; + + number_unsigned_t number_unsigned; + + number_float_t number_float; + + json_value() = default; + + json_value(boolean_t v) noexcept : boolean(v) {} + + json_value(number_integer_t v) noexcept : number_integer(v) {} + + json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} + + json_value(number_float_t v) noexcept : number_float(v) {} + + json_value(value_t t) { + switch(t) { + case value_t::object: { + object = create<object_t>(); + break; + } + + case value_t::array: { + array = create<array_t>(); + break; + } + + case value_t::string: { + string = create<string_t>(""); + break; + } + + case value_t::boolean: { + boolean = boolean_t(false); + break; + } + + case value_t::number_integer: { + number_integer = number_integer_t(0); + break; + } + + case value_t::number_unsigned: { + number_unsigned = number_unsigned_t(0); + break; + } + + case value_t::number_float: { + number_float = number_float_t(0.0); + break; + } + + case value_t::null: { + object = nullptr; + break; + } + + default: { + object = nullptr; + if(JSON_UNLIKELY(t == value_t::null)) { + JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.1.2")); + } + break; + } + } + } + + json_value(const string_t &value) { + string = create<string_t>(value); + } + + json_value(string_t &&value) { + string = create<string_t>(std::move(value)); + } + + json_value(const object_t &value) { + object = create<object_t>(value); + } + + json_value(object_t &&value) { + object = create<object_t>(std::move(value)); + } + + json_value(const array_t &value) { + array = create<array_t>(value); + } + + json_value(array_t &&value) { + array = create<array_t>(std::move(value)); + } + + void destroy(value_t t) noexcept { + switch(t) { + case value_t::object: { + AllocatorType<object_t> alloc; + std::allocator_traits<decltype(alloc)>::destroy(alloc, object); + std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1); + break; + } + + case value_t::array: { + AllocatorType<array_t> alloc; + std::allocator_traits<decltype(alloc)>::destroy(alloc, array); + std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1); + break; + } + + case value_t::string: { + AllocatorType<string_t> alloc; + std::allocator_traits<decltype(alloc)>::destroy(alloc, string); + std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1); + break; + } + + default: { + break; + } + } + } + }; + + void assert_invariant() const noexcept { + assert(m_type != value_t::object or m_value.object != nullptr); + assert(m_type != value_t::array or m_value.array != nullptr); + assert(m_type != value_t::string or m_value.string != nullptr); + } + +public: + using parse_event_t = typename parser::parse_event_t; + + using parser_callback_t = typename parser::parser_callback_t; + + basic_json(const value_t v) + : m_type(v), m_value(v) { + assert_invariant(); + } + + basic_json(std::nullptr_t = nullptr) noexcept + : basic_json(value_t::null) { + assert_invariant(); + } + + template<typename CompatibleType, + typename U = detail::uncvref_t<CompatibleType>, + detail::enable_if_t< + detail::is_compatible_type<basic_json_t, U>::value, int> = 0> + basic_json(CompatibleType &&val) noexcept(noexcept( + JSONSerializer<U>::to_json(std::declval<basic_json_t &>(), + std::forward<CompatibleType>(val)))) { + JSONSerializer<U>::to_json(*this, std::forward<CompatibleType>(val)); + assert_invariant(); + } + + template<typename BasicJsonType, + detail::enable_if_t< + detail::is_basic_json<BasicJsonType>::value and not std::is_same<basic_json, BasicJsonType>::value, int> = 0> + basic_json(const BasicJsonType &val) { + using other_boolean_t = typename BasicJsonType::boolean_t; + using other_number_float_t = typename BasicJsonType::number_float_t; + using other_number_integer_t = typename BasicJsonType::number_integer_t; + using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using other_string_t = typename BasicJsonType::string_t; + using other_object_t = typename BasicJsonType::object_t; + using other_array_t = typename BasicJsonType::array_t; + + switch(val.type()) { + case value_t::boolean: + JSONSerializer<other_boolean_t>::to_json(*this, val.template get<other_boolean_t>()); + break; + case value_t::number_float: + JSONSerializer<other_number_float_t>::to_json(*this, val.template get<other_number_float_t>()); + break; + case value_t::number_integer: + JSONSerializer<other_number_integer_t>::to_json(*this, val.template get<other_number_integer_t>()); + break; + case value_t::number_unsigned: + JSONSerializer<other_number_unsigned_t>::to_json(*this, val.template get<other_number_unsigned_t>()); + break; + case value_t::string: + JSONSerializer<other_string_t>::to_json(*this, val.template get_ref<const other_string_t &>()); + break; + case value_t::object: + JSONSerializer<other_object_t>::to_json(*this, val.template get_ref<const other_object_t &>()); + break; + case value_t::array: + JSONSerializer<other_array_t>::to_json(*this, val.template get_ref<const other_array_t &>()); + break; + case value_t::null: + *this = nullptr; + break; + case value_t::discarded: + m_type = value_t::discarded; + break; + } + assert_invariant(); + } + + basic_json(initializer_list_t init, + bool type_deduction = true, + value_t manual_type = value_t::array) { + bool is_an_object = std::all_of(init.begin(), init.end(), + [](const detail::json_ref<basic_json> &element_ref) { + return (element_ref->is_array() and element_ref->size() == 2 and + (*element_ref)[0].is_string()); + }); + + if(not type_deduction) { + if(manual_type == value_t::array) { + is_an_object = false; + } + + if(JSON_UNLIKELY(manual_type == value_t::object and not is_an_object)) { + JSON_THROW(type_error::create(301, "cannot create object from initializer list")); + } + } + + if(is_an_object) { + m_type = value_t::object; + m_value = value_t::object; + + std::for_each(init.begin(), init.end(), [this](const detail::json_ref<basic_json> &element_ref) { + auto element = element_ref.moved_or_copied(); + m_value.object->emplace( + std::move(*((*element.m_value.array)[0].m_value.string)), + std::move((*element.m_value.array)[1])); + }); + } else { + m_type = value_t::array; + m_value.array = create<array_t>(init.begin(), init.end()); + } + + assert_invariant(); + } + + static basic_json array(initializer_list_t init = {}) { + return basic_json(init, false, value_t::array); + } + + static basic_json object(initializer_list_t init = {}) { + return basic_json(init, false, value_t::object); + } + + basic_json(size_type cnt, const basic_json &val) + : m_type(value_t::array) { + m_value.array = create<array_t>(cnt, val); + assert_invariant(); + } + + template<class InputIT, typename std::enable_if< + std::is_same<InputIT, typename basic_json_t::iterator>::value or + std::is_same<InputIT, typename basic_json_t::const_iterator>::value, int>::type = 0> + basic_json(InputIT first, InputIT last) { + assert(first.m_object != nullptr); + assert(last.m_object != nullptr); + + if(JSON_UNLIKELY(first.m_object != last.m_object)) { + JSON_THROW(invalid_iterator::create(201, "iterators are not compatible")); + } + + m_type = first.m_object->m_type; + + switch(m_type) { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: { + if(JSON_UNLIKELY(not first.m_it.primitive_iterator.is_begin() + or not last.m_it.primitive_iterator.is_end())) { + JSON_THROW(invalid_iterator::create(204, "iterators out of range")); + } + break; + } + + default: + break; + } + + switch(m_type) { + case value_t::number_integer: { + m_value.number_integer = first.m_object->m_value.number_integer; + break; + } + + case value_t::number_unsigned: { + m_value.number_unsigned = first.m_object->m_value.number_unsigned; + break; + } + + case value_t::number_float: { + m_value.number_float = first.m_object->m_value.number_float; + break; + } + + case value_t::boolean: { + m_value.boolean = first.m_object->m_value.boolean; + break; + } + + case value_t::string: { + m_value = *first.m_object->m_value.string; + break; + } + + case value_t::object: { + m_value.object = create<object_t>(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: { + m_value.array = create<array_t>(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + default: + JSON_THROW(invalid_iterator::create(206, "cannot construct with iterators from "+ + std::string(first.m_object->type_name()))); + } + + assert_invariant(); + } + + basic_json(const detail::json_ref<basic_json> &ref) + : basic_json(ref.moved_or_copied()) {} + + basic_json(const basic_json &other) + : m_type(other.m_type) { + other.assert_invariant(); + + switch(m_type) { + case value_t::object: { + m_value = *other.m_value.object; + break; + } + + case value_t::array: { + m_value = *other.m_value.array; + break; + } + + case value_t::string: { + m_value = *other.m_value.string; + break; + } + + case value_t::boolean: { + m_value = other.m_value.boolean; + break; + } + + case value_t::number_integer: { + m_value = other.m_value.number_integer; + break; + } + + case value_t::number_unsigned: { + m_value = other.m_value.number_unsigned; + break; + } + + case value_t::number_float: { + m_value = other.m_value.number_float; + break; + } + + default: + break; + } + + assert_invariant(); + } + + basic_json(basic_json &&other) noexcept + : m_type(std::move(other.m_type)), + m_value(std::move(other.m_value)) { + other.assert_invariant(); + + other.m_type = value_t::null; + other.m_value = {}; + + assert_invariant(); + } + + reference &operator=(basic_json other) noexcept( + std::is_nothrow_move_constructible<value_t>::value and + std::is_nothrow_move_assignable<value_t>::value and + std::is_nothrow_move_constructible<json_value>::value and + std::is_nothrow_move_assignable<json_value>::value + ) { + other.assert_invariant(); + + using std::swap; + swap(m_type, other.m_type); + swap(m_value, other.m_value); + + assert_invariant(); + return *this; + } + + ~basic_json() noexcept { + assert_invariant(); + m_value.destroy(m_type); + } + +public: + string_t dump(const int indent = -1, const char indent_char = ' ', + const bool ensure_ascii = false) const { + string_t result; + serializer s(detail::output_adapter<char, string_t>(result), indent_char); + + if(indent >= 0) { + s.dump(*this, true, ensure_ascii, static_cast<unsigned int>(indent)); + } else { + s.dump(*this, false, ensure_ascii, 0); + } + + return result; + } + + constexpr value_t type() const noexcept { + return m_type; + } + + constexpr bool is_primitive() const noexcept { + return is_null() or is_string() or is_boolean() or is_number(); + } + + constexpr bool is_structured() const noexcept { + return is_array() or is_object(); + } + + constexpr bool is_null() const noexcept { + return (m_type == value_t::null); + } + + constexpr bool is_boolean() const noexcept { + return (m_type == value_t::boolean); + } + + constexpr bool is_number() const noexcept { + return is_number_integer() or is_number_float(); + } + + constexpr bool is_number_integer() const noexcept { + return (m_type == value_t::number_integer or m_type == value_t::number_unsigned); + } + + constexpr bool is_number_unsigned() const noexcept { + return (m_type == value_t::number_unsigned); + } + + constexpr bool is_number_float() const noexcept { + return (m_type == value_t::number_float); + } + + constexpr bool is_object() const noexcept { + return (m_type == value_t::object); + } + + constexpr bool is_array() const noexcept { + return (m_type == value_t::array); + } + + constexpr bool is_string() const noexcept { + return (m_type == value_t::string); + } + + constexpr bool is_discarded() const noexcept { + return (m_type == value_t::discarded); + } + + constexpr operator value_t() const noexcept { + return m_type; + } + +private: + boolean_t get_impl(boolean_t *) const { + if(JSON_LIKELY(is_boolean())) { + return m_value.boolean; + } + + JSON_THROW(type_error::create(302, "type must be boolean, but is "+std::string(type_name()))); + } + + object_t *get_impl_ptr(object_t *) noexcept { + return is_object() ? m_value.object : nullptr; + } + + constexpr const object_t *get_impl_ptr(const object_t *) const noexcept { + return is_object() ? m_value.object : nullptr; + } + + array_t *get_impl_ptr(array_t *) noexcept { + return is_array() ? m_value.array : nullptr; + } + + constexpr const array_t *get_impl_ptr(const array_t *) const noexcept { + return is_array() ? m_value.array : nullptr; + } + + string_t *get_impl_ptr(string_t *) noexcept { + return is_string() ? m_value.string : nullptr; + } + + constexpr const string_t *get_impl_ptr(const string_t *) const noexcept { + return is_string() ? m_value.string : nullptr; + } + + boolean_t *get_impl_ptr(boolean_t *) noexcept { + return is_boolean() ? &m_value.boolean : nullptr; + } + + constexpr const boolean_t *get_impl_ptr(const boolean_t *) const noexcept { + return is_boolean() ? &m_value.boolean : nullptr; + } + + number_integer_t *get_impl_ptr(number_integer_t *) noexcept { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + constexpr const number_integer_t *get_impl_ptr(const number_integer_t *) const noexcept { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + number_unsigned_t *get_impl_ptr(number_unsigned_t *) noexcept { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + constexpr const number_unsigned_t *get_impl_ptr(const number_unsigned_t *) const noexcept { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + number_float_t *get_impl_ptr(number_float_t *) noexcept { + return is_number_float() ? &m_value.number_float : nullptr; + } + + constexpr const number_float_t *get_impl_ptr(const number_float_t *) const noexcept { + return is_number_float() ? &m_value.number_float : nullptr; + } + + template<typename ReferenceType, typename ThisType> + static ReferenceType get_ref_impl(ThisType &obj) { + auto ptr = obj.template get_ptr<typename std::add_pointer<ReferenceType>::type>(); + + if(JSON_LIKELY(ptr != nullptr)) { + return *ptr; + } + + JSON_THROW(type_error::create(303, "incompatible ReferenceType for get_ref, actual type is "+ + std::string(obj.type_name()))); + } + +public: + template<typename BasicJsonType, detail::enable_if_t< + std::is_same<typename std::remove_const<BasicJsonType>::type, basic_json_t>::value, + int> = 0> + basic_json get() const { + return *this; + } + + template<typename BasicJsonType, detail::enable_if_t< + not std::is_same<BasicJsonType, basic_json>::value and + detail::is_basic_json<BasicJsonType>::value, int> = 0> + BasicJsonType get() const { + return *this; + } + + template<typename ValueTypeCV, typename ValueType = detail::uncvref_t<ValueTypeCV>, + detail::enable_if_t< + not detail::is_basic_json<ValueType>::value and + detail::has_from_json<basic_json_t, ValueType>::value and + not detail::has_non_default_from_json<basic_json_t, ValueType>::value, + int> = 0> + ValueType get() const noexcept(noexcept( + JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t &>(), std::declval<ValueType &>()))) { + static_assert(not std::is_reference<ValueTypeCV>::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + static_assert(std::is_default_constructible<ValueType>::value, + "types must be DefaultConstructible when used with get()"); + + ValueType ret; + JSONSerializer<ValueType>::from_json(*this, ret); + return ret; + } + + template<typename ValueTypeCV, typename ValueType = detail::uncvref_t<ValueTypeCV>, + detail::enable_if_t<not std::is_same<basic_json_t, ValueType>::value and + detail::has_non_default_from_json<basic_json_t, ValueType>::value, + int> = 0> + ValueType get() const noexcept(noexcept( + JSONSerializer<ValueTypeCV>::from_json(std::declval<const basic_json_t &>()))) { + static_assert(not std::is_reference<ValueTypeCV>::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + return JSONSerializer<ValueTypeCV>::from_json(*this); + } + + template<typename PointerType, typename std::enable_if< + std::is_pointer<PointerType>::value, int>::type = 0> + PointerType get() noexcept { + return get_ptr<PointerType>(); + } + + template<typename PointerType, typename std::enable_if< + std::is_pointer<PointerType>::value, int>::type = 0> + constexpr const PointerType get() const noexcept { + return get_ptr<PointerType>(); + } + + template<typename PointerType, typename std::enable_if< + std::is_pointer<PointerType>::value, int>::type = 0> + PointerType get_ptr() noexcept { + using pointee_t = typename std::remove_const<typename + std::remove_pointer<typename + std::remove_const<PointerType>::type>::type>::type; + + static_assert( + std::is_same<object_t, pointee_t>::value + or std::is_same<array_t, pointee_t>::value + or std::is_same<string_t, pointee_t>::value + or std::is_same<boolean_t, pointee_t>::value + or std::is_same<number_integer_t, pointee_t>::value + or std::is_same<number_unsigned_t, pointee_t>::value + or std::is_same<number_float_t, pointee_t>::value, "incompatible pointer type"); + + return get_impl_ptr(static_cast<PointerType>(nullptr)); + } + + template<typename PointerType, typename std::enable_if< + std::is_pointer<PointerType>::value and + std::is_const<typename std::remove_pointer<PointerType>::type>::value, int>::type = 0> + constexpr const PointerType get_ptr() const noexcept { + using pointee_t = typename std::remove_const<typename + std::remove_pointer<typename + std::remove_const<PointerType>::type>::type>::type; + + static_assert( + std::is_same<object_t, pointee_t>::value + or std::is_same<array_t, pointee_t>::value + or std::is_same<string_t, pointee_t>::value + or std::is_same<boolean_t, pointee_t>::value + or std::is_same<number_integer_t, pointee_t>::value + or std::is_same<number_unsigned_t, pointee_t>::value + or std::is_same<number_float_t, pointee_t>::value, "incompatible pointer type"); + + return get_impl_ptr(static_cast<PointerType>(nullptr)); + } + + template<typename ReferenceType, typename std::enable_if< + std::is_reference<ReferenceType>::value, int>::type = 0> + ReferenceType get_ref() { + return get_ref_impl<ReferenceType>(*this); + } + + template<typename ReferenceType, typename std::enable_if< + std::is_reference<ReferenceType>::value and + std::is_const<typename std::remove_reference<ReferenceType>::type>::value, int>::type = 0> + ReferenceType get_ref() const { + return get_ref_impl<ReferenceType>(*this); + } + + template<typename ValueType, typename std::enable_if< + not std::is_pointer<ValueType>::value and + not std::is_same<ValueType, detail::json_ref<basic_json>>::value and + not std::is_same<ValueType, typename string_t::value_type>::value and + not detail::is_basic_json<ValueType>::value + #ifndef _MSC_VER + and not std::is_same<ValueType, std::initializer_list<typename string_t::value_type>>::value +#endif +#if defined(JSON_HAS_CPP_17) + and not std::is_same<ValueType, typename std::string_view>::value +#endif + , int>::type = 0> + operator ValueType() const { + return get<ValueType>(); + } + + reference at(size_type idx) { + if(JSON_LIKELY(is_array())) { + JSON_TRY { + return m_value.array->at(idx); + } + JSON_CATCH (std::out_of_range &) { + JSON_THROW(out_of_range::create(401, "array index "+std::to_string(idx)+" is out of range")); + } + } else { + JSON_THROW(type_error::create(304, "cannot use at() with "+std::string(type_name()))); + } + } + + const_reference at(size_type idx) const { + if(JSON_LIKELY(is_array())) { + JSON_TRY { + return m_value.array->at(idx); + } + JSON_CATCH (std::out_of_range &) { + JSON_THROW(out_of_range::create(401, "array index "+std::to_string(idx)+" is out of range")); + } + } else { + JSON_THROW(type_error::create(304, "cannot use at() with "+std::string(type_name()))); + } + } + + reference at(const typename object_t::key_type &key) { + if(JSON_LIKELY(is_object())) { + JSON_TRY { + return m_value.object->at(key); + } + JSON_CATCH (std::out_of_range &) { + JSON_THROW(out_of_range::create(403, "key '"+key+"' not found")); + } + } else { + JSON_THROW(type_error::create(304, "cannot use at() with "+std::string(type_name()))); + } + } + + const_reference at(const typename object_t::key_type &key) const { + if(JSON_LIKELY(is_object())) { + JSON_TRY { + return m_value.object->at(key); + } + JSON_CATCH (std::out_of_range &) { + JSON_THROW(out_of_range::create(403, "key '"+key+"' not found")); + } + } else { + JSON_THROW(type_error::create(304, "cannot use at() with "+std::string(type_name()))); + } + } + + reference operator[](size_type idx) { + if(is_null()) { + m_type = value_t::array; + m_value.array = create<array_t>(); + assert_invariant(); + } + + if(JSON_LIKELY(is_array())) { + if(idx >= m_value.array->size()) { + m_value.array->insert(m_value.array->end(), + idx-m_value.array->size()+1, + basic_json()); + } + + return m_value.array->operator[](idx); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with "+std::string(type_name()))); + } + + const_reference operator[](size_type idx) const { + if(JSON_LIKELY(is_array())) { + return m_value.array->operator[](idx); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with "+std::string(type_name()))); + } + + reference operator[](const typename object_t::key_type &key) { + if(is_null()) { + m_type = value_t::object; + m_value.object = create<object_t>(); + assert_invariant(); + } + + if(JSON_LIKELY(is_object())) { + return m_value.object->operator[](key); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with "+std::string(type_name()))); + } + + const_reference operator[](const typename object_t::key_type &key) const { + if(JSON_LIKELY(is_object())) { + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with "+std::string(type_name()))); + } + + template<typename T> + reference operator[](T *key) { + if(is_null()) { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + if(JSON_LIKELY(is_object())) { + return m_value.object->operator[](key); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with "+std::string(type_name()))); + } + + template<typename T> + const_reference operator[](T *key) const { + if(JSON_LIKELY(is_object())) { + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with "+std::string(type_name()))); + } + + template<class ValueType, typename std::enable_if< + std::is_convertible<basic_json_t, ValueType>::value, int>::type = 0> + ValueType value(const typename object_t::key_type &key, const ValueType &default_value) const { + if(JSON_LIKELY(is_object())) { + const auto it = find(key); + if(it != end()) { + return *it; + } + + return default_value; + } + + JSON_THROW(type_error::create(306, "cannot use value() with "+std::string(type_name()))); + } + + string_t value(const typename object_t::key_type &key, const char *default_value) const { + return value(key, string_t(default_value)); + } + + template<class ValueType, typename std::enable_if< + std::is_convertible<basic_json_t, ValueType>::value, int>::type = 0> + ValueType value(const json_pointer &ptr, const ValueType &default_value) const { + if(JSON_LIKELY(is_object())) { + JSON_TRY { + return ptr.get_checked(this); + } + JSON_CATCH (out_of_range &) { + return default_value; + } + } + + JSON_THROW(type_error::create(306, "cannot use value() with "+std::string(type_name()))); + } + + string_t value(const json_pointer &ptr, const char *default_value) const { + return value(ptr, string_t(default_value)); + } + + reference front() { + return *begin(); + } + + const_reference front() const { + return *cbegin(); + } + + reference back() { + auto tmp = end(); + --tmp; + return *tmp; + } + + const_reference back() const { + auto tmp = cend(); + --tmp; + return *tmp; + } + + template<class IteratorType, typename std::enable_if< + std::is_same<IteratorType, typename basic_json_t::iterator>::value or + std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int>::type + = 0> + IteratorType erase(IteratorType pos) { + if(JSON_UNLIKELY(this != pos.m_object)) { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + IteratorType result = end(); + + switch(m_type) { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: { + if(JSON_UNLIKELY(not pos.m_it.primitive_iterator.is_begin())) { + JSON_THROW(invalid_iterator::create(205, "iterator out of range")); + } + + if(is_string()) { + AllocatorType<string_t> alloc; + std::allocator_traits<decltype(alloc)>::destroy(alloc, m_value.string); + std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.string, 1); + m_value.string = nullptr; + } + + m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: { + result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator); + break; + } + + case value_t::array: { + result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); + break; + } + + default: + JSON_THROW(type_error::create(307, "cannot use erase() with "+std::string(type_name()))); + } + + return result; + } + + template<class IteratorType, typename std::enable_if< + std::is_same<IteratorType, typename basic_json_t::iterator>::value or + std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int>::type + = 0> + IteratorType erase(IteratorType first, IteratorType last) { + if(JSON_UNLIKELY(this != first.m_object or this != last.m_object)) { + JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value")); + } + + IteratorType result = end(); + + switch(m_type) { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: { + if(JSON_LIKELY(not first.m_it.primitive_iterator.is_begin() + or not last.m_it.primitive_iterator.is_end())) { + JSON_THROW(invalid_iterator::create(204, "iterators out of range")); + } + + if(is_string()) { + AllocatorType<string_t> alloc; + std::allocator_traits<decltype(alloc)>::destroy(alloc, m_value.string); + std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.string, 1); + m_value.string = nullptr; + } + + m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: { + result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: { + result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + default: + JSON_THROW(type_error::create(307, "cannot use erase() with "+std::string(type_name()))); + } + + return result; + } + + size_type erase(const typename object_t::key_type &key) { + if(JSON_LIKELY(is_object())) { + return m_value.object->erase(key); + } + + JSON_THROW(type_error::create(307, "cannot use erase() with "+std::string(type_name()))); + } + + void erase(const size_type idx) { + if(JSON_LIKELY(is_array())) { + if(JSON_UNLIKELY(idx >= size())) { + JSON_THROW(out_of_range::create(401, "array index "+std::to_string(idx)+" is out of range")); + } + + m_value.array->erase(m_value.array->begin()+static_cast<difference_type>(idx)); + } else { + JSON_THROW(type_error::create(307, "cannot use erase() with "+std::string(type_name()))); + } + } + + template<typename KeyT> + iterator find(KeyT &&key) { + auto result = end(); + + if(is_object()) { + result.m_it.object_iterator = m_value.object->find(std::forward<KeyT>(key)); + } + + return result; + } + + template<typename KeyT> + const_iterator find(KeyT &&key) const { + auto result = cend(); + + if(is_object()) { + result.m_it.object_iterator = m_value.object->find(std::forward<KeyT>(key)); + } + + return result; + } + + template<typename KeyT> + size_type count(KeyT &&key) const { + return is_object() ? m_value.object->count(std::forward<KeyT>(key)) : 0; + } + + iterator begin() noexcept { + iterator result(this); + result.set_begin(); + return result; + } + + const_iterator begin() const noexcept { + return cbegin(); + } + + const_iterator cbegin() const noexcept { + const_iterator result(this); + result.set_begin(); + return result; + } + + iterator end() noexcept { + iterator result(this); + result.set_end(); + return result; + } + + const_iterator end() const noexcept { + return cend(); + } + + const_iterator cend() const noexcept { + const_iterator result(this); + result.set_end(); + return result; + } + + reverse_iterator rbegin() noexcept { + return reverse_iterator(end()); + } + + const_reverse_iterator rbegin() const noexcept { + return crbegin(); + } + + reverse_iterator rend() noexcept { + return reverse_iterator(begin()); + } + + const_reverse_iterator rend() const noexcept { + return crend(); + } + + const_reverse_iterator crbegin() const noexcept { + return const_reverse_iterator(cend()); + } + + const_reverse_iterator crend() const noexcept { + return const_reverse_iterator(cbegin()); + } + +public: + iteration_proxy<iterator> items() noexcept { + return iteration_proxy<iterator>(*this); + } + + iteration_proxy<const_iterator> items() const noexcept { + return iteration_proxy<const_iterator>(*this); + } + + bool empty() const noexcept { + switch(m_type) { + case value_t::null: { + return true; + } + + case value_t::array: { + return m_value.array->empty(); + } + + case value_t::object: { + return m_value.object->empty(); + } + + default: { + return false; + } + } + } + + size_type size() const noexcept { + switch(m_type) { + case value_t::null: { + return 0; + } + + case value_t::array: { + return m_value.array->size(); + } + + case value_t::object: { + return m_value.object->size(); + } + + default: { + return 1; + } + } + } + + size_type max_size() const noexcept { + switch(m_type) { + case value_t::array: { + return m_value.array->max_size(); + } + + case value_t::object: { + return m_value.object->max_size(); + } + + default: { + return size(); + } + } + } + + void clear() noexcept { + switch(m_type) { + case value_t::number_integer: { + m_value.number_integer = 0; + break; + } + + case value_t::number_unsigned: { + m_value.number_unsigned = 0; + break; + } + + case value_t::number_float: { + m_value.number_float = 0.0; + break; + } + + case value_t::boolean: { + m_value.boolean = false; + break; + } + + case value_t::string: { + m_value.string->clear(); + break; + } + + case value_t::array: { + m_value.array->clear(); + break; + } + + case value_t::object: { + m_value.object->clear(); + break; + } + + default: + break; + } + } + + void push_back(basic_json &&val) { + if(JSON_UNLIKELY(not(is_null() or is_array()))) { + JSON_THROW(type_error::create(308, "cannot use push_back() with "+std::string(type_name()))); + } + + if(is_null()) { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + m_value.array->push_back(std::move(val)); + + val.m_type = value_t::null; + } + + reference operator+=(basic_json &&val) { + push_back(std::move(val)); + return *this; + } + + void push_back(const basic_json &val) { + if(JSON_UNLIKELY(not(is_null() or is_array()))) { + JSON_THROW(type_error::create(308, "cannot use push_back() with "+std::string(type_name()))); + } + + if(is_null()) { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + m_value.array->push_back(val); + } + + reference operator+=(const basic_json &val) { + push_back(val); + return *this; + } + + void push_back(const typename object_t::value_type &val) { + if(JSON_UNLIKELY(not(is_null() or is_object()))) { + JSON_THROW(type_error::create(308, "cannot use push_back() with "+std::string(type_name()))); + } + + if(is_null()) { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + m_value.object->insert(val); + } + + reference operator+=(const typename object_t::value_type &val) { + push_back(val); + return *this; + } + + void push_back(initializer_list_t init) { + if(is_object() and init.size() == 2 and (*init.begin())->is_string()) { + basic_json &&key = init.begin()->moved_or_copied(); + push_back(typename object_t::value_type( + std::move(key.get_ref<string_t &>()), (init.begin()+1)->moved_or_copied())); + } else { + push_back(basic_json(init)); + } + } + + reference operator+=(initializer_list_t init) { + push_back(init); + return *this; + } + + template<class... Args> + void emplace_back(Args &&... args) { + if(JSON_UNLIKELY(not(is_null() or is_array()))) { + JSON_THROW(type_error::create(311, "cannot use emplace_back() with "+std::string(type_name()))); + } + + if(is_null()) { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + m_value.array->emplace_back(std::forward<Args>(args)...); + } + + template<class... Args> + std::pair<iterator, bool> emplace(Args &&... args) { + if(JSON_UNLIKELY(not(is_null() or is_object()))) { + JSON_THROW(type_error::create(311, "cannot use emplace() with "+std::string(type_name()))); + } + + if(is_null()) { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + auto res = m_value.object->emplace(std::forward<Args>(args)...); + + auto it = begin(); + it.m_it.object_iterator = res.first; + + return {it, res.second}; + } + + iterator insert(const_iterator pos, const basic_json &val) { + if(JSON_LIKELY(is_array())) { + if(JSON_UNLIKELY(pos.m_object != this)) { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, val); + return result; + } + + JSON_THROW(type_error::create(309, "cannot use insert() with "+std::string(type_name()))); + } + + iterator insert(const_iterator pos, basic_json &&val) { + return insert(pos, val); + } + + iterator insert(const_iterator pos, size_type cnt, const basic_json &val) { + if(JSON_LIKELY(is_array())) { + if(JSON_UNLIKELY(pos.m_object != this)) { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); + return result; + } + + JSON_THROW(type_error::create(309, "cannot use insert() with "+std::string(type_name()))); + } + + iterator insert(const_iterator pos, const_iterator first, const_iterator last) { + if(JSON_UNLIKELY(not is_array())) { + JSON_THROW(type_error::create(309, "cannot use insert() with "+std::string(type_name()))); + } + + if(JSON_UNLIKELY(pos.m_object != this)) { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + if(JSON_UNLIKELY(first.m_object != last.m_object)) { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + if(JSON_UNLIKELY(first.m_object == this)) { + JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container")); + } + + iterator result(this); + result.m_it.array_iterator = m_value.array->insert( + pos.m_it.array_iterator, + first.m_it.array_iterator, + last.m_it.array_iterator); + return result; + } + + iterator insert(const_iterator pos, initializer_list_t ilist) { + if(JSON_UNLIKELY(not is_array())) { + JSON_THROW(type_error::create(309, "cannot use insert() with "+std::string(type_name()))); + } + + if(JSON_UNLIKELY(pos.m_object != this)) { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist.begin(), ilist.end()); + return result; + } + + void insert(const_iterator first, const_iterator last) { + if(JSON_UNLIKELY(not is_object())) { + JSON_THROW(type_error::create(309, "cannot use insert() with "+std::string(type_name()))); + } + + if(JSON_UNLIKELY(first.m_object != last.m_object)) { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + if(JSON_UNLIKELY(not first.m_object->is_object())) { + JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); + } + + m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); + } + + void update(const_reference j) { + if(is_null()) { + m_type = value_t::object; + m_value.object = create<object_t>(); + assert_invariant(); + } + + if(JSON_UNLIKELY(not is_object())) { + JSON_THROW(type_error::create(312, "cannot use update() with "+std::string(type_name()))); + } + if(JSON_UNLIKELY(not j.is_object())) { + JSON_THROW(type_error::create(312, "cannot use update() with "+std::string(j.type_name()))); + } + + for(auto it = j.cbegin(); it != j.cend(); ++it) { + m_value.object->operator[](it.key()) = it.value(); + } + } + + void update(const_iterator first, const_iterator last) { + if(is_null()) { + m_type = value_t::object; + m_value.object = create<object_t>(); + assert_invariant(); + } + + if(JSON_UNLIKELY(not is_object())) { + JSON_THROW(type_error::create(312, "cannot use update() with "+std::string(type_name()))); + } + + if(JSON_UNLIKELY(first.m_object != last.m_object)) { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + if(JSON_UNLIKELY(not first.m_object->is_object() + or not last.m_object->is_object())) { + JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); + } + + for(auto it = first; it != last; ++it) { + m_value.object->operator[](it.key()) = it.value(); + } + } + + void swap(reference other) noexcept( + std::is_nothrow_move_constructible<value_t>::value and + std::is_nothrow_move_assignable<value_t>::value and + std::is_nothrow_move_constructible<json_value>::value and + std::is_nothrow_move_assignable<json_value>::value + ) { + std::swap(m_type, other.m_type); + std::swap(m_value, other.m_value); + assert_invariant(); + } + + void swap(array_t &other) { + if(JSON_LIKELY(is_array())) { + std::swap(*(m_value.array), other); + } else { + JSON_THROW(type_error::create(310, "cannot use swap() with "+std::string(type_name()))); + } + } + + void swap(object_t &other) { + if(JSON_LIKELY(is_object())) { + std::swap(*(m_value.object), other); + } else { + JSON_THROW(type_error::create(310, "cannot use swap() with "+std::string(type_name()))); + } + } + + void swap(string_t &other) { + if(JSON_LIKELY(is_string())) { + std::swap(*(m_value.string), other); + } else { + JSON_THROW(type_error::create(310, "cannot use swap() with "+std::string(type_name()))); + } + } + +public: + friend bool operator==(const_reference lhs, const_reference rhs) noexcept { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if(lhs_type == rhs_type) { + switch(lhs_type) { + case value_t::array: + return (*lhs.m_value.array == *rhs.m_value.array); + + case value_t::object: + return (*lhs.m_value.object == *rhs.m_value.object); + + case value_t::null: + return true; + + case value_t::string: + return (*lhs.m_value.string == *rhs.m_value.string); + + case value_t::boolean: + return (lhs.m_value.boolean == rhs.m_value.boolean); + + case value_t::number_integer: + return (lhs.m_value.number_integer == rhs.m_value.number_integer); + + case value_t::number_unsigned: + return (lhs.m_value.number_unsigned == rhs.m_value.number_unsigned); + + case value_t::number_float: + return (lhs.m_value.number_float == rhs.m_value.number_float); + + default: + return false; + } + } else if(lhs_type == value_t::number_integer and rhs_type == value_t::number_float) { + return (static_cast<number_float_t>(lhs.m_value.number_integer) == rhs.m_value.number_float); + } else if(lhs_type == value_t::number_float and rhs_type == value_t::number_integer) { + return (lhs.m_value.number_float == static_cast<number_float_t>(rhs.m_value.number_integer)); + } else if(lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) { + return (static_cast<number_float_t>(lhs.m_value.number_unsigned) == rhs.m_value.number_float); + } else if(lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) { + return (lhs.m_value.number_float == static_cast<number_float_t>(rhs.m_value.number_unsigned)); + } else if(lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) { + return (static_cast<number_integer_t>(lhs.m_value.number_unsigned) == rhs.m_value.number_integer); + } else if(lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) { + return (lhs.m_value.number_integer == static_cast<number_integer_t>(rhs.m_value.number_unsigned)); + } + + return false; + } + + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator==(const_reference lhs, const ScalarType rhs) noexcept { + return (lhs == basic_json(rhs)); + } + + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator==(const ScalarType lhs, const_reference rhs) noexcept { + return (basic_json(lhs) == rhs); + } + + friend bool operator!=(const_reference lhs, const_reference rhs) noexcept { + return not(lhs == rhs); + } + + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator!=(const_reference lhs, const ScalarType rhs) noexcept { + return (lhs != basic_json(rhs)); + } + + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator!=(const ScalarType lhs, const_reference rhs) noexcept { + return (basic_json(lhs) != rhs); + } + + friend bool operator<(const_reference lhs, const_reference rhs) noexcept { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if(lhs_type == rhs_type) { + switch(lhs_type) { + case value_t::array: + return (*lhs.m_value.array) < (*rhs.m_value.array); + + case value_t::object: + return *lhs.m_value.object < *rhs.m_value.object; + + case value_t::null: + return false; + + case value_t::string: + return *lhs.m_value.string < *rhs.m_value.string; + + case value_t::boolean: + return lhs.m_value.boolean < rhs.m_value.boolean; + + case value_t::number_integer: + return lhs.m_value.number_integer < rhs.m_value.number_integer; + + case value_t::number_unsigned: + return lhs.m_value.number_unsigned < rhs.m_value.number_unsigned; + + case value_t::number_float: + return lhs.m_value.number_float < rhs.m_value.number_float; + + default: + return false; + } + } else if(lhs_type == value_t::number_integer and rhs_type == value_t::number_float) { + return static_cast<number_float_t>(lhs.m_value.number_integer) < rhs.m_value.number_float; + } else if(lhs_type == value_t::number_float and rhs_type == value_t::number_integer) { + return lhs.m_value.number_float < static_cast<number_float_t>(rhs.m_value.number_integer); + } else if(lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) { + return static_cast<number_float_t>(lhs.m_value.number_unsigned) < rhs.m_value.number_float; + } else if(lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) { + return lhs.m_value.number_float < static_cast<number_float_t>(rhs.m_value.number_unsigned); + } else if(lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) { + return lhs.m_value.number_integer < static_cast<number_integer_t>(rhs.m_value.number_unsigned); + } else if(lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) { + return static_cast<number_integer_t>(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; + } + + return operator<(lhs_type, rhs_type); + } + + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator<(const_reference lhs, const ScalarType rhs) noexcept { + return (lhs < basic_json(rhs)); + } + + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator<(const ScalarType lhs, const_reference rhs) noexcept { + return (basic_json(lhs) < rhs); + } + + friend bool operator<=(const_reference lhs, const_reference rhs) noexcept { + return not(rhs < lhs); + } + + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator<=(const_reference lhs, const ScalarType rhs) noexcept { + return (lhs <= basic_json(rhs)); + } + + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator<=(const ScalarType lhs, const_reference rhs) noexcept { + return (basic_json(lhs) <= rhs); + } + + friend bool operator>(const_reference lhs, const_reference rhs) noexcept { + return not(lhs <= rhs); + } + + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator>(const_reference lhs, const ScalarType rhs) noexcept { + return (lhs > basic_json(rhs)); + } + + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator>(const ScalarType lhs, const_reference rhs) noexcept { + return (basic_json(lhs) > rhs); + } + + friend bool operator>=(const_reference lhs, const_reference rhs) noexcept { + return not(lhs < rhs); + } + + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator>=(const_reference lhs, const ScalarType rhs) noexcept { + return (lhs >= basic_json(rhs)); + } + + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator>=(const ScalarType lhs, const_reference rhs) noexcept { + return (basic_json(lhs) >= rhs); + } + + friend std::ostream &operator<<(std::ostream &o, const basic_json &j) { + const bool pretty_print = (o.width() > 0); + const auto indentation = (pretty_print ? o.width() : 0); + + o.width(0); + + serializer s(detail::output_adapter<char>(o), o.fill()); + s.dump(j, pretty_print, false, static_cast<unsigned int>(indentation)); + return o; + } + + static basic_json parse(detail::input_adapter i, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true) { + basic_json result; + parser(i, cb, allow_exceptions).parse(true, result); + return result; + } + + static basic_json parse(detail::input_adapter &i, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true) { + basic_json result; + parser(i, cb, allow_exceptions).parse(true, result); + return result; + } + + static bool accept(detail::input_adapter i) { + return parser(i).accept(true); + } + + static bool accept(detail::input_adapter &i) { + return parser(i).accept(true); + } + + template<class IteratorType, typename std::enable_if< + std::is_base_of< + std::random_access_iterator_tag, + typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0> + static basic_json parse(IteratorType first, IteratorType last, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true) { + basic_json result; + parser(detail::input_adapter(first, last), cb, allow_exceptions).parse(true, result); + return result; + } + + template<class IteratorType, typename std::enable_if< + std::is_base_of< + std::random_access_iterator_tag, + typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0> + static bool accept(IteratorType first, IteratorType last) { + return parser(detail::input_adapter(first, last)).accept(true); + } + + friend std::istream &operator>>(std::istream &i, basic_json &j) { + parser(detail::input_adapter(i)).parse(false, j); + return i; + } + + const char *type_name() const noexcept { + { + switch(m_type) { + case value_t::null: + return "null"; + case value_t::object: + return "object"; + case value_t::array: + return "array"; + case value_t::string: + return "string"; + case value_t::boolean: + return "boolean"; + case value_t::discarded: + return "discarded"; + default: + return "number"; + } + } + } + +private: + value_t m_type = value_t::null; + + json_value m_value = {}; + +public: + static std::vector<uint8_t> to_cbor(const basic_json &j) { + std::vector<uint8_t> result; + to_cbor(j, result); + return result; + } + + static void to_cbor(const basic_json &j, detail::output_adapter<uint8_t> o) { + binary_writer<uint8_t>(o).write_cbor(j); + } + + static void to_cbor(const basic_json &j, detail::output_adapter<char> o) { + binary_writer<char>(o).write_cbor(j); + } + + static std::vector<uint8_t> to_msgpack(const basic_json &j) { + std::vector<uint8_t> result; + to_msgpack(j, result); + return result; + } + + static void to_msgpack(const basic_json &j, detail::output_adapter<uint8_t> o) { + binary_writer<uint8_t>(o).write_msgpack(j); + } + + static void to_msgpack(const basic_json &j, detail::output_adapter<char> o) { + binary_writer<char>(o).write_msgpack(j); + } + + static std::vector<uint8_t> to_ubjson(const basic_json &j, + const bool use_size = false, + const bool use_type = false) { + std::vector<uint8_t> result; + to_ubjson(j, result, use_size, use_type); + return result; + } + + static void to_ubjson(const basic_json &j, detail::output_adapter<uint8_t> o, + const bool use_size = false, const bool use_type = false) { + binary_writer<uint8_t>(o).write_ubjson(j, use_size, use_type); + } + + static void to_ubjson(const basic_json &j, detail::output_adapter<char> o, + const bool use_size = false, const bool use_type = false) { + binary_writer<char>(o).write_ubjson(j, use_size, use_type); + } + + static basic_json from_cbor(detail::input_adapter i, + const bool strict = true) { + return binary_reader(i).parse_cbor(strict); + } + + template<typename A1, typename A2, + detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0> + static basic_json from_cbor(A1 &&a1, A2 &&a2, const bool strict = true) { + return binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).parse_cbor(strict); + } + + static basic_json from_msgpack(detail::input_adapter i, + const bool strict = true) { + return binary_reader(i).parse_msgpack(strict); + } + + template<typename A1, typename A2, + detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0> + static basic_json from_msgpack(A1 &&a1, A2 &&a2, const bool strict = true) { + return binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).parse_msgpack(strict); + } + + static basic_json from_ubjson(detail::input_adapter i, + const bool strict = true) { + return binary_reader(i).parse_ubjson(strict); + } + + template<typename A1, typename A2, + detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0> + static basic_json from_ubjson(A1 &&a1, A2 &&a2, const bool strict = true) { + return binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).parse_ubjson(strict); + } + + reference operator[](const json_pointer &ptr) { + return ptr.get_unchecked(this); + } + + const_reference operator[](const json_pointer &ptr) const { + return ptr.get_unchecked(this); + } + + reference at(const json_pointer &ptr) { + return ptr.get_checked(this); + } + + const_reference at(const json_pointer &ptr) const { + return ptr.get_checked(this); + } + + basic_json flatten() const { + basic_json result(value_t::object); + json_pointer::flatten("", *this, result); + return result; + } + + basic_json unflatten() const { + return json_pointer::unflatten(*this); + } + + basic_json patch(const basic_json &json_patch) const { + basic_json result = *this; + + enum class patch_operations { + add, remove, replace, move, copy, test, invalid + }; + + const auto get_op = [](const std::string &op) { + if(op == "add") { + return patch_operations::add; + } + if(op == "remove") { + return patch_operations::remove; + } + if(op == "replace") { + return patch_operations::replace; + } + if(op == "move") { + return patch_operations::move; + } + if(op == "copy") { + return patch_operations::copy; + } + if(op == "test") { + return patch_operations::test; + } + + return patch_operations::invalid; + }; + + const auto operation_add = [&result](json_pointer &ptr, basic_json val) { + if(ptr.is_root()) { + result = val; + } else { + json_pointer top_pointer = ptr.top(); + if(top_pointer != ptr) { + result.at(top_pointer); + } + + const auto last_path = ptr.pop_back(); + basic_json &parent = result[ptr]; + + switch(parent.m_type) { + case value_t::null: + case value_t::object: { + parent[last_path] = val; + break; + } + + case value_t::array: { + if(last_path == "-") { + parent.push_back(val); + } else { + const auto idx = json_pointer::array_index(last_path); + if(JSON_UNLIKELY(static_cast<size_type>(idx) > parent.size())) { + JSON_THROW(out_of_range::create(401, "array index "+std::to_string(idx)+" is out of range")); + } else { + parent.insert(parent.begin()+static_cast<difference_type>(idx), val); + } + } + break; + } + + default: { + assert(false); + } + } + } + }; + + const auto operation_remove = [&result](json_pointer &ptr) { + const auto last_path = ptr.pop_back(); + basic_json &parent = result.at(ptr); + + if(parent.is_object()) { + auto it = parent.find(last_path); + if(JSON_LIKELY(it != parent.end())) { + parent.erase(it); + } else { + JSON_THROW(out_of_range::create(403, "key '"+last_path+"' not found")); + } + } else if(parent.is_array()) { + parent.erase(static_cast<size_type>(json_pointer::array_index(last_path))); + } + }; + + if(JSON_UNLIKELY(not json_patch.is_array())) { + JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); + } + + for(const auto &val : json_patch) { + const auto get_value = [&val](const std::string &op, + const std::string &member, + bool string_type) -> basic_json & { + auto it = val.m_value.object->find(member); + + const auto error_msg = (op == "op") ? "operation" : "operation '"+op+"'"; + + if(JSON_UNLIKELY(it == val.m_value.object->end())) { + JSON_THROW(parse_error::create(105, 0, error_msg+" must have member '"+member+"'")); + } + + if(JSON_UNLIKELY(string_type and not it->second.is_string())) { + JSON_THROW(parse_error::create(105, 0, error_msg+" must have string member '"+member+"'")); + } + + return it->second; + }; + + if(JSON_UNLIKELY(not val.is_object())) { + JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); + } + + const std::string op = get_value("op", "op", true); + const std::string path = get_value(op, "path", true); + json_pointer ptr(path); + + switch(get_op(op)) { + case patch_operations::add: { + operation_add(ptr, get_value("add", "value", false)); + break; + } + + case patch_operations::remove: { + operation_remove(ptr); + break; + } + + case patch_operations::replace: { + result.at(ptr) = get_value("replace", "value", false); + break; + } + + case patch_operations::move: { + const std::string from_path = get_value("move", "from", true); + json_pointer from_ptr(from_path); + + basic_json v = result.at(from_ptr); + + operation_remove(from_ptr); + operation_add(ptr, v); + break; + } + + case patch_operations::copy: { + const std::string from_path = get_value("copy", "from", true); + const json_pointer from_ptr(from_path); + + basic_json v = result.at(from_ptr); + + operation_add(ptr, v); + break; + } + + case patch_operations::test: { + bool success = false; + JSON_TRY { + success = (result.at(ptr) == get_value("test", "value", false)); + } + JSON_CATCH (out_of_range &) { + } + + if(JSON_UNLIKELY(not success)) { + JSON_THROW(other_error::create(501, "unsuccessful: "+val.dump())); + } + + break; + } + + case patch_operations::invalid: { + JSON_THROW(parse_error::create(105, 0, "operation value '"+op+"' is invalid")); + } + } + } + + return result; + } + + static basic_json diff(const basic_json &source, const basic_json &target, + const std::string &path = "") { + basic_json result(value_t::array); + + if(source == target) { + return result; + } + + if(source.type() != target.type()) { + result.push_back( + { + {"op", "replace"}, + {"path", path}, + {"value", target} + }); + } else { + switch(source.type()) { + case value_t::array: { + std::size_t i = 0; + while(i < source.size() and i < target.size()) { + auto temp_diff = diff(source[i], target[i], path+"/"+std::to_string(i)); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + ++i; + } + + const auto end_index = static_cast<difference_type>(result.size()); + while(i < source.size()) { + result.insert(result.begin()+end_index, object( + { + {"op", "remove"}, + {"path", path+"/"+std::to_string(i)} + })); + ++i; + } + + while(i < target.size()) { + result.push_back( + { + {"op", "add"}, + {"path", path+"/"+std::to_string(i)}, + {"value", target[i]} + }); + ++i; + } + + break; + } + + case value_t::object: { + for(auto it = source.cbegin(); it != source.cend(); ++it) { + const auto key = json_pointer::escape(it.key()); + + if(target.find(it.key()) != target.end()) { + auto temp_diff = diff(it.value(), target[it.key()], path+"/"+key); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + } else { + result.push_back(object( + { + {"op", "remove"}, + {"path", path+"/"+key} + })); + } + } + + for(auto it = target.cbegin(); it != target.cend(); ++it) { + if(source.find(it.key()) == source.end()) { + const auto key = json_pointer::escape(it.key()); + result.push_back( + { + {"op", "add"}, + {"path", path+"/"+key}, + {"value", it.value()} + }); + } + } + + break; + } + + default: { + result.push_back( + { + {"op", "replace"}, + {"path", path}, + {"value", target} + }); + break; + } + } + } + + return result; + } + + void merge_patch(const basic_json &patch) { + if(patch.is_object()) { + if(not is_object()) { + *this = object(); + } + for(auto it = patch.begin(); it != patch.end(); ++it) { + if(it.value().is_null()) { + erase(it.key()); + } else { + operator[](it.key()).merge_patch(it.value()); + } + } + } else { + *this = patch; + } + } + +}; +} + +namespace std { +template<> +inline void swap(nlohmann::json &j1, + nlohmann::json &j2) noexcept( +is_nothrow_move_constructible<nlohmann::json>::value and +is_nothrow_move_assignable<nlohmann::json>::value +) { + j1.swap(j2); +} + +template<> +struct hash<nlohmann::json> { + std::size_t operator()(const nlohmann::json &j) const { + const auto &h = hash<nlohmann::json::string_t>(); + return h(j.dump()); + } +}; + +template<> +struct less<::nlohmann::detail::value_t> { + bool operator()(nlohmann::detail::value_t lhs, + nlohmann::detail::value_t rhs) const noexcept { + return nlohmann::detail::operator<(lhs, rhs); + } +}; + +} + +inline nlohmann::json operator "" _json(const char *s, std::size_t n) { + return nlohmann::json::parse(s, s+n); +} + +inline nlohmann::json::json_pointer operator "" _json_pointer(const char *s, std::size_t n) { + return nlohmann::json::json_pointer(std::string(s, n)); +} + +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) +#pragma GCC diagnostic pop +#endif +#if defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#undef JSON_CATCH +#undef JSON_THROW +#undef JSON_TRY +#undef JSON_LIKELY +#undef JSON_UNLIKELY +#undef JSON_HAS_CPP_14 +#undef JSON_HAS_CPP_17 +#undef NLOHMANN_BASIC_JSON_TPL_DECLARATION +#undef NLOHMANN_BASIC_JSON_TPL +#undef NLOHMANN_JSON_HAS_HELPER diff --git a/src/rapidxml/rapidxml.h b/3rd_party/rapidxml.h similarity index 100% rename from src/rapidxml/rapidxml.h rename to 3rd_party/rapidxml.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 74cd0602..437b9b4d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,6 +78,7 @@ include_directories( ${LIBCLANG_INCLUDE_DIRS} ${ASPELL_INCLUDE_DIR} ${LIBGIT2_INCLUDE_DIRS} + 3rd_party ) add_subdirectory("src") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b84b87e9..c2bd858c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,64 +3,64 @@ file(GLOB JUCI_SRC_BUILDSYSTEMS cmake.h cmake.cc meson.h meson.cc makefile_base.h makefile_base.cc) set(JUCI_SHARED_FILES - ${JUCI_SRC_BUILDSYSTEMS} - autocomplete.cc - compile_commands.cc - ctags.cc - dispatcher.cc - documentation_cppreference.cc - filesystem.cc - git.cc - menu.cc - project_build.cc - source.cc - source_base.cc - source_clang.cc - source_diff.cc - source_language_protocol.cc - source_spellcheck.cc - terminal.cc - usages_clang.cc - ) + ${JUCI_SRC_BUILDSYSTEMS} + autocomplete.cc + compile_commands.cc + ctags.cc + dispatcher.cc + documentation_cppreference.cc + filesystem.cc + git.cc + menu.cc + project_build.cc + source.cc + source_base.cc + source_clang.cc + source_diff.cc + source_language_protocol.cc + source_spellcheck.cc + terminal.cc + usages_clang.cc + ) if (LIBLLDB_FOUND) - list(APPEND JUCI_SHARED_FILES debug_lldb.cc) + list(APPEND JUCI_SHARED_FILES debug_lldb.cc) endif () add_library(juci_shared STATIC ${JUCI_SHARED_FILES}) target_link_libraries(juci_shared - ${GTKMM_LIBRARIES} - ${GTKSVMM_LIBRARIES} - ${Boost_LIBRARIES} - ${LIBLLDB_LIBRARIES} - ${ASPELL_LIBRARIES} - ${LIBGIT2_LIBRARIES} - clangmm - tiny-process-library - ) + ${GTKMM_LIBRARIES} + ${GTKSVMM_LIBRARIES} + ${Boost_LIBRARIES} + ${LIBLLDB_LIBRARIES} + ${ASPELL_LIBRARIES} + ${LIBGIT2_LIBRARIES} + clangmm + tiny-process-library + ) add_executable(juci - config.cc - dialogs.cc - dialogs_unix.cc - directories.cc - entrybox.cc - info.cc - juci.cc - notebook.cc - project.cc - selection_dialog.cc - tooltips.cc - window.cc - ) + config.cc + dialogs.cc + dialogs_unix.cc + directories.cc + entrybox.cc + info.cc + juci.cc + notebook.cc + project.cc + selection_dialog.cc + tooltips.cc + window.cc + ) target_link_libraries(juci juci_shared) install(TARGETS juci RUNTIME DESTINATION bin) if (${CMAKE_SYSTEM_NAME} MATCHES Linux|.*BSD|DragonFly) - install(FILES "${CMAKE_SOURCE_DIR}/share/juci.desktop" - DESTINATION "${CMAKE_INSTALL_PREFIX}/share/applications") - install(FILES "${CMAKE_SOURCE_DIR}/share/juci.svg" - DESTINATION "${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/scalable/apps") + install(FILES "${CMAKE_SOURCE_DIR}/share/juci.desktop" + DESTINATION "${CMAKE_INSTALL_PREFIX}/share/applications") + install(FILES "${CMAKE_SOURCE_DIR}/share/juci.svg" + DESTINATION "${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/scalable/apps") elseif (APPLE) - install(CODE "execute_process(COMMAND /usr/bin/python ${CMAKE_SOURCE_DIR}/share/set_icon_macos.py ${CMAKE_SOURCE_DIR}/share/juci.png ${CMAKE_INSTALL_PREFIX}/bin/juci)") + install(CODE "execute_process(COMMAND /usr/bin/python ${CMAKE_SOURCE_DIR}/share/set_icon_macos.py ${CMAKE_SOURCE_DIR}/share/juci.png ${CMAKE_INSTALL_PREFIX}/bin/juci)") endif () # add a target to generate API documentation with Doxygen @@ -68,10 +68,10 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake_modules/") find_package(Plantuml) find_package(Doxygen) if (DOXYGEN_FOUND) - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY) - add_custom_target(doc - ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "Generating API documentation with Doxygen to ${CMAKE_CURRENT_BINARY_DIR}" VERBATIM - ) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY) + add_custom_target(doc + ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating API documentation with Doxygen to ${CMAKE_CURRENT_BINARY_DIR}" VERBATIM + ) endif (DOXYGEN_FOUND) diff --git a/src/cmake.cc b/src/cmake.cc index 118c3488..252f2586 100644 --- a/src/cmake.cc +++ b/src/cmake.cc @@ -1,57 +1,32 @@ -#include "cmake.h" +#include "project_build.h" +#include "makefile_base.h" #include "filesystem.h" -#include "dialogs.h" #include "config.h" -#include "terminal.h" -#include "compile_commands.h" -#include "rapidxml/rapidxml.h" +#include "rapidxml.h" namespace { - enum class Configuration { - Debug, Release - }; - - class CMakeProjectUpdater { + class CMakeProjectUpdater : public MakefileProjectUpdater { public: - CMakeProjectUpdater(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path) : - m_ref_project_path(project_path), m_ref_build_path(build_path) {} + using MakefileProjectUpdater::MakefileProjectUpdater; - CMakeProjectUpdater &set_configuration(Configuration conf) noexcept { - m_configuration = conf; - return *this; + private: + std::string get_extra_arguments() const { + return m_configuration == Configuration::Debug ? " -DCMAKE_BUILD_TYPE=Debug" : " -DCMAKE_BUILD_TYPE=Release"; } - void do_update(bool force) { - check_arguments(); - boost::filesystem::create_directory(m_ref_build_path); - if(!force && boost::filesystem::exists(m_ref_build_path / "CMakeCache.txt")) return; - auto compile_commands_path = m_ref_build_path / "compile_commands.json"; - Dialog::Message message("Creating/updating build"); - auto exit_status = Terminal::get().process(Config::get().project.cmake.command+' '+ - filesystem::escape_argument(m_ref_project_path.string())+ - " -DCMAKE_EXPORT_COMPILE_COMMANDS=ON"+get_extra_arguments(), - m_ref_build_path); - if(exit_status == EXIT_SUCCESS) -#ifdef _WIN32 - applyMsys2Patch(compile_commands_path); -#endif - message.hide(); + std::string get_updater_arguments() const override { + return Config::get().project.cmake.command+' '+ filesystem::escape_argument(m_ref_project_path.string()) + + " -DCMAKE_EXPORT_COMPILE_COMMANDS=ON"+ get_extra_arguments(); } - private: - void check_arguments() { - if(m_ref_project_path.empty() || m_ref_build_path.empty()) - throw std::runtime_error("Arguments Incomplete"); - if(!boost::filesystem::exists(m_ref_project_path / "CMakeLists.txt")) - throw std::runtime_error("CMake Project Not Found"); - } + const char *get_project_name() const noexcept override { return "CMakeLists.txt"; }; - std::string get_extra_arguments() const { - return m_configuration == Configuration::Debug ? " -DCMAKE_BUILD_TYPE=Debug" : " -DCMAKE_BUILD_TYPE=Release"; - } + const char *get_cache_identifier_name() const noexcept override { return "CMakeCache.txt"; } - //Temporary fix to MSYS2's libclang - void applyMsys2Patch(const boost::filesystem::path &compile_commands_path) const { + void on_success() override { + //Temporary fix to MSYS2's libclang +#ifdef _WIN32 + auto compile_commands_path = m_ref_build_path / "compile_commands.json"; auto compile_commands_file = filesystem::read(compile_commands_path); const std::array<const std::string, 2> drives = {"-I", "-isystem"}; for(const auto &target: drives) { @@ -65,37 +40,21 @@ namespace { } } filesystem::write(compile_commands_path, compile_commands_file); +#endif } - - Configuration m_configuration; - const boost::filesystem::path &m_ref_project_path; - const boost::filesystem::path &m_ref_build_path; }; } -CMake::CMake(const boost::filesystem::path &path) : MakefileBase(path, "CMakeLists.txt") {} +Project::CMakeBuild::CMakeBuild(const boost::filesystem::path &path) : Build(path, "CMakeLists.txt") {} -bool CMake::update_default_build(const boost::filesystem::path &default_build_path, bool force) { - try { - CMakeProjectUpdater(get_project_path(), default_build_path).set_configuration(Configuration::Release).do_update( - force); - } - catch(std::exception &e) { - Terminal::get().print("Configuration For: "+get_project_path().string()+" Failed With: "+e.what()+'\n'); - return false; - } - return true; +bool Project::CMakeBuild::update_default(bool force) { + return CMakeProjectUpdater(get_project_path(), get_default_path()). + do_update(MakefileProjectUpdater::Configuration::Release, force); } -bool CMake::update_debug_build(const boost::filesystem::path &debug_build_path, bool force) { - try { - CMakeProjectUpdater(get_project_path(), debug_build_path).set_configuration(Configuration::Debug).do_update(force); - } - catch(std::exception &e) { - Terminal::get().print("Configuration For: "+get_project_path().string()+" Failed With: "+e.what()+'\n'); - return false; - } - return true; +bool Project::CMakeBuild::update_debug(bool force) { + return CMakeProjectUpdater(get_project_path(), get_debug_path()). + do_update(MakefileProjectUpdater::Configuration::Debug, force); } namespace { @@ -114,15 +73,8 @@ namespace { return ""; } - static std::string file_reader(const boost::filesystem::path &build_path) { - std::stringstream ss; - std::ifstream inf(build_path.string()); - ss << inf.rdbuf(); - return ss.str(); - } - void parse_project(const boost::filesystem::path &build_path) { - const auto project_file = file_reader(find_project_file(build_path)); + const auto project_file = filesystem::read(find_project_file(build_path)); document.parse<0>(const_cast<char *>(project_file.c_str())); } @@ -162,9 +114,10 @@ namespace { } -boost::filesystem::path -CMake::get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &) { - GetExecutableHelper helper(build_path); +boost::filesystem::path Project::CMakeBuild::get_executable(const boost::filesystem::path &) { + GetExecutableHelper helper(get_default_path()); auto &exec = helper.get_executables(); return exec.empty() ? "" : exec[0]; } + +std::string Project::CMakeBuild::get_compile_command() { return Config::get().project.cmake.compile_command; } diff --git a/src/cmake.h b/src/cmake.h deleted file mode 100644 index 1fa22ce9..00000000 --- a/src/cmake.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include "makefile_base.h" - -class CMake : public MakefileBase { -public: - explicit CMake(const boost::filesystem::path &path); - - bool update_default_build(const boost::filesystem::path &default_build_path, bool force) override; - - bool update_debug_build(const boost::filesystem::path &debug_build_path, bool force) override; - - boost::filesystem::path - get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) override; -}; diff --git a/src/compile_commands.cc b/src/compile_commands.cc index 07c3d221..412f1338 100644 --- a/src/compile_commands.cc +++ b/src/compile_commands.cc @@ -1,7 +1,8 @@ #include "compile_commands.h" #include "clangmm.h" -#include <boost/property_tree/json_parser.hpp> +#include <fstream> #include <regex> +#include <json.h> std::vector<std::string> CompileCommands::Command::parameter_values(const std::string ¶meter_name) const { std::vector<std::string> parameter_values; @@ -21,65 +22,68 @@ std::vector<std::string> CompileCommands::Command::parameter_values(const std::s CompileCommands::CompileCommands(const boost::filesystem::path &build_path) { try { - boost::property_tree::ptree root_pt; - boost::property_tree::json_parser::read_json((build_path/"compile_commands.json").string(), root_pt); - - auto commands_pt=root_pt.get_child(""); - for(auto &command: commands_pt) { - boost::filesystem::path directory=command.second.get<std::string>("directory"); - auto parameters_str=command.second.get<std::string>("command"); - boost::filesystem::path file=command.second.get<std::string>("file"); - - std::vector<std::string> parameters; - bool backslash=false; - bool single_quote=false; - bool double_quote=false; - size_t parameter_start_pos=std::string::npos; - size_t parameter_size=0; - auto add_parameter=[¶meters, ¶meters_str, ¶meter_start_pos, ¶meter_size] { - auto parameter=parameters_str.substr(parameter_start_pos, parameter_size); - // Remove escaping - for(size_t c=0;c<parameter.size()-1;++c) { - if(parameter[c]=='\\') - parameter.replace(c, 2, std::string()+parameter[c+1]); - } - parameters.emplace_back(parameter); - }; - for(size_t c=0;c<parameters_str.size();++c) { - if(backslash) - backslash=false; - else if(parameters_str[c]=='\\') - backslash=true; - else if((parameters_str[c]==' ' || parameters_str[c]=='\t') && !backslash && !single_quote && !double_quote) { - if(parameter_start_pos!=std::string::npos) { - add_parameter(); - parameter_start_pos=std::string::npos; - parameter_size=0; - } - continue; - } - else if(parameters_str[c]=='\'' && !backslash && !double_quote) { - single_quote=!single_quote; - continue; - } - else if(parameters_str[c]=='\"' && !backslash && !single_quote) { - double_quote=!double_quote; - continue; - } + std::ifstream commands_file((build_path/"compile_commands.json").string()); + nlohmann::json commands_pt; + commands_file >> commands_pt; - if(parameter_start_pos==std::string::npos) - parameter_start_pos=c; - ++parameter_size; - } - if(parameter_start_pos!=std::string::npos) - add_parameter(); + for(auto& command: commands_pt) { + boost::filesystem::path directory=command["directory"].get<std::string>(); + boost::filesystem::path file=command["file"].get<std::string>(); - commands.emplace_back(Command{directory, parameters, boost::filesystem::absolute(file, build_path)}); + commands.emplace_back( + Command{directory, parse_command(command["command"]), boost::filesystem::absolute(file, build_path)}); } } catch(...) {} } +std::vector<std::string> CompileCommands::parse_command(const std::string& parameters_str) const { + std::vector<std::string> parameters; + bool backslash=false; + bool single_quote=false; + bool double_quote=false; + size_t parameter_start_pos= std::string::npos; + size_t parameter_size=0; + auto add_parameter=[¶meters, ¶meters_str, ¶meter_start_pos, ¶meter_size] { + auto parameter=parameters_str.substr(parameter_start_pos, parameter_size); + // Remove escaping + for(size_t c=0;c<parameter.size()-1;++c) { + if(parameter[c]=='\\') + parameter.replace(c, 2, std::string()+parameter[c+1]); + } + parameters.emplace_back(parameter); + }; + for(size_t c=0;c<parameters_str.size();++c) { + if(backslash) + backslash=false; + else if(parameters_str[c]=='\\') + backslash=true; + else if((parameters_str[c] == ' ' || parameters_str[c] == '\t') && !single_quote && !double_quote) { + if(parameter_start_pos != std::string::npos) { + add_parameter(); + parameter_start_pos= std::string::npos; + parameter_size=0; + } + continue; + } + else if(parameters_str[c] == '\'' && !double_quote) { + single_quote=!single_quote; + continue; + } + else if(parameters_str[c] == '\"' && !single_quote) { + double_quote=!double_quote; + continue; + } + + if(parameter_start_pos == std::string::npos) + parameter_start_pos=c; + ++parameter_size; + } + if(parameter_start_pos != std::string::npos) + add_parameter(); + return parameters; +} + std::vector<std::string> CompileCommands::get_arguments(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) { std::string default_std_argument="-std=c++1y"; diff --git a/src/compile_commands.h b/src/compile_commands.h index c7bb0f4a..b18a1cbc 100644 --- a/src/compile_commands.h +++ b/src/compile_commands.h @@ -15,11 +15,13 @@ class CompileCommands { std::vector<std::string> parameter_values(const std::string ¶meter_name) const; }; - CompileCommands(const boost::filesystem::path &build_path); + explicit CompileCommands(const boost::filesystem::path &build_path); std::vector<Command> commands; /// Return arguments for the given file using libclangmm static std::vector<std::string> get_arguments(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path); + + std::vector<std::string> parse_command(const std::string& parameters_str) const; }; diff --git a/src/makefile_base.cc b/src/makefile_base.cc index a73a76b4..5a6d232b 100644 --- a/src/makefile_base.cc +++ b/src/makefile_base.cc @@ -1,49 +1,37 @@ -#include <regex> -#include "meson.h" #include "makefile_base.h" #include "terminal.h" -#include "filesystem.h" +#include "dialogs.h" -bool MakefileBase::create_build_directory(const boost::filesystem::path &build_path) { - if (!boost::filesystem::exists(build_path)) { - boost::system::error_code ec; - boost::filesystem::create_directories(build_path, ec); - if (ec) { - Terminal::get().print("Error: could not create " + build_path.string() + ": " + ec.message() + "\n", - true); - return false; +MakefileProjectUpdater::MakefileProjectUpdater(const boost::filesystem::path &project_path, + const boost::filesystem::path &build_path) : + m_configuration(Configuration::Release), m_ref_project_path(project_path), m_ref_build_path(build_path) {} + +bool MakefileProjectUpdater::do_update(Configuration conf, bool force) noexcept { + m_configuration = conf; + try { + check_arguments(); + boost::filesystem::create_directory(m_ref_build_path); + if(force || !is_generated()) { + Dialog::Message message("Creating/updating build"); + if(Terminal::get().process(get_updater_arguments(), m_ref_build_path) == EXIT_SUCCESS) + on_success(); + message.hide(); } } + catch(std::exception &e) { + Terminal::get().print("Configuration For: "+m_ref_project_path.string()+" Failed With: "+e.what()+'\n'); + return false; + } return true; } -namespace { - bool find_project_impl(const boost::filesystem::path &file_path) { - for (auto &line: filesystem::read_lines(file_path)) { - const static std::regex project_regex("^ *project *\\(.*\\r?$", std::regex::icase); - std::smatch sm; - if (std::regex_match(line, sm, project_regex)) - return true; - } - return false; - } +bool MakefileProjectUpdater::is_generated() const { return exists(m_ref_build_path / get_cache_identifier_name()); } - boost::filesystem::path find_project(const boost::filesystem::path &path, const std::string &filename) { - auto search_path = is_directory(path) ? path : path.parent_path(); - while (true) { - auto search_file = search_path / filename; - if (exists(search_file)) { - if (find_project_impl(search_file)) { - return search_path; - } - } - if (search_path == search_path.root_directory()) - break; - search_path = search_path.parent_path(); - } - return ""; - } -} +void MakefileProjectUpdater::on_success() {} -MakefileBase::MakefileBase(const boost::filesystem::path &path, const std::string &filename) : project_path( - find_project(path, filename)) {} +void MakefileProjectUpdater::check_arguments() const { + if(m_ref_project_path.empty() || m_ref_build_path.empty()) + throw std::runtime_error("Arguments Incomplete"); + if(!boost::filesystem::exists(m_ref_project_path / get_project_name())) + throw std::runtime_error("Project Not Found"); +} diff --git a/src/makefile_base.h b/src/makefile_base.h index d2493ab6..d6283280 100644 --- a/src/makefile_base.h +++ b/src/makefile_base.h @@ -2,24 +2,24 @@ #include <boost/filesystem.hpp> -class MakefileBase { +class MakefileProjectUpdater { public: - MakefileBase(const boost::filesystem::path &path, const std::string &filename); + enum class Configuration { + Debug, Release + }; - virtual ~MakefileBase() = default; - - auto &get_project_path() const { return project_path; } - - virtual bool update_default_build(const boost::filesystem::path &default_build_path, bool force) = 0; - - virtual bool update_debug_build(const boost::filesystem::path &debug_build_path, bool force) = 0; - - virtual boost::filesystem::path - get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) = 0; + MakefileProjectUpdater(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path); + bool do_update(Configuration conf, bool force) noexcept; protected: - static bool create_build_directory(const boost::filesystem::path &build_path); - -private: - const boost::filesystem::path project_path; + virtual std::string get_updater_arguments() const = 0; + virtual const char* get_project_name() const noexcept = 0; + virtual const char* get_cache_identifier_name() const noexcept = 0; + virtual void on_success(); + void check_arguments() const; + bool is_generated() const; + + Configuration m_configuration; + const boost::filesystem::path &m_ref_project_path; + const boost::filesystem::path &m_ref_build_path; }; diff --git a/src/meson.cc b/src/meson.cc index 30485f12..2dff9f7b 100644 --- a/src/meson.cc +++ b/src/meson.cc @@ -1,56 +1,44 @@ -#include "meson.h" +#include "project_build.h" +#include "makefile_base.h" #include "filesystem.h" #include "compile_commands.h" -#include <regex> -#include "terminal.h" -#include "dialogs.h" #include "config.h" -Meson::Meson(const boost::filesystem::path &path) : MakefileBase(path, "meson.build") {} +namespace { + class MesonProjectUpdater : public MakefileProjectUpdater { + public: + using MakefileProjectUpdater::MakefileProjectUpdater; -bool Meson::update_default_build(const boost::filesystem::path &default_build_path, bool force) { - if (get_project_path().empty() || !boost::filesystem::exists(get_project_path() / "meson.build") || - default_build_path.empty()) - return false; + private: + std::string get_extra_arguments() const { + return m_configuration == Configuration::Debug ? "--buildtype debug " : ""; + } - if (!create_build_directory(default_build_path)) return false; + std::string get_updater_arguments() const override { + return Config::get().project.meson.command+' '+(is_generated()?"--internal regenerate ":"")+ + get_extra_arguments()+filesystem::escape_argument(m_ref_project_path.string()); + } - auto compile_commands_path = default_build_path / "compile_commands.json"; - bool compile_commands_exists = boost::filesystem::exists(compile_commands_path); - if (!force && compile_commands_exists) - return true; + const char *get_project_name() const noexcept override { return "meson.build"; }; - Dialog::Message message("Creating/updating default build"); - auto exit_status = Terminal::get().process( - Config::get().project.meson.command + ' ' + (compile_commands_exists ? "--internal regenerate " : "") + - filesystem::escape_argument(get_project_path().string()), default_build_path); - message.hide(); - if (exit_status == EXIT_SUCCESS) - return true; - return false; + const char *get_cache_identifier_name() const noexcept override { return "compile_commands.json"; } + }; } -bool Meson::update_debug_build(const boost::filesystem::path &debug_build_path, bool force) { - if (get_project_path().empty() || !boost::filesystem::exists(get_project_path() / "meson.build") || - debug_build_path.empty()) - return false; - - if (!create_build_directory(debug_build_path)) return false; +Project::MesonBuild::MesonBuild(const boost::filesystem::path &path) : Build(path, "meson.build") {} - bool compile_commands_exists=boost::filesystem::exists(debug_build_path/"compile_commands.json"); - if(!force && compile_commands_exists) - return true; +bool Project::MesonBuild::update_default(bool force) { + return MesonProjectUpdater(get_project_path(), get_default_path()). + do_update(MakefileProjectUpdater::Configuration::Release, force); +} - Dialog::Message message("Creating/updating debug build"); - auto exit_status=Terminal::get().process(Config::get().project.meson.command+' '+(compile_commands_exists?"--internal regenerate ":"")+ - "--buildtype debug "+filesystem::escape_argument(get_project_path().string()), debug_build_path); - message.hide(); - if(exit_status==EXIT_SUCCESS) - return true; - return false; +bool Project::MesonBuild::update_debug(bool force) { + return MesonProjectUpdater(get_project_path(), get_debug_path()). + do_update(MakefileProjectUpdater::Configuration::Debug, force); } -boost::filesystem::path Meson::get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) { +boost::filesystem::path Project::MesonBuild::get_executable(const boost::filesystem::path &file_path) { + auto build_path = get_default_path(); CompileCommands compile_commands(build_path); size_t best_match_size=-1; @@ -80,3 +68,5 @@ boost::filesystem::path Meson::get_executable(const boost::filesystem::path &bui return best_match_executable; } + +std::string Project::MesonBuild::get_compile_command() { return Config::get().project.meson.compile_command; } diff --git a/src/meson.h b/src/meson.h deleted file mode 100644 index 43c5b1c4..00000000 --- a/src/meson.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include "makefile_base.h" - -class Meson : public MakefileBase { -public: - Meson(const boost::filesystem::path &path); - - bool update_default_build(const boost::filesystem::path &default_build_path, bool force) override; - - bool update_debug_build(const boost::filesystem::path &debug_build_path, bool force) override; - - boost::filesystem::path - get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) override; - -}; diff --git a/src/project_build.cc b/src/project_build.cc index 3398086a..58939de6 100644 --- a/src/project_build.cc +++ b/src/project_build.cc @@ -1,6 +1,7 @@ #include "project_build.h" #include "config.h" #include "filesystem.h" +#include <regex> std::unique_ptr<Project::Build> Project::Build::create(const boost::filesystem::path &path) { auto search_path = boost::filesystem::is_directory(path) ? path : path.parent_path(); @@ -81,42 +82,33 @@ boost::filesystem::path Project::Build::get_debug_path() { return filesystem::get_normal_path(debug_build_path); } -Project::CMakeBuild::CMakeBuild(const boost::filesystem::path &path) : Project::Build(), cmake(path) { - project_path = cmake.get_project_path(); -} - -bool Project::CMakeBuild::update_default(bool force) { - return cmake.update_default_build(get_default_path(), force); -} - -bool Project::CMakeBuild::update_debug(bool force) { - return cmake.update_debug_build(get_debug_path(), force); -} - -std::string Project::CMakeBuild::get_compile_command() { - return Config::get().project.cmake.compile_command; -} - -boost::filesystem::path Project::CMakeBuild::get_executable(const boost::filesystem::path &path) { - return cmake.get_executable(get_default_path(), path).string(); -} - -Project::MesonBuild::MesonBuild(const boost::filesystem::path &path) : Project::Build(), meson(path) { - project_path = meson.get_project_path(); -} - -bool Project::MesonBuild::update_default(bool force) { - return meson.update_default_build(get_default_path(), force); -} - -bool Project::MesonBuild::update_debug(bool force) { - return meson.update_debug_build(get_debug_path(), force); -} +namespace { + bool find_project_impl(const boost::filesystem::path &file_path) { + for (auto &line: filesystem::read_lines(file_path)) { + const static std::regex project_regex("^ *project *\\(.*\\r?$", std::regex::icase); + std::smatch sm; + if (std::regex_match(line, sm, project_regex)) + return true; + } + return false; + } -std::string Project::MesonBuild::get_compile_command() { - return Config::get().project.meson.compile_command; + boost::filesystem::path find_project(const boost::filesystem::path &path, const std::string &filename) { + auto search_path = is_directory(path) ? path : path.parent_path(); + while (true) { + auto search_file = search_path / filename; + if (exists(search_file)) { + if (find_project_impl(search_file)) { + return search_path; + } + } + if (search_path == search_path.root_directory()) + break; + search_path = search_path.parent_path(); + } + return ""; + } } -boost::filesystem::path Project::MesonBuild::get_executable(const boost::filesystem::path &path) { - return meson.get_executable(get_default_path(), path); -} +Project::Build::Build(const boost::filesystem::path &path, const std::string &filename) : + project_path(find_project(path, filename)) {} diff --git a/src/project_build.h b/src/project_build.h index 43c23d6d..245914bb 100644 --- a/src/project_build.h +++ b/src/project_build.h @@ -1,12 +1,12 @@ #pragma once #include <boost/filesystem.hpp> -#include "cmake.h" -#include "meson.h" namespace Project { class Build { public: - virtual ~Build() {} + Build() = default; + explicit Build(const boost::filesystem::path &path, const std::string &filename); + virtual ~Build() = default; virtual boost::filesystem::path get_default_path(); virtual bool update_default(bool force=false) {return false;} @@ -24,12 +24,10 @@ namespace Project { boost::filesystem::path project_path; }; - class CMakeBuild : public Build { - ::CMake cmake; public: - CMakeBuild(const boost::filesystem::path &path); - + explicit CMakeBuild(const boost::filesystem::path &path); + bool update_default(bool force) override; bool update_debug(bool force) override; @@ -40,9 +38,8 @@ namespace Project { }; class MesonBuild : public Build { - Meson meson; public: - MesonBuild(const boost::filesystem::path &path); + explicit MesonBuild(const boost::filesystem::path &path); bool update_default(bool force) override; From 5ad82df509ba49f8631279f56751dc901f82cc1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=88=E9=A1=BA=20=E5=88=98?= <yshliu0321@icloud.com> Date: Sat, 26 May 2018 11:31:18 +0800 Subject: [PATCH 12/12] Minoring Code --- 3rd_party/json.h | 12438 +++++++++++++++++++-------------------------- src/cmake.cc | 1 + src/config.cc | 309 +- src/config.h | 21 - src/window.cc | 1 + 5 files changed, 5415 insertions(+), 7355 deletions(-) diff --git a/3rd_party/json.h b/3rd_party/json.h index b631877e..00537155 100644 --- a/3rd_party/json.h +++ b/3rd_party/json.h @@ -53,37 +53,37 @@ SOFTWARE. #include <vector> namespace nlohmann { -template<typename = void, typename = void> -struct adl_serializer; - -template<template<typename U, typename V, typename... Args> class ObjectType = -std::map, - template<typename U, typename... Args> class ArrayType = std::vector, - class StringType = std::string, class BooleanType = bool, - class NumberIntegerType = std::int64_t, - class NumberUnsignedType = std::uint64_t, - class NumberFloatType = double, - template<typename U> class AllocatorType = std::allocator, - template<typename T, typename SFINAE = void> class JSONSerializer = - adl_serializer> -class basic_json; - -template<typename BasicJsonType> -class json_pointer; - -using json = basic_json<>; + template<typename = void, typename = void> + struct adl_serializer; + + template<template<typename U, typename V, typename... Args> class ObjectType = + std::map, + template<typename U, typename... Args> class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template<typename U> class AllocatorType = std::allocator, + template<typename T, typename SFINAE = void> class JSONSerializer = + adl_serializer> + class basic_json; + + template<typename BasicJsonType> + class json_pointer; + + using json = basic_json<>; } #endif #if defined(__clang__) -#if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 - #error "unsupported Clang version" - #endif + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version" + #endif #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) -#if (__GNUC__ * 10000+__GNUC_MINOR__ * 100+__GNUC_PATCHLEVEL__) < 40900 -#error "unsupported GCC version" -#endif + #if (__GNUC__ * 10000+__GNUC_MINOR__ * 100+__GNUC_PATCHLEVEL__) < 40900 + #error "unsupported GCC version" + #endif #endif #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) @@ -166,6410 +166,4560 @@ using json = basic_json<>; #include <type_traits> namespace nlohmann { -namespace detail { -template<typename> -struct is_basic_json : std::false_type { -}; + namespace detail { + template<typename> + struct is_basic_json : std::false_type { + }; -NLOHMANN_BASIC_JSON_TPL_DECLARATION -struct is_basic_json<NLOHMANN_BASIC_JSON_TPL > : std::true_type { -}; + NLOHMANN_BASIC_JSON_TPL_DECLARATION + struct is_basic_json<NLOHMANN_BASIC_JSON_TPL > : std::true_type { + }; -template<bool B, typename T = void> -using enable_if_t = typename std::enable_if<B, T>::type; + template<bool B, typename T = void> + using enable_if_t = typename std::enable_if<B, T>::type; -template<typename T> -using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type; + template<typename T> + using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type; -template<std::size_t... Ints> -struct index_sequence { - using type = index_sequence; - using value_type = std::size_t; + template<std::size_t... Ints> + struct index_sequence { + using type = index_sequence; + using value_type = std::size_t; - static constexpr std::size_t size() noexcept { - return sizeof...(Ints); - } -}; - -template<class Sequence1, class Sequence2> -struct merge_and_renumber; - -template<std::size_t... I1, std::size_t... I2> -struct merge_and_renumber<index_sequence<I1...>, index_sequence<I2...>> - : index_sequence<I1..., (sizeof...(I1)+I2)...> { -}; - -template<std::size_t N> -struct make_index_sequence - : merge_and_renumber<typename make_index_sequence<N / 2>::type, - typename make_index_sequence<N-N / 2>::type> { -}; - -template<> -struct make_index_sequence<0> : index_sequence<> { -}; -template<> -struct make_index_sequence<1> : index_sequence<0> { -}; - -template<typename... Ts> -using index_sequence_for = make_index_sequence<sizeof...(Ts)>; - -template<class...> -struct conjunction : std::true_type { -}; -template<class B1> -struct conjunction<B1> : B1 { -}; -template<class B1, class... Bn> -struct conjunction<B1, Bn...> : std::conditional<bool(B1::value), conjunction<Bn...>, B1>::type { -}; - -template<class B> -struct negation : std::integral_constant<bool, not B::value> { -}; - -template<unsigned N> -struct priority_tag : priority_tag<N-1> { -}; -template<> -struct priority_tag<0> { -}; - -template<typename T, typename = void> -struct is_complete_type : std::false_type { -}; - -template<typename T> -struct is_complete_type<T, decltype(void(sizeof(T)))> : std::true_type { -}; - -NLOHMANN_JSON_HAS_HELPER(mapped_type); - -NLOHMANN_JSON_HAS_HELPER(key_type); - -NLOHMANN_JSON_HAS_HELPER(value_type); - -NLOHMANN_JSON_HAS_HELPER(iterator); - -template<bool B, class RealType, class CompatibleObjectType> -struct is_compatible_object_type_impl : std::false_type { -}; - -template<class RealType, class CompatibleObjectType> -struct is_compatible_object_type_impl<true, RealType, CompatibleObjectType> { - static constexpr auto value = - std::is_constructible<typename RealType::key_type, typename CompatibleObjectType::key_type>::value and - std::is_constructible<typename RealType::mapped_type, typename CompatibleObjectType::mapped_type>::value; -}; - -template<class BasicJsonType, class CompatibleObjectType> -struct is_compatible_object_type { - static auto constexpr value = is_compatible_object_type_impl< - conjunction<negation<std::is_same<void, CompatibleObjectType>>, - has_mapped_type<CompatibleObjectType>, - has_key_type<CompatibleObjectType>>::value, - typename BasicJsonType::object_t, CompatibleObjectType>::value; -}; - -template<typename BasicJsonType, typename T> -struct is_basic_json_nested_type { - static auto constexpr value = std::is_same<T, typename BasicJsonType::iterator>::value or - std::is_same<T, typename BasicJsonType::const_iterator>::value or - std::is_same<T, typename BasicJsonType::reverse_iterator>::value or - std::is_same<T, typename BasicJsonType::const_reverse_iterator>::value; -}; - -template<class BasicJsonType, class CompatibleArrayType> -struct is_compatible_array_type { - static auto constexpr value = - conjunction<negation<std::is_same<void, CompatibleArrayType>>, - negation<is_compatible_object_type< - BasicJsonType, CompatibleArrayType>>, - negation<std::is_constructible<typename BasicJsonType::string_t, - CompatibleArrayType>>, - negation<is_basic_json_nested_type<BasicJsonType, CompatibleArrayType>>, - has_value_type<CompatibleArrayType>, - has_iterator<CompatibleArrayType>>::value; -}; - -template<bool, typename, typename> -struct is_compatible_integer_type_impl : std::false_type { -}; - -template<typename RealIntegerType, typename CompatibleNumberIntegerType> -struct is_compatible_integer_type_impl<true, RealIntegerType, CompatibleNumberIntegerType> { - using RealLimits = std::numeric_limits<RealIntegerType>; - using CompatibleLimits = std::numeric_limits<CompatibleNumberIntegerType>; - - static constexpr auto value = - std::is_constructible<RealIntegerType, CompatibleNumberIntegerType>::value and - CompatibleLimits::is_integer and - RealLimits::is_signed == CompatibleLimits::is_signed; -}; - -template<typename RealIntegerType, typename CompatibleNumberIntegerType> -struct is_compatible_integer_type { - static constexpr auto value = - is_compatible_integer_type_impl< - std::is_integral<CompatibleNumberIntegerType>::value and - not std::is_same<bool, CompatibleNumberIntegerType>::value, - RealIntegerType, CompatibleNumberIntegerType>::value; -}; - -template<typename BasicJsonType, typename T> -struct has_from_json { -private: - template<typename U, typename = enable_if_t<std::is_same<void, decltype(uncvref_t<U>::from_json( - std::declval<BasicJsonType>(), std::declval<T &>()))>::value>> - static int detect(U &&); - - static void detect(...); - -public: - static constexpr bool value = std::is_integral<decltype( - detect(std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value; -}; - -template<typename BasicJsonType, typename T> -struct has_non_default_from_json { -private: - template< - typename U, - typename = enable_if_t<std::is_same< - T, decltype(uncvref_t<U>::from_json(std::declval<BasicJsonType>()))>::value >> - static int detect(U &&); - - static void detect(...); - -public: - static constexpr bool value = std::is_integral<decltype(detect( - std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value; -}; - -template<typename BasicJsonType, typename T> -struct has_to_json { -private: - template<typename U, typename = decltype(uncvref_t<U>::to_json( - std::declval<BasicJsonType &>(), std::declval<T>()))> - static int detect(U &&); - - static void detect(...); - -public: - static constexpr bool value = std::is_integral<decltype(detect( - std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value; -}; - -template<typename BasicJsonType, typename CompatibleCompleteType> -struct is_compatible_complete_type { - static constexpr bool value = - not std::is_base_of<std::istream, CompatibleCompleteType>::value and - not is_basic_json<CompatibleCompleteType>::value and - not is_basic_json_nested_type<BasicJsonType, CompatibleCompleteType>::value and - has_to_json<BasicJsonType, CompatibleCompleteType>::value; -}; - -template<typename BasicJsonType, typename CompatibleType> -struct is_compatible_type - : conjunction<is_complete_type<CompatibleType>, - is_compatible_complete_type<BasicJsonType, CompatibleType>> { -}; - -template<typename T> -struct static_const { - static constexpr T value{}; -}; - -template<typename T> -constexpr T static_const<T>::value; -} -} + static constexpr std::size_t size() noexcept { + return sizeof...(Ints); + } + }; -#include <exception> -#include <stdexcept> + template<class Sequence1, class Sequence2> + struct merge_and_renumber; -namespace nlohmann { -namespace detail { -class exception : public std::exception { -public: - const char *what() const noexcept override { - return m.what(); - } + template<std::size_t... I1, std::size_t... I2> + struct merge_and_renumber<index_sequence<I1...>, index_sequence<I2...>> + : index_sequence<I1..., (sizeof...(I1)+I2)...> { + }; - const int id; + template<std::size_t N> + struct make_index_sequence + : merge_and_renumber<typename make_index_sequence<N / 2>::type, + typename make_index_sequence<N-N / 2>::type> { + }; -protected: - exception(int id_, const char *what_arg) : id(id_), m(what_arg) {} + template<> + struct make_index_sequence<0> : index_sequence<> { + }; + template<> + struct make_index_sequence<1> : index_sequence<0> { + }; - static std::string name(const std::string &ename, int id_) { - return "[json.exception."+ename+"."+std::to_string(id_)+"] "; - } + template<typename... Ts> + using index_sequence_for = make_index_sequence<sizeof...(Ts)>; -private: - std::runtime_error m; -}; - -class parse_error : public exception { -public: - static parse_error create(int id_, std::size_t byte_, const std::string &what_arg) { - std::string w = exception::name("parse_error", id_)+"parse error"+ - (byte_ != 0 ? (" at "+std::to_string(byte_)) : "")+ - ": "+what_arg; - return parse_error(id_, byte_, w.c_str()); - } + template<class...> + struct conjunction : std::true_type { + }; + template<class B1> + struct conjunction<B1> : B1 { + }; + template<class B1, class... Bn> + struct conjunction<B1, Bn...> : std::conditional<bool(B1::value), conjunction<Bn...>, B1>::type { + }; - const std::size_t byte; + template<class B> + struct negation : std::integral_constant<bool, not B::value> { + }; -private: - parse_error(int id_, std::size_t byte_, const char *what_arg) - : exception(id_, what_arg), byte(byte_) {} -}; + template<unsigned N> + struct priority_tag : priority_tag<N-1> { + }; + template<> + struct priority_tag<0> { + }; -class invalid_iterator : public exception { -public: - static invalid_iterator create(int id_, const std::string &what_arg) { - std::string w = exception::name("invalid_iterator", id_)+what_arg; - return invalid_iterator(id_, w.c_str()); - } + template<typename T, typename = void> + struct is_complete_type : std::false_type { + }; -private: - invalid_iterator(int id_, const char *what_arg) - : exception(id_, what_arg) {} -}; + template<typename T> + struct is_complete_type<T, decltype(void(sizeof(T)))> : std::true_type { + }; -class type_error : public exception { -public: - static type_error create(int id_, const std::string &what_arg) { - std::string w = exception::name("type_error", id_)+what_arg; - return type_error(id_, w.c_str()); - } + NLOHMANN_JSON_HAS_HELPER(mapped_type); -private: - type_error(int id_, const char *what_arg) : exception(id_, what_arg) {} -}; + NLOHMANN_JSON_HAS_HELPER(key_type); -class out_of_range : public exception { -public: - static out_of_range create(int id_, const std::string &what_arg) { - std::string w = exception::name("out_of_range", id_)+what_arg; - return out_of_range(id_, w.c_str()); - } + NLOHMANN_JSON_HAS_HELPER(value_type); -private: - out_of_range(int id_, const char *what_arg) : exception(id_, what_arg) {} -}; + NLOHMANN_JSON_HAS_HELPER(iterator); -class other_error : public exception { -public: - static other_error create(int id_, const std::string &what_arg) { - std::string w = exception::name("other_error", id_)+what_arg; - return other_error(id_, w.c_str()); - } + template<bool B, class RealType, class CompatibleObjectType> + struct is_compatible_object_type_impl : std::false_type { + }; -private: - other_error(int id_, const char *what_arg) : exception(id_, what_arg) {} -}; -} -} + template<class RealType, class CompatibleObjectType> + struct is_compatible_object_type_impl<true, RealType, CompatibleObjectType> { + static constexpr auto value = + std::is_constructible<typename RealType::key_type, typename CompatibleObjectType::key_type>::value and + std::is_constructible<typename RealType::mapped_type, typename CompatibleObjectType::mapped_type>::value; + }; -#include <array> + template<class BasicJsonType, class CompatibleObjectType> + struct is_compatible_object_type { + static auto constexpr value = is_compatible_object_type_impl< + conjunction<negation<std::is_same<void, CompatibleObjectType>>, + has_mapped_type<CompatibleObjectType>, + has_key_type<CompatibleObjectType>>::value, + typename BasicJsonType::object_t, CompatibleObjectType>::value; + }; -namespace nlohmann { -namespace detail { -enum class value_t : std::uint8_t { - null, - object, - array, - string, - boolean, - number_integer, - number_unsigned, - number_float, - discarded -}; - -inline bool operator<(const value_t lhs, const value_t rhs) noexcept { - static constexpr std::array<std::uint8_t, 8> order = {{ - 0, 3, 4, 5, - 1, 2, 2, 2 - } - }; + template<typename BasicJsonType, typename T> + struct is_basic_json_nested_type { + static auto constexpr value = std::is_same<T, typename BasicJsonType::iterator>::value or + std::is_same<T, typename BasicJsonType::const_iterator>::value or + std::is_same<T, typename BasicJsonType::reverse_iterator>::value or + std::is_same<T, typename BasicJsonType::const_reverse_iterator>::value; + }; - const auto l_index = static_cast<std::size_t>(lhs); - const auto r_index = static_cast<std::size_t>(rhs); - return l_index < order.size() and r_index < order.size() and order[l_index] < order[r_index]; -} -} -} + template<class BasicJsonType, class CompatibleArrayType> + struct is_compatible_array_type { + static auto constexpr value = + conjunction<negation<std::is_same<void, CompatibleArrayType>>, + negation<is_compatible_object_type< + BasicJsonType, CompatibleArrayType>>, + negation<std::is_constructible<typename BasicJsonType::string_t, + CompatibleArrayType>>, + negation<is_basic_json_nested_type<BasicJsonType, CompatibleArrayType>>, + has_value_type<CompatibleArrayType>, + has_iterator<CompatibleArrayType>>::value; + }; -#include <forward_list> -#include <tuple> -#include <valarray> + template<bool, typename, typename> + struct is_compatible_integer_type_impl : std::false_type { + }; -namespace nlohmann { -namespace detail { -template<typename BasicJsonType, typename ArithmeticType, - enable_if_t<std::is_arithmetic<ArithmeticType>::value and - not std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value, - int> = 0> -void get_arithmetic_value(const BasicJsonType &j, ArithmeticType &val) { - switch(static_cast<value_t>(j)) { - case value_t::number_unsigned: { - val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t *>()); - break; - } - case value_t::number_integer: { - val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t *>()); - break; - } - case value_t::number_float: { - val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t *>()); - break; - } + template<typename RealIntegerType, typename CompatibleNumberIntegerType> + struct is_compatible_integer_type_impl<true, RealIntegerType, CompatibleNumberIntegerType> { + using RealLimits = std::numeric_limits<RealIntegerType>; + using CompatibleLimits = std::numeric_limits<CompatibleNumberIntegerType>; - default: - JSON_THROW(type_error::create(302, "type must be number, but is "+std::string(j.type_name()))); - } -} + static constexpr auto value = + std::is_constructible<RealIntegerType, CompatibleNumberIntegerType>::value and + CompatibleLimits::is_integer and + RealLimits::is_signed == CompatibleLimits::is_signed; + }; -template<typename BasicJsonType> -void from_json(const BasicJsonType &j, typename BasicJsonType::boolean_t &b) { - if(JSON_UNLIKELY(not j.is_boolean())) { - JSON_THROW(type_error::create(302, "type must be boolean, but is "+std::string(j.type_name()))); - } - b = *j.template get_ptr<const typename BasicJsonType::boolean_t *>(); -} + template<typename RealIntegerType, typename CompatibleNumberIntegerType> + struct is_compatible_integer_type { + static constexpr auto value = + is_compatible_integer_type_impl< + std::is_integral<CompatibleNumberIntegerType>::value and + not std::is_same<bool, CompatibleNumberIntegerType>::value, + RealIntegerType, CompatibleNumberIntegerType>::value; + }; -template<typename BasicJsonType> -void from_json(const BasicJsonType &j, typename BasicJsonType::string_t &s) { - if(JSON_UNLIKELY(not j.is_string())) { - JSON_THROW(type_error::create(302, "type must be string, but is "+std::string(j.type_name()))); - } - s = *j.template get_ptr<const typename BasicJsonType::string_t *>(); -} + template<typename BasicJsonType, typename T> + struct has_from_json { + private: + template<typename U, typename = enable_if_t<std::is_same<void, decltype(uncvref_t<U>::from_json( + std::declval<BasicJsonType>(), std::declval<T &>()))>::value>> + static int detect(U &&); -template<typename BasicJsonType> -void from_json(const BasicJsonType &j, typename BasicJsonType::number_float_t &val) { - get_arithmetic_value(j, val); -} + static void detect(...); -template<typename BasicJsonType> -void from_json(const BasicJsonType &j, typename BasicJsonType::number_unsigned_t &val) { - get_arithmetic_value(j, val); -} + public: + static constexpr bool value = std::is_integral<decltype( + detect(std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value; + }; -template<typename BasicJsonType> -void from_json(const BasicJsonType &j, typename BasicJsonType::number_integer_t &val) { - get_arithmetic_value(j, val); -} + template<typename BasicJsonType, typename T> + struct has_non_default_from_json { + private: + template< + typename U, + typename = enable_if_t<std::is_same< + T, decltype(uncvref_t<U>::from_json(std::declval<BasicJsonType>()))>::value >> + static int detect(U &&); -template<typename BasicJsonType, typename EnumType, - enable_if_t<std::is_enum<EnumType>::value, int> = 0> -void from_json(const BasicJsonType &j, EnumType &e) { - typename std::underlying_type<EnumType>::type val; - get_arithmetic_value(j, val); - e = static_cast<EnumType>(val); -} + static void detect(...); -template<typename BasicJsonType> -void from_json(const BasicJsonType &j, typename BasicJsonType::array_t &arr) { - if(JSON_UNLIKELY(not j.is_array())) { - JSON_THROW(type_error::create(302, "type must be array, but is "+std::string(j.type_name()))); - } - arr = *j.template get_ptr<const typename BasicJsonType::array_t *>(); -} + public: + static constexpr bool value = std::is_integral<decltype(detect( + std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value; + }; -template<typename BasicJsonType, typename T, typename Allocator, - enable_if_t<std::is_convertible<BasicJsonType, T>::value, int> = 0> -void from_json(const BasicJsonType &j, std::forward_list<T, Allocator> &l) { - if(JSON_UNLIKELY(not j.is_array())) { - JSON_THROW(type_error::create(302, "type must be array, but is "+std::string(j.type_name()))); - } - std::transform(j.rbegin(), j.rend(), - std::front_inserter(l), [](const BasicJsonType &i) { - return i.template get<T>(); - }); -} + template<typename BasicJsonType, typename T> + struct has_to_json { + private: + template<typename U, typename = decltype(uncvref_t<U>::to_json( + std::declval<BasicJsonType &>(), std::declval<T>()))> + static int detect(U &&); -template<typename BasicJsonType, typename T, - enable_if_t<std::is_convertible<BasicJsonType, T>::value, int> = 0> -void from_json(const BasicJsonType &j, std::valarray<T> &l) { - if(JSON_UNLIKELY(not j.is_array())) { - JSON_THROW(type_error::create(302, "type must be array, but is "+std::string(j.type_name()))); - } - l.resize(j.size()); - std::copy(j.m_value.array->begin(), j.m_value.array->end(), std::begin(l)); -} + static void detect(...); -template<typename BasicJsonType, typename CompatibleArrayType> -void from_json_array_impl(const BasicJsonType &j, CompatibleArrayType &arr, priority_tag<0>) { - using std::end; + public: + static constexpr bool value = std::is_integral<decltype(detect( + std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value; + }; - std::transform(j.begin(), j.end(), - std::inserter(arr, end(arr)), [](const BasicJsonType &i) { - return i.template get<typename CompatibleArrayType::value_type>(); - }); -} + template<typename BasicJsonType, typename CompatibleCompleteType> + struct is_compatible_complete_type { + static constexpr bool value = + not std::is_base_of<std::istream, CompatibleCompleteType>::value and + not is_basic_json<CompatibleCompleteType>::value and + not is_basic_json_nested_type<BasicJsonType, CompatibleCompleteType>::value and + has_to_json<BasicJsonType, CompatibleCompleteType>::value; + }; -template<typename BasicJsonType, typename CompatibleArrayType> -auto from_json_array_impl(const BasicJsonType &j, CompatibleArrayType &arr, priority_tag<1>) --> decltype( -arr.reserve(std::declval<typename CompatibleArrayType::size_type>()), - void()) { - using std::end; - - arr.reserve(j.size()); - std::transform(j.begin(), j.end(), - std::inserter(arr, end(arr)), [](const BasicJsonType &i) { - return i.template get<typename CompatibleArrayType::value_type>(); - }); -} + template<typename BasicJsonType, typename CompatibleType> + struct is_compatible_type + : conjunction<is_complete_type<CompatibleType>, + is_compatible_complete_type<BasicJsonType, CompatibleType>> { + }; -template<typename BasicJsonType, typename T, std::size_t N> -void from_json_array_impl(const BasicJsonType &j, std::array<T, N> &arr, priority_tag<2>) { - for(std::size_t i = 0; i < N; ++i) { - arr[i] = j.at(i).template get<T>(); - } -} + template<typename T> + struct static_const { + static constexpr T value{}; + }; -template< - typename BasicJsonType, typename CompatibleArrayType, - enable_if_t< - is_compatible_array_type<BasicJsonType, CompatibleArrayType>::value and - not std::is_same<typename BasicJsonType::array_t, - CompatibleArrayType>::value and - std::is_constructible< - BasicJsonType, typename CompatibleArrayType::value_type>::value, - int> = 0> -void from_json(const BasicJsonType &j, CompatibleArrayType &arr) { - if(JSON_UNLIKELY(not j.is_array())) { - JSON_THROW(type_error::create(302, "type must be array, but is "+ - std::string(j.type_name()))); + template<typename T> + constexpr T static_const<T>::value; } - - from_json_array_impl(j, arr, priority_tag<2>{}); } -template<typename BasicJsonType, typename CompatibleObjectType, - enable_if_t<is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value, int> = 0> -void from_json(const BasicJsonType &j, CompatibleObjectType &obj) { - if(JSON_UNLIKELY(not j.is_object())) { - JSON_THROW(type_error::create(302, "type must be object, but is "+std::string(j.type_name()))); - } +#include <exception> +#include <stdexcept> - auto inner_object = j.template get_ptr<const typename BasicJsonType::object_t *>(); - using value_type = typename CompatibleObjectType::value_type; - std::transform( - inner_object->begin(), inner_object->end(), - std::inserter(obj, obj.begin()), - [](typename BasicJsonType::object_t::value_type const &p) { - return value_type(p.first, p.second.template get<typename CompatibleObjectType::mapped_type>()); - }); -} +namespace nlohmann { + namespace detail { + class exception : public std::exception { + public: + const char *what() const noexcept override { + return m.what(); + } -template<typename BasicJsonType, typename ArithmeticType, - enable_if_t< - std::is_arithmetic<ArithmeticType>::value and - not std::is_same<ArithmeticType, typename BasicJsonType::number_unsigned_t>::value and - not std::is_same<ArithmeticType, typename BasicJsonType::number_integer_t>::value and - not std::is_same<ArithmeticType, typename BasicJsonType::number_float_t>::value and - not std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value, - int> = 0> -void from_json(const BasicJsonType &j, ArithmeticType &val) { - switch(static_cast<value_t>(j)) { - case value_t::number_unsigned: { - val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t *>()); - break; - } - case value_t::number_integer: { - val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t *>()); - break; - } - case value_t::number_float: { - val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t *>()); - break; - } - case value_t::boolean: { - val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::boolean_t *>()); - break; - } + const int id; - default: - JSON_THROW(type_error::create(302, "type must be number, but is "+std::string(j.type_name()))); - } -} + protected: + exception(int id_, const char *what_arg) : id(id_), m(what_arg) {} -template<typename BasicJsonType, typename A1, typename A2> -void from_json(const BasicJsonType &j, std::pair<A1, A2> &p) { - p = {j.at(0).template get<A1>(), j.at(1).template get<A2>()}; -} + static std::string name(const std::string &ename, int id_) { + return "[json.exception."+ename+"."+std::to_string(id_)+"] "; + } -template<typename BasicJsonType, typename Tuple, std::size_t... Idx> -void from_json_tuple_impl(const BasicJsonType &j, Tuple &t, index_sequence<Idx...>) { - t = std::make_tuple(j.at(Idx).template get<typename std::tuple_element<Idx, Tuple>::type>()...); -} + private: + std::runtime_error m; + }; -template<typename BasicJsonType, typename... Args> -void from_json(const BasicJsonType &j, std::tuple<Args...> &t) { - from_json_tuple_impl(j, t, index_sequence_for<Args...>{}); -} + class parse_error : public exception { + public: + static parse_error create(int id_, std::size_t byte_, const std::string &what_arg) { + std::string w = exception::name("parse_error", id_)+"parse error"+ + (byte_ != 0 ? (" at "+std::to_string(byte_)) : "")+ + ": "+what_arg; + return parse_error(id_, byte_, w.c_str()); + } -struct from_json_fn { -private: - template<typename BasicJsonType, typename T> - auto call(const BasicJsonType &j, T &val, priority_tag<1>) const - noexcept(noexcept(from_json(j, val))) - -> decltype(from_json(j, val), void()) { - return from_json(j, val); - } + const std::size_t byte; - template<typename BasicJsonType, typename T> - void call(const BasicJsonType &, T &, priority_tag<0>) const noexcept { - static_assert(sizeof(BasicJsonType) == 0, - "could not find from_json() method in T's namespace"); -#ifdef _MSC_VER + private: + parse_error(int id_, std::size_t byte_, const char *what_arg) + : exception(id_, what_arg), byte(byte_) {} + }; - using decayed = uncvref_t<T>; - static_assert(sizeof(typename decayed::force_msvc_stacktrace) == 0, - "forcing MSVC stacktrace to show which T we're talking about."); -#endif - } + class invalid_iterator : public exception { + public: + static invalid_iterator create(int id_, const std::string &what_arg) { + std::string w = exception::name("invalid_iterator", id_)+what_arg; + return invalid_iterator(id_, w.c_str()); + } -public: - template<typename BasicJsonType, typename T> - void operator()(const BasicJsonType &j, T &val) const - noexcept(noexcept(std::declval<from_json_fn>().call(j, val, priority_tag<1>{}))) { - return call(j, val, priority_tag<1>{}); - } -}; -} + private: + invalid_iterator(int id_, const char *what_arg) + : exception(id_, what_arg) {} + }; -namespace { -constexpr const auto &from_json = detail::static_const<detail::from_json_fn>::value; -} -} + class type_error : public exception { + public: + static type_error create(int id_, const std::string &what_arg) { + std::string w = exception::name("type_error", id_)+what_arg; + return type_error(id_, w.c_str()); + } -namespace nlohmann { -namespace detail { -template<value_t> -struct external_constructor; + private: + type_error(int id_, const char *what_arg) : exception(id_, what_arg) {} + }; -template<> -struct external_constructor<value_t::boolean> { - template<typename BasicJsonType> - static void construct(BasicJsonType &j, typename BasicJsonType::boolean_t b) noexcept { - j.m_type = value_t::boolean; - j.m_value = b; - j.assert_invariant(); - } -}; + class out_of_range : public exception { + public: + static out_of_range create(int id_, const std::string &what_arg) { + std::string w = exception::name("out_of_range", id_)+what_arg; + return out_of_range(id_, w.c_str()); + } -template<> -struct external_constructor<value_t::string> { - template<typename BasicJsonType> - static void construct(BasicJsonType &j, const typename BasicJsonType::string_t &s) { - j.m_type = value_t::string; - j.m_value = s; - j.assert_invariant(); - } + private: + out_of_range(int id_, const char *what_arg) : exception(id_, what_arg) {} + }; - template<typename BasicJsonType> - static void construct(BasicJsonType &j, typename BasicJsonType::string_t &&s) { - j.m_type = value_t::string; - j.m_value = std::move(s); - j.assert_invariant(); - } -}; + class other_error : public exception { + public: + static other_error create(int id_, const std::string &what_arg) { + std::string w = exception::name("other_error", id_)+what_arg; + return other_error(id_, w.c_str()); + } -template<> -struct external_constructor<value_t::number_float> { - template<typename BasicJsonType> - static void construct(BasicJsonType &j, typename BasicJsonType::number_float_t val) noexcept { - j.m_type = value_t::number_float; - j.m_value = val; - j.assert_invariant(); + private: + other_error(int id_, const char *what_arg) : exception(id_, what_arg) {} + }; } -}; +} -template<> -struct external_constructor<value_t::number_unsigned> { - template<typename BasicJsonType> - static void construct(BasicJsonType &j, typename BasicJsonType::number_unsigned_t val) noexcept { - j.m_type = value_t::number_unsigned; - j.m_value = val; - j.assert_invariant(); - } -}; +#include <array> -template<> -struct external_constructor<value_t::number_integer> { - template<typename BasicJsonType> - static void construct(BasicJsonType &j, typename BasicJsonType::number_integer_t val) noexcept { - j.m_type = value_t::number_integer; - j.m_value = val; - j.assert_invariant(); - } -}; +namespace nlohmann { + namespace detail { + enum class value_t : std::uint8_t { + null, + object, + array, + string, + boolean, + number_integer, + number_unsigned, + number_float, + discarded + }; -template<> -struct external_constructor<value_t::array> { - template<typename BasicJsonType> - static void construct(BasicJsonType &j, const typename BasicJsonType::array_t &arr) { - j.m_type = value_t::array; - j.m_value = arr; - j.assert_invariant(); - } + inline bool operator<(const value_t lhs, const value_t rhs) noexcept { + static constexpr std::array<std::uint8_t, 8> order = {{ + 0, 3, 4, 5, + 1, 2, 2, 2 + } + }; - template<typename BasicJsonType> - static void construct(BasicJsonType &j, typename BasicJsonType::array_t &&arr) { - j.m_type = value_t::array; - j.m_value = std::move(arr); - j.assert_invariant(); + const auto l_index = static_cast<std::size_t>(lhs); + const auto r_index = static_cast<std::size_t>(rhs); + return l_index < order.size() and r_index < order.size() and order[l_index] < order[r_index]; + } } +} - template<typename BasicJsonType, typename CompatibleArrayType, - enable_if_t<not std::is_same<CompatibleArrayType, typename BasicJsonType::array_t>::value, - int> = 0> - static void construct(BasicJsonType &j, const CompatibleArrayType &arr) { - using std::begin; - using std::end; - j.m_type = value_t::array; - j.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr)); - j.assert_invariant(); - } +#include <forward_list> +#include <tuple> +#include <valarray> - template<typename BasicJsonType> - static void construct(BasicJsonType &j, const std::vector<bool> &arr) { - j.m_type = value_t::array; - j.m_value = value_t::array; - j.m_value.array->reserve(arr.size()); - for(const bool x : arr) { - j.m_value.array->push_back(x); - } - j.assert_invariant(); - } +namespace nlohmann { + namespace detail { + template<typename BasicJsonType, typename ArithmeticType, + enable_if_t<std::is_arithmetic<ArithmeticType>::value and + not std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value, + int> = 0> + void get_arithmetic_value(const BasicJsonType &j, ArithmeticType &val) { + switch(static_cast<value_t>(j)) { + case value_t::number_unsigned: { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t *>()); + break; + } + case value_t::number_integer: { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t *>()); + break; + } + case value_t::number_float: { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t *>()); + break; + } - template<typename BasicJsonType, typename T, - enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0> - static void construct(BasicJsonType &j, const std::valarray<T> &arr) { - j.m_type = value_t::array; - j.m_value = value_t::array; - j.m_value.array->resize(arr.size()); - std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin()); - j.assert_invariant(); - } -}; + default: + JSON_THROW(type_error::create(302, "type must be number, but is "+std::string(j.type_name()))); + } + } -template<> -struct external_constructor<value_t::object> { - template<typename BasicJsonType> - static void construct(BasicJsonType &j, const typename BasicJsonType::object_t &obj) { - j.m_type = value_t::object; - j.m_value = obj; - j.assert_invariant(); - } + template<typename BasicJsonType> + void from_json(const BasicJsonType &j, typename BasicJsonType::boolean_t &b) { + if(JSON_UNLIKELY(not j.is_boolean())) { + JSON_THROW(type_error::create(302, "type must be boolean, but is "+std::string(j.type_name()))); + } + b = *j.template get_ptr<const typename BasicJsonType::boolean_t *>(); + } - template<typename BasicJsonType> - static void construct(BasicJsonType &j, typename BasicJsonType::object_t &&obj) { - j.m_type = value_t::object; - j.m_value = std::move(obj); - j.assert_invariant(); - } + template<typename BasicJsonType> + void from_json(const BasicJsonType &j, typename BasicJsonType::string_t &s) { + if(JSON_UNLIKELY(not j.is_string())) { + JSON_THROW(type_error::create(302, "type must be string, but is "+std::string(j.type_name()))); + } + s = *j.template get_ptr<const typename BasicJsonType::string_t *>(); + } - template<typename BasicJsonType, typename CompatibleObjectType, - enable_if_t<not std::is_same<CompatibleObjectType, typename BasicJsonType::object_t>::value, int> = 0> - static void construct(BasicJsonType &j, const CompatibleObjectType &obj) { - using std::begin; - using std::end; + template<typename BasicJsonType> + void from_json(const BasicJsonType &j, typename BasicJsonType::number_float_t &val) { + get_arithmetic_value(j, val); + } - j.m_type = value_t::object; - j.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj)); - j.assert_invariant(); - } -}; + template<typename BasicJsonType> + void from_json(const BasicJsonType &j, typename BasicJsonType::number_unsigned_t &val) { + get_arithmetic_value(j, val); + } -template<typename BasicJsonType, typename T, - enable_if_t<std::is_same<T, typename BasicJsonType::boolean_t>::value, int> = 0> -void to_json(BasicJsonType &j, T b) noexcept { - external_constructor<value_t::boolean>::construct(j, b); -} + template<typename BasicJsonType> + void from_json(const BasicJsonType &j, typename BasicJsonType::number_integer_t &val) { + get_arithmetic_value(j, val); + } -template<typename BasicJsonType, typename CompatibleString, - enable_if_t<std::is_constructible<typename BasicJsonType::string_t, CompatibleString>::value, int> = 0> -void to_json(BasicJsonType &j, const CompatibleString &s) { - external_constructor<value_t::string>::construct(j, s); -} + template<typename BasicJsonType, typename EnumType, + enable_if_t<std::is_enum<EnumType>::value, int> = 0> + void from_json(const BasicJsonType &j, EnumType &e) { + typename std::underlying_type<EnumType>::type val; + get_arithmetic_value(j, val); + e = static_cast<EnumType>(val); + } -template<typename BasicJsonType> -void to_json(BasicJsonType &j, typename BasicJsonType::string_t &&s) { - external_constructor<value_t::string>::construct(j, std::move(s)); -} + template<typename BasicJsonType> + void from_json(const BasicJsonType &j, typename BasicJsonType::array_t &arr) { + if(JSON_UNLIKELY(not j.is_array())) { + JSON_THROW(type_error::create(302, "type must be array, but is "+std::string(j.type_name()))); + } + arr = *j.template get_ptr<const typename BasicJsonType::array_t *>(); + } -template<typename BasicJsonType, typename FloatType, - enable_if_t<std::is_floating_point<FloatType>::value, int> = 0> -void to_json(BasicJsonType &j, FloatType val) noexcept { - external_constructor<value_t::number_float>::construct(j, static_cast<typename BasicJsonType::number_float_t>(val)); -} + template<typename BasicJsonType, typename T, typename Allocator, + enable_if_t<std::is_convertible<BasicJsonType, T>::value, int> = 0> + void from_json(const BasicJsonType &j, std::forward_list<T, Allocator> &l) { + if(JSON_UNLIKELY(not j.is_array())) { + JSON_THROW(type_error::create(302, "type must be array, but is "+std::string(j.type_name()))); + } + std::transform(j.rbegin(), j.rend(), + std::front_inserter(l), [](const BasicJsonType &i) { + return i.template get<T>(); + }); + } -template<typename BasicJsonType, typename CompatibleNumberUnsignedType, - enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_unsigned_t, CompatibleNumberUnsignedType>::value, int> = 0> -void to_json(BasicJsonType &j, CompatibleNumberUnsignedType val) noexcept { - external_constructor<value_t::number_unsigned>::construct(j, - static_cast<typename BasicJsonType::number_unsigned_t>(val)); -} + template<typename BasicJsonType, typename T, + enable_if_t<std::is_convertible<BasicJsonType, T>::value, int> = 0> + void from_json(const BasicJsonType &j, std::valarray<T> &l) { + if(JSON_UNLIKELY(not j.is_array())) { + JSON_THROW(type_error::create(302, "type must be array, but is "+std::string(j.type_name()))); + } + l.resize(j.size()); + std::copy(j.m_value.array->begin(), j.m_value.array->end(), std::begin(l)); + } -template<typename BasicJsonType, typename CompatibleNumberIntegerType, - enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType>::value, int> = 0> -void to_json(BasicJsonType &j, CompatibleNumberIntegerType val) noexcept { - external_constructor<value_t::number_integer>::construct(j, - static_cast<typename BasicJsonType::number_integer_t>(val)); -} + template<typename BasicJsonType, typename CompatibleArrayType> + void from_json_array_impl(const BasicJsonType &j, CompatibleArrayType &arr, priority_tag<0>) { + using std::end; -template<typename BasicJsonType, typename EnumType, - enable_if_t<std::is_enum<EnumType>::value, int> = 0> -void to_json(BasicJsonType &j, EnumType e) noexcept { - using underlying_type = typename std::underlying_type<EnumType>::type; - external_constructor<value_t::number_integer>::construct(j, static_cast<underlying_type>(e)); -} + std::transform(j.begin(), j.end(), + std::inserter(arr, end(arr)), [](const BasicJsonType &i) { + return i.template get<typename CompatibleArrayType::value_type>(); + }); + } -template<typename BasicJsonType> -void to_json(BasicJsonType &j, const std::vector<bool> &e) { - external_constructor<value_t::array>::construct(j, e); -} + template<typename BasicJsonType, typename CompatibleArrayType> + auto from_json_array_impl(const BasicJsonType &j, CompatibleArrayType &arr, priority_tag<1>) + -> decltype( + arr.reserve(std::declval<typename CompatibleArrayType::size_type>()), + void()) { + using std::end; -template<typename BasicJsonType, typename CompatibleArrayType, - enable_if_t<is_compatible_array_type<BasicJsonType, CompatibleArrayType>::value or - std::is_same<typename BasicJsonType::array_t, CompatibleArrayType>::value, - int> = 0> -void to_json(BasicJsonType &j, const CompatibleArrayType &arr) { - external_constructor<value_t::array>::construct(j, arr); -} + arr.reserve(j.size()); + std::transform(j.begin(), j.end(), + std::inserter(arr, end(arr)), [](const BasicJsonType &i) { + return i.template get<typename CompatibleArrayType::value_type>(); + }); + } -template<typename BasicJsonType, typename T, - enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0> -void to_json(BasicJsonType &j, std::valarray<T> arr) { - external_constructor<value_t::array>::construct(j, std::move(arr)); -} + template<typename BasicJsonType, typename T, std::size_t N> + void from_json_array_impl(const BasicJsonType &j, std::array<T, N> &arr, priority_tag<2>) { + for(std::size_t i = 0; i < N; ++i) { + arr[i] = j.at(i).template get<T>(); + } + } -template<typename BasicJsonType> -void to_json(BasicJsonType &j, typename BasicJsonType::array_t &&arr) { - external_constructor<value_t::array>::construct(j, std::move(arr)); -} + template< + typename BasicJsonType, typename CompatibleArrayType, + enable_if_t< + is_compatible_array_type<BasicJsonType, CompatibleArrayType>::value and + not std::is_same<typename BasicJsonType::array_t, + CompatibleArrayType>::value and + std::is_constructible< + BasicJsonType, typename CompatibleArrayType::value_type>::value, + int> = 0> + void from_json(const BasicJsonType &j, CompatibleArrayType &arr) { + if(JSON_UNLIKELY(not j.is_array())) { + JSON_THROW(type_error::create(302, "type must be array, but is "+ + std::string(j.type_name()))); + } -template<typename BasicJsonType, typename CompatibleObjectType, - enable_if_t<is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value, int> = 0> -void to_json(BasicJsonType &j, const CompatibleObjectType &obj) { - external_constructor<value_t::object>::construct(j, obj); -} + from_json_array_impl(j, arr, priority_tag<2>{}); + } -template<typename BasicJsonType> -void to_json(BasicJsonType &j, typename BasicJsonType::object_t &&obj) { - external_constructor<value_t::object>::construct(j, std::move(obj)); -} + template<typename BasicJsonType, typename CompatibleObjectType, + enable_if_t<is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value, int> = 0> + void from_json(const BasicJsonType &j, CompatibleObjectType &obj) { + if(JSON_UNLIKELY(not j.is_object())) { + JSON_THROW(type_error::create(302, "type must be object, but is "+std::string(j.type_name()))); + } -template<typename BasicJsonType, typename T, std::size_t N, - enable_if_t<not std::is_constructible<typename BasicJsonType::string_t, T (&)[N]>::value, int> = 0> -void to_json(BasicJsonType &j, T (&arr)[N]) { - external_constructor<value_t::array>::construct(j, arr); -} + auto inner_object = j.template get_ptr<const typename BasicJsonType::object_t *>(); + using value_type = typename CompatibleObjectType::value_type; + std::transform( + inner_object->begin(), inner_object->end(), + std::inserter(obj, obj.begin()), + [](typename BasicJsonType::object_t::value_type const &p) { + return value_type(p.first, p.second.template get<typename CompatibleObjectType::mapped_type>()); + }); + } -template<typename BasicJsonType, typename... Args> -void to_json(BasicJsonType &j, const std::pair<Args...> &p) { - j = {p.first, p.second}; -} + template<typename BasicJsonType, typename ArithmeticType, + enable_if_t< + std::is_arithmetic<ArithmeticType>::value and + not std::is_same<ArithmeticType, typename BasicJsonType::number_unsigned_t>::value and + not std::is_same<ArithmeticType, typename BasicJsonType::number_integer_t>::value and + not std::is_same<ArithmeticType, typename BasicJsonType::number_float_t>::value and + not std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value, + int> = 0> + void from_json(const BasicJsonType &j, ArithmeticType &val) { + switch(static_cast<value_t>(j)) { + case value_t::number_unsigned: { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t *>()); + break; + } + case value_t::number_integer: { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t *>()); + break; + } + case value_t::number_float: { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t *>()); + break; + } + case value_t::boolean: { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::boolean_t *>()); + break; + } -template<typename BasicJsonType, typename Tuple, std::size_t... Idx> -void to_json_tuple_impl(BasicJsonType &j, const Tuple &t, index_sequence<Idx...>) { - j = {std::get<Idx>(t)...}; -} + default: + JSON_THROW(type_error::create(302, "type must be number, but is "+std::string(j.type_name()))); + } + } -template<typename BasicJsonType, typename... Args> -void to_json(BasicJsonType &j, const std::tuple<Args...> &t) { - to_json_tuple_impl(j, t, index_sequence_for<Args...>{}); -} + template<typename BasicJsonType, typename A1, typename A2> + void from_json(const BasicJsonType &j, std::pair<A1, A2> &p) { + p = {j.at(0).template get<A1>(), j.at(1).template get<A2>()}; + } -struct to_json_fn { -private: - template<typename BasicJsonType, typename T> - auto call(BasicJsonType &j, T &&val, priority_tag<1>) const noexcept(noexcept(to_json(j, std::forward<T>(val)))) - -> decltype(to_json(j, std::forward<T>(val)), void()) { - return to_json(j, std::forward<T>(val)); - } + template<typename BasicJsonType, typename Tuple, std::size_t... Idx> + void from_json_tuple_impl(const BasicJsonType &j, Tuple &t, index_sequence<Idx...>) { + t = std::make_tuple(j.at(Idx).template get<typename std::tuple_element<Idx, Tuple>::type>()...); + } + + template<typename BasicJsonType, typename... Args> + void from_json(const BasicJsonType &j, std::tuple<Args...> &t) { + from_json_tuple_impl(j, t, index_sequence_for<Args...>{}); + } - template<typename BasicJsonType, typename T> - void call(BasicJsonType &, T &&, priority_tag<0>) const noexcept { - static_assert(sizeof(BasicJsonType) == 0, - "could not find to_json() method in T's namespace"); + struct from_json_fn { + private: + template<typename BasicJsonType, typename T> + auto call(const BasicJsonType &j, T &val, priority_tag<1>) const + noexcept(noexcept(from_json(j, val))) + -> decltype(from_json(j, val), void()) { + return from_json(j, val); + } + template<typename BasicJsonType, typename T> + void call(const BasicJsonType &, T &, priority_tag<0>) const noexcept { + static_assert(sizeof(BasicJsonType) == 0, + "could not find from_json() method in T's namespace"); #ifdef _MSC_VER - using decayed = uncvref_t<T>; + using decayed = uncvref_t<T>; static_assert(sizeof(typename decayed::force_msvc_stacktrace) == 0, "forcing MSVC stacktrace to show which T we're talking about."); #endif - } + } -public: - template<typename BasicJsonType, typename T> - void operator()(BasicJsonType &j, T &&val) const - noexcept(noexcept(std::declval<to_json_fn>().call(j, std::forward<T>(val), priority_tag<1>{}))) { - return call(j, std::forward<T>(val), priority_tag<1>{}); + public: + template<typename BasicJsonType, typename T> + void operator()(const BasicJsonType &j, T &val) const + noexcept(noexcept(std::declval<from_json_fn>().call(j, val, priority_tag<1>{}))) { + return call(j, val, priority_tag<1>{}); + } + }; } -}; -} -namespace { -constexpr const auto &to_json = detail::static_const<detail::to_json_fn>::value; -} + namespace { + constexpr const auto &from_json = detail::static_const<detail::from_json_fn>::value; + } } -#include <cstring> -#include <ios> -#include <istream> - namespace nlohmann { -namespace detail { -struct input_adapter_protocol { - virtual std::char_traits<char>::int_type get_character() = 0; + namespace detail { + template<value_t> + struct external_constructor; + + template<> + struct external_constructor<value_t::boolean> { + template<typename BasicJsonType> + static void construct(BasicJsonType &j, typename BasicJsonType::boolean_t b) noexcept { + j.m_type = value_t::boolean; + j.m_value = b; + j.assert_invariant(); + } + }; - virtual void unget_character() = 0; + template<> + struct external_constructor<value_t::string> { + template<typename BasicJsonType> + static void construct(BasicJsonType &j, const typename BasicJsonType::string_t &s) { + j.m_type = value_t::string; + j.m_value = s; + j.assert_invariant(); + } - virtual ~input_adapter_protocol() = default; -}; + template<typename BasicJsonType> + static void construct(BasicJsonType &j, typename BasicJsonType::string_t &&s) { + j.m_type = value_t::string; + j.m_value = std::move(s); + j.assert_invariant(); + } + }; -using input_adapter_t = std::shared_ptr<input_adapter_protocol>; + template<> + struct external_constructor<value_t::number_float> { + template<typename BasicJsonType> + static void construct(BasicJsonType &j, typename BasicJsonType::number_float_t val) noexcept { + j.m_type = value_t::number_float; + j.m_value = val; + j.assert_invariant(); + } + }; -class input_stream_adapter : public input_adapter_protocol { -public: - ~input_stream_adapter() override { - is.clear(); - } + template<> + struct external_constructor<value_t::number_unsigned> { + template<typename BasicJsonType> + static void construct(BasicJsonType &j, typename BasicJsonType::number_unsigned_t val) noexcept { + j.m_type = value_t::number_unsigned; + j.m_value = val; + j.assert_invariant(); + } + }; - explicit input_stream_adapter(std::istream &i) - : is(i), sb(*i.rdbuf()) { - std::char_traits<char>::int_type c; - if((c = get_character()) == 0xEF) { - if((c = get_character()) == 0xBB) { - if((c = get_character()) == 0xBF) { - return; - } else if(c != std::char_traits<char>::eof()) { - is.unget(); + template<> + struct external_constructor<value_t::number_integer> { + template<typename BasicJsonType> + static void construct(BasicJsonType &j, typename BasicJsonType::number_integer_t val) noexcept { + j.m_type = value_t::number_integer; + j.m_value = val; + j.assert_invariant(); + } + }; + + template<> + struct external_constructor<value_t::array> { + template<typename BasicJsonType> + static void construct(BasicJsonType &j, const typename BasicJsonType::array_t &arr) { + j.m_type = value_t::array; + j.m_value = arr; + j.assert_invariant(); + } + + template<typename BasicJsonType> + static void construct(BasicJsonType &j, typename BasicJsonType::array_t &&arr) { + j.m_type = value_t::array; + j.m_value = std::move(arr); + j.assert_invariant(); + } + + template<typename BasicJsonType, typename CompatibleArrayType, + enable_if_t<not std::is_same<CompatibleArrayType, typename BasicJsonType::array_t>::value, + int> = 0> + static void construct(BasicJsonType &j, const CompatibleArrayType &arr) { + using std::begin; + using std::end; + j.m_type = value_t::array; + j.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr)); + j.assert_invariant(); + } + + template<typename BasicJsonType> + static void construct(BasicJsonType &j, const std::vector<bool> &arr) { + j.m_type = value_t::array; + j.m_value = value_t::array; + j.m_value.array->reserve(arr.size()); + for(const bool x : arr) { + j.m_value.array->push_back(x); } - is.putback('\xBB'); - } else if(c != std::char_traits<char>::eof()) { - is.unget(); + j.assert_invariant(); } - is.putback('\xEF'); - } else if(c != std::char_traits<char>::eof()) { - is.unget(); - } - } - input_stream_adapter(const input_stream_adapter &) = delete; + template<typename BasicJsonType, typename T, + enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0> + static void construct(BasicJsonType &j, const std::valarray<T> &arr) { + j.m_type = value_t::array; + j.m_value = value_t::array; + j.m_value.array->resize(arr.size()); + std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin()); + j.assert_invariant(); + } + }; - input_stream_adapter &operator=(input_stream_adapter &) = delete; + template<> + struct external_constructor<value_t::object> { + template<typename BasicJsonType> + static void construct(BasicJsonType &j, const typename BasicJsonType::object_t &obj) { + j.m_type = value_t::object; + j.m_value = obj; + j.assert_invariant(); + } - std::char_traits<char>::int_type get_character() override { - return sb.sbumpc(); - } + template<typename BasicJsonType> + static void construct(BasicJsonType &j, typename BasicJsonType::object_t &&obj) { + j.m_type = value_t::object; + j.m_value = std::move(obj); + j.assert_invariant(); + } - void unget_character() override { - sb.sungetc(); - } + template<typename BasicJsonType, typename CompatibleObjectType, + enable_if_t<not std::is_same<CompatibleObjectType, typename BasicJsonType::object_t>::value, int> = 0> + static void construct(BasicJsonType &j, const CompatibleObjectType &obj) { + using std::begin; + using std::end; -private: - std::istream &is; - std::streambuf &sb; -}; + j.m_type = value_t::object; + j.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj)); + j.assert_invariant(); + } + }; -class input_buffer_adapter : public input_adapter_protocol { -public: - input_buffer_adapter(const char *b, const std::size_t l) - : cursor(b), limit(b+l), start(b) { - if(l >= 3 and b[0] == '\xEF' and b[1] == '\xBB' and b[2] == '\xBF') { - cursor += 3; + template<typename BasicJsonType, typename T, + enable_if_t<std::is_same<T, typename BasicJsonType::boolean_t>::value, int> = 0> + void to_json(BasicJsonType &j, T b) noexcept { + external_constructor<value_t::boolean>::construct(j, b); } - } - - input_buffer_adapter(const input_buffer_adapter &) = delete; - input_buffer_adapter &operator=(input_buffer_adapter &) = delete; - - std::char_traits<char>::int_type get_character() noexcept override { - if(JSON_LIKELY(cursor < limit)) { - return std::char_traits<char>::to_int_type(*(cursor++)); + template<typename BasicJsonType, typename CompatibleString, + enable_if_t<std::is_constructible<typename BasicJsonType::string_t, CompatibleString>::value, int> = 0> + void to_json(BasicJsonType &j, const CompatibleString &s) { + external_constructor<value_t::string>::construct(j, s); } - return std::char_traits<char>::eof(); - } - - void unget_character() noexcept override { - if(JSON_LIKELY(cursor > start)) { - --cursor; + template<typename BasicJsonType> + void to_json(BasicJsonType &j, typename BasicJsonType::string_t &&s) { + external_constructor<value_t::string>::construct(j, std::move(s)); } - } - -private: - const char *cursor; - - const char *limit; - - const char *start; -}; - -class input_adapter { -public: - input_adapter(std::istream &i) - : ia(std::make_shared<input_stream_adapter>(i)) {} - - input_adapter(std::istream &&i) - : ia(std::make_shared<input_stream_adapter>(i)) {} - - template<typename CharT, - typename std::enable_if< - std::is_pointer<CharT>::value and - std::is_integral<typename std::remove_pointer<CharT>::type>::value and - sizeof(typename std::remove_pointer<CharT>::type) == 1, - int>::type = 0> - input_adapter(CharT b, std::size_t l) - : ia(std::make_shared<input_buffer_adapter>(reinterpret_cast<const char *>(b), l)) {} - - template<typename CharT, - typename std::enable_if< - std::is_pointer<CharT>::value and - std::is_integral<typename std::remove_pointer<CharT>::type>::value and - sizeof(typename std::remove_pointer<CharT>::type) == 1, - int>::type = 0> - input_adapter(CharT b) - : input_adapter(reinterpret_cast<const char *>(b), - std::strlen(reinterpret_cast<const char *>(b))) {} - template<class IteratorType, - typename std::enable_if< - std::is_same<typename std::iterator_traits<IteratorType>::iterator_category, std::random_access_iterator_tag>::value, - int>::type = 0> - input_adapter(IteratorType first, IteratorType last) { - assert(std::accumulate( - first, last, std::pair<bool, int>(true, 0), - [&first](std::pair<bool, int> res, decltype(*first) val) { - res.first &= (val == *(std::next(std::addressof(*first), res.second++))); - return res; - }).first); - - static_assert( - sizeof(typename std::iterator_traits<IteratorType>::value_type) == 1, - "each element in the iterator range must have the size of 1 byte"); - - const auto len = static_cast<size_t>(std::distance(first, last)); - if(JSON_LIKELY(len > 0)) { - ia = std::make_shared<input_buffer_adapter>(reinterpret_cast<const char *>(&(*first)), len); - } else { - ia = std::make_shared<input_buffer_adapter>(nullptr, len); + template<typename BasicJsonType, typename FloatType, + enable_if_t<std::is_floating_point<FloatType>::value, int> = 0> + void to_json(BasicJsonType &j, FloatType val) noexcept { + external_constructor<value_t::number_float>::construct(j, + static_cast<typename BasicJsonType::number_float_t>(val)); } - } - - template<class T, std::size_t N> - input_adapter(T (&array)[N]) - : input_adapter(std::begin(array), std::end(array)) {} - - template<class ContiguousContainer, typename - std::enable_if<not std::is_pointer<ContiguousContainer>::value and - std::is_base_of<std::random_access_iterator_tag, typename std::iterator_traits<decltype(std::begin( - std::declval<ContiguousContainer const>()))>::iterator_category>::value, - int>::type = 0> - input_adapter(const ContiguousContainer &c) - : input_adapter(std::begin(c), std::end(c)) {} - - operator input_adapter_t() { - return ia; - } - -private: - input_adapter_t ia = nullptr; -}; -} -} - -#include <clocale> -#include <cstdlib> -#include <iomanip> -#include <sstream> - -namespace nlohmann { -namespace detail { -template<typename BasicJsonType> -class lexer { - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - -public: - enum class token_type { - uninitialized, - literal_true, - literal_false, - literal_null, - value_string, - value_unsigned, - value_integer, - value_float, - begin_array, - begin_object, - end_array, - end_object, - name_separator, - value_separator, - parse_error, - end_of_input, - literal_or_value - }; - static const char *token_type_name(const token_type t) noexcept { - switch(t) { - case token_type::uninitialized: - return "<uninitialized>"; - case token_type::literal_true: - return "true literal"; - case token_type::literal_false: - return "false literal"; - case token_type::literal_null: - return "null literal"; - case token_type::value_string: - return "string literal"; - case lexer::token_type::value_unsigned: - case lexer::token_type::value_integer: - case lexer::token_type::value_float: - return "number literal"; - case token_type::begin_array: - return "'['"; - case token_type::begin_object: - return "'{'"; - case token_type::end_array: - return "']'"; - case token_type::end_object: - return "'}'"; - case token_type::name_separator: - return "':'"; - case token_type::value_separator: - return "','"; - case token_type::parse_error: - return "<parse error>"; - case token_type::end_of_input: - return "end of input"; - case token_type::literal_or_value: - return "'[', '{', or a literal"; - default: - return "unknown token"; + template<typename BasicJsonType, typename CompatibleNumberUnsignedType, + enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_unsigned_t, CompatibleNumberUnsignedType>::value, int> = 0> + void to_json(BasicJsonType &j, CompatibleNumberUnsignedType val) noexcept { + external_constructor<value_t::number_unsigned>::construct(j, + static_cast<typename BasicJsonType::number_unsigned_t>(val)); } - } - - explicit lexer(detail::input_adapter_t adapter) - : ia(std::move(adapter)), decimal_point_char(get_decimal_point()) {} - - lexer(const lexer &) = delete; - - lexer &operator=(lexer &) = delete; - -private: - static char get_decimal_point() noexcept { - const auto loc = localeconv(); - assert(loc != nullptr); - return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); - } - - int get_codepoint() { - assert(current == 'u'); - int codepoint = 0; - const auto factors = {12, 8, 4, 0}; - for(const auto factor : factors) { - get(); - - if(current >= '0' and current <= '9') { - codepoint += ((current-0x30) << factor); - } else if(current >= 'A' and current <= 'F') { - codepoint += ((current-0x37) << factor); - } else if(current >= 'a' and current <= 'f') { - codepoint += ((current-0x57) << factor); - } else { - return -1; - } + template<typename BasicJsonType, typename CompatibleNumberIntegerType, + enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType>::value, int> = 0> + void to_json(BasicJsonType &j, CompatibleNumberIntegerType val) noexcept { + external_constructor<value_t::number_integer>::construct(j, + static_cast<typename BasicJsonType::number_integer_t>(val)); } - assert(0x0000 <= codepoint and codepoint <= 0xFFFF); - return codepoint; - } - - bool next_byte_in_range(std::initializer_list<int> ranges) { - assert(ranges.size() == 2 or ranges.size() == 4 or ranges.size() == 6); - add(current); - - for(auto range = ranges.begin(); range != ranges.end(); ++range) { - get(); - if(JSON_LIKELY(*range <= current and current <= *(++range))) { - add(current); - } else { - error_message = "invalid string: ill-formed UTF-8 byte"; - return false; - } + template<typename BasicJsonType, typename EnumType, + enable_if_t<std::is_enum<EnumType>::value, int> = 0> + void to_json(BasicJsonType &j, EnumType e) noexcept { + using underlying_type = typename std::underlying_type<EnumType>::type; + external_constructor<value_t::number_integer>::construct(j, static_cast<underlying_type>(e)); } - return true; - } - - token_type scan_string() { - reset(); - - assert(current == '\"'); - - while(true) { - switch(get()) { - case std::char_traits<char>::eof(): { - error_message = "invalid string: missing closing quote"; - return token_type::parse_error; - } - - case '\"': { - return token_type::value_string; - } - - case '\\': { - switch(get()) { - case '\"': - add('\"'); - break; - - case '\\': - add('\\'); - break; - - case '/': - add('/'); - break; - - case 'b': - add('\b'); - break; - - case 'f': - add('\f'); - break; - - case 'n': - add('\n'); - break; - - case 'r': - add('\r'); - break; - - case 't': - add('\t'); - break; - - case 'u': { - const int codepoint1 = get_codepoint(); - int codepoint = codepoint1; - - if(JSON_UNLIKELY(codepoint1 == -1)) { - error_message = "invalid string: '\\u' must be followed by 4 hex digits"; - return token_type::parse_error; - } - - if(0xD800 <= codepoint1 and codepoint1 <= 0xDBFF) { - if(JSON_LIKELY(get() == '\\' and get() == 'u')) { - const int codepoint2 = get_codepoint(); - - if(JSON_UNLIKELY(codepoint2 == -1)) { - error_message = "invalid string: '\\u' must be followed by 4 hex digits"; - return token_type::parse_error; - } - - if(JSON_LIKELY(0xDC00 <= codepoint2 and codepoint2 <= 0xDFFF)) { - codepoint = - - (codepoint1 << 10) - - +codepoint2 - - -0x35FDC00; - } else { - error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF"; - return token_type::parse_error; - } - } else { - error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF"; - return token_type::parse_error; - } - } else { - if(JSON_UNLIKELY(0xDC00 <= codepoint1 and codepoint1 <= 0xDFFF)) { - error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF"; - return token_type::parse_error; - } - } - - assert(0x00 <= codepoint and codepoint <= 0x10FFFF); - - if(codepoint < 0x80) { - add(codepoint); - } else if(codepoint <= 0x7FF) { - add(0xC0 | (codepoint >> 6)); - add(0x80 | (codepoint & 0x3F)); - } else if(codepoint <= 0xFFFF) { - add(0xE0 | (codepoint >> 12)); - add(0x80 | ((codepoint >> 6) & 0x3F)); - add(0x80 | (codepoint & 0x3F)); - } else { - add(0xF0 | (codepoint >> 18)); - add(0x80 | ((codepoint >> 12) & 0x3F)); - add(0x80 | ((codepoint >> 6) & 0x3F)); - add(0x80 | (codepoint & 0x3F)); - } - - break; - } - - default: - error_message = "invalid string: forbidden character after backslash"; - return token_type::parse_error; - } - - break; - } - - case 0x00: - case 0x01: - case 0x02: - case 0x03: - case 0x04: - case 0x05: - case 0x06: - case 0x07: - case 0x08: - case 0x09: - case 0x0A: - case 0x0B: - case 0x0C: - case 0x0D: - case 0x0E: - case 0x0F: - case 0x10: - case 0x11: - case 0x12: - case 0x13: - case 0x14: - case 0x15: - case 0x16: - case 0x17: - case 0x18: - case 0x19: - case 0x1A: - case 0x1B: - case 0x1C: - case 0x1D: - case 0x1E: - case 0x1F: { - error_message = "invalid string: control character must be escaped"; - return token_type::parse_error; - } - - case 0x20: - case 0x21: - case 0x23: - case 0x24: - case 0x25: - case 0x26: - case 0x27: - case 0x28: - case 0x29: - case 0x2A: - case 0x2B: - case 0x2C: - case 0x2D: - case 0x2E: - case 0x2F: - case 0x30: - case 0x31: - case 0x32: - case 0x33: - case 0x34: - case 0x35: - case 0x36: - case 0x37: - case 0x38: - case 0x39: - case 0x3A: - case 0x3B: - case 0x3C: - case 0x3D: - case 0x3E: - case 0x3F: - case 0x40: - case 0x41: - case 0x42: - case 0x43: - case 0x44: - case 0x45: - case 0x46: - case 0x47: - case 0x48: - case 0x49: - case 0x4A: - case 0x4B: - case 0x4C: - case 0x4D: - case 0x4E: - case 0x4F: - case 0x50: - case 0x51: - case 0x52: - case 0x53: - case 0x54: - case 0x55: - case 0x56: - case 0x57: - case 0x58: - case 0x59: - case 0x5A: - case 0x5B: - case 0x5D: - case 0x5E: - case 0x5F: - case 0x60: - case 0x61: - case 0x62: - case 0x63: - case 0x64: - case 0x65: - case 0x66: - case 0x67: - case 0x68: - case 0x69: - case 0x6A: - case 0x6B: - case 0x6C: - case 0x6D: - case 0x6E: - case 0x6F: - case 0x70: - case 0x71: - case 0x72: - case 0x73: - case 0x74: - case 0x75: - case 0x76: - case 0x77: - case 0x78: - case 0x79: - case 0x7A: - case 0x7B: - case 0x7C: - case 0x7D: - case 0x7E: - case 0x7F: { - add(current); - break; - } - - case 0xC2: - case 0xC3: - case 0xC4: - case 0xC5: - case 0xC6: - case 0xC7: - case 0xC8: - case 0xC9: - case 0xCA: - case 0xCB: - case 0xCC: - case 0xCD: - case 0xCE: - case 0xCF: - case 0xD0: - case 0xD1: - case 0xD2: - case 0xD3: - case 0xD4: - case 0xD5: - case 0xD6: - case 0xD7: - case 0xD8: - case 0xD9: - case 0xDA: - case 0xDB: - case 0xDC: - case 0xDD: - case 0xDE: - case 0xDF: { - if(JSON_UNLIKELY(not next_byte_in_range({0x80, 0xBF}))) { - return token_type::parse_error; - } - break; - } - - case 0xE0: { - if(JSON_UNLIKELY(not(next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) { - return token_type::parse_error; - } - break; - } - - case 0xE1: - case 0xE2: - case 0xE3: - case 0xE4: - case 0xE5: - case 0xE6: - case 0xE7: - case 0xE8: - case 0xE9: - case 0xEA: - case 0xEB: - case 0xEC: - case 0xEE: - case 0xEF: { - if(JSON_UNLIKELY(not(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) { - return token_type::parse_error; - } - break; - } - - case 0xED: { - if(JSON_UNLIKELY(not(next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) { - return token_type::parse_error; - } - break; - } - - case 0xF0: { - if(JSON_UNLIKELY(not(next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) { - return token_type::parse_error; - } - break; - } - - case 0xF1: - case 0xF2: - case 0xF3: { - if(JSON_UNLIKELY(not(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) { - return token_type::parse_error; - } - break; - } - - case 0xF4: { - if(JSON_UNLIKELY(not(next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) { - return token_type::parse_error; - } - break; - } - - default: { - error_message = "invalid string: ill-formed UTF-8 byte"; - return token_type::parse_error; - } - } + template<typename BasicJsonType> + void to_json(BasicJsonType &j, const std::vector<bool> &e) { + external_constructor<value_t::array>::construct(j, e); } - } - static void strtof(float &f, const char *str, char **endptr) noexcept { - f = std::strtof(str, endptr); - } - - static void strtof(double &f, const char *str, char **endptr) noexcept { - f = std::strtod(str, endptr); - } - - static void strtof(long double &f, const char *str, char **endptr) noexcept { - f = std::strtold(str, endptr); - } - - token_type scan_number() { - reset(); - - token_type number_type = token_type::value_unsigned; + template<typename BasicJsonType, typename CompatibleArrayType, + enable_if_t<is_compatible_array_type<BasicJsonType, CompatibleArrayType>::value or + std::is_same<typename BasicJsonType::array_t, CompatibleArrayType>::value, + int> = 0> + void to_json(BasicJsonType &j, const CompatibleArrayType &arr) { + external_constructor<value_t::array>::construct(j, arr); + } - switch(current) { - case '-': { - add(current); - goto scan_number_minus; - } + template<typename BasicJsonType, typename T, + enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0> + void to_json(BasicJsonType &j, std::valarray<T> arr) { + external_constructor<value_t::array>::construct(j, std::move(arr)); + } - case '0': { - add(current); - goto scan_number_zero; - } - - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': { - add(current); - goto scan_number_any1; - } + template<typename BasicJsonType> + void to_json(BasicJsonType &j, typename BasicJsonType::array_t &&arr) { + external_constructor<value_t::array>::construct(j, std::move(arr)); + } - default: { - assert(false); - } + template<typename BasicJsonType, typename CompatibleObjectType, + enable_if_t<is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value, int> = 0> + void to_json(BasicJsonType &j, const CompatibleObjectType &obj) { + external_constructor<value_t::object>::construct(j, obj); } - scan_number_minus: + template<typename BasicJsonType> + void to_json(BasicJsonType &j, typename BasicJsonType::object_t &&obj) { + external_constructor<value_t::object>::construct(j, std::move(obj)); + } - number_type = token_type::value_integer; - switch(get()) { - case '0': { - add(current); - goto scan_number_zero; - } - - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': { - add(current); - goto scan_number_any1; - } + template<typename BasicJsonType, typename T, std::size_t N, + enable_if_t<not std::is_constructible<typename BasicJsonType::string_t, T (&)[N]>::value, int> = 0> + void to_json(BasicJsonType &j, T (&arr)[N]) { + external_constructor<value_t::array>::construct(j, arr); + } - default: { - error_message = "invalid number; expected digit after '-'"; - return token_type::parse_error; - } + template<typename BasicJsonType, typename... Args> + void to_json(BasicJsonType &j, const std::pair<Args...> &p) { + j = {p.first, p.second}; } - scan_number_zero: + template<typename BasicJsonType, typename Tuple, std::size_t... Idx> + void to_json_tuple_impl(BasicJsonType &j, const Tuple &t, index_sequence<Idx...>) { + j = {std::get<Idx>(t)...}; + } - switch(get()) { - case '.': { - add(decimal_point_char); - goto scan_number_decimal1; - } + template<typename BasicJsonType, typename... Args> + void to_json(BasicJsonType &j, const std::tuple<Args...> &t) { + to_json_tuple_impl(j, t, index_sequence_for<Args...>{}); + } - case 'e': - case 'E': { - add(current); - goto scan_number_exponent; + struct to_json_fn { + private: + template<typename BasicJsonType, typename T> + auto call(BasicJsonType &j, T &&val, priority_tag<1>) const noexcept(noexcept(to_json(j, std::forward<T>(val)))) + -> decltype(to_json(j, std::forward<T>(val)), void()) { + return to_json(j, std::forward<T>(val)); } - default: - goto scan_number_done; - } - - scan_number_any1: + template<typename BasicJsonType, typename T> + void call(BasicJsonType &, T &&, priority_tag<0>) const noexcept { + static_assert(sizeof(BasicJsonType) == 0, + "could not find to_json() method in T's namespace"); - switch(get()) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': { - add(current); - goto scan_number_any1; - } +#ifdef _MSC_VER - case '.': { - add(decimal_point_char); - goto scan_number_decimal1; + using decayed = uncvref_t<T>; + static_assert(sizeof(typename decayed::force_msvc_stacktrace) == 0, + "forcing MSVC stacktrace to show which T we're talking about."); +#endif } - case 'e': - case 'E': { - add(current); - goto scan_number_exponent; + public: + template<typename BasicJsonType, typename T> + void operator()(BasicJsonType &j, T &&val) const + noexcept(noexcept(std::declval<to_json_fn>().call(j, std::forward<T>(val), priority_tag<1>{}))) { + return call(j, std::forward<T>(val), priority_tag<1>{}); } + }; + } - default: - goto scan_number_done; - } + namespace { + constexpr const auto &to_json = detail::static_const<detail::to_json_fn>::value; + } +} - scan_number_decimal1: +#include <cstring> +#include <ios> +#include <istream> - number_type = token_type::value_float; - switch(get()) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': { - add(current); - goto scan_number_decimal2; - } +namespace nlohmann { + namespace detail { + struct input_adapter_protocol { + virtual std::char_traits<char>::int_type get_character() = 0; - default: { - error_message = "invalid number; expected digit after '.'"; - return token_type::parse_error; - } - } + virtual void unget_character() = 0; - scan_number_decimal2: + virtual ~input_adapter_protocol() = default; + }; - switch(get()) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': { - add(current); - goto scan_number_decimal2; - } + using input_adapter_t = std::shared_ptr<input_adapter_protocol>; - case 'e': - case 'E': { - add(current); - goto scan_number_exponent; + class input_stream_adapter : public input_adapter_protocol { + public: + ~input_stream_adapter() override { + is.clear(); } - default: - goto scan_number_done; - } - - scan_number_exponent: - - number_type = token_type::value_float; - switch(get()) { - case '+': - case '-': { - add(current); - goto scan_number_sign; - } - - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': { - add(current); - goto scan_number_any2; + explicit input_stream_adapter(std::istream &i) + : is(i), sb(*i.rdbuf()) { + std::char_traits<char>::int_type c; + if((c = get_character()) == 0xEF) { + if((c = get_character()) == 0xBB) { + if((c = get_character()) == 0xBF) { + return; + } else if(c != std::char_traits<char>::eof()) { + is.unget(); + } + is.putback('\xBB'); + } else if(c != std::char_traits<char>::eof()) { + is.unget(); + } + is.putback('\xEF'); + } else if(c != std::char_traits<char>::eof()) { + is.unget(); + } } - default: { - error_message = - "invalid number; expected '+', '-', or digit after exponent"; - return token_type::parse_error; - } - } + input_stream_adapter(const input_stream_adapter &) = delete; - scan_number_sign: + input_stream_adapter &operator=(input_stream_adapter &) = delete; - switch(get()) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': { - add(current); - goto scan_number_any2; + std::char_traits<char>::int_type get_character() override { + return sb.sbumpc(); } - default: { - error_message = "invalid number; expected digit after exponent sign"; - return token_type::parse_error; + void unget_character() override { + sb.sungetc(); } - } - scan_number_any2: + private: + std::istream &is; + std::streambuf &sb; + }; - switch(get()) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': { - add(current); - goto scan_number_any2; + class input_buffer_adapter : public input_adapter_protocol { + public: + input_buffer_adapter(const char *b, const std::size_t l) + : cursor(b), limit(b+l), start(b) { + if(l >= 3 and b[0] == '\xEF' and b[1] == '\xBB' and b[2] == '\xBF') { + cursor += 3; + } } - default: - goto scan_number_done; - } - - scan_number_done: - - unget(); + input_buffer_adapter(const input_buffer_adapter &) = delete; - char *endptr = nullptr; - errno = 0; + input_buffer_adapter &operator=(input_buffer_adapter &) = delete; - if(number_type == token_type::value_unsigned) { - const auto x = std::strtoull(token_buffer.data(), &endptr, 10); - - assert(endptr == token_buffer.data()+token_buffer.size()); - - if(errno == 0) { - value_unsigned = static_cast<number_unsigned_t>(x); - if(value_unsigned == x) { - return token_type::value_unsigned; + std::char_traits<char>::int_type get_character() noexcept override { + if(JSON_LIKELY(cursor < limit)) { + return std::char_traits<char>::to_int_type(*(cursor++)); } - } - } else if(number_type == token_type::value_integer) { - const auto x = std::strtoll(token_buffer.data(), &endptr, 10); - assert(endptr == token_buffer.data()+token_buffer.size()); + return std::char_traits<char>::eof(); + } - if(errno == 0) { - value_integer = static_cast<number_integer_t>(x); - if(value_integer == x) { - return token_type::value_integer; + void unget_character() noexcept override { + if(JSON_LIKELY(cursor > start)) { + --cursor; } } - } - strtof(value_float, token_buffer.data(), &endptr); + private: + const char *cursor; - assert(endptr == token_buffer.data()+token_buffer.size()); + const char *limit; - return token_type::value_float; - } + const char *start; + }; - token_type scan_literal(const char *literal_text, const std::size_t length, - token_type return_type) { - assert(current == literal_text[0]); - for(std::size_t i = 1; i < length; ++i) { - if(JSON_UNLIKELY(get() != literal_text[i])) { - error_message = "invalid literal"; - return token_type::parse_error; + class input_adapter { + public: + input_adapter(std::istream &i) + : ia(std::make_shared<input_stream_adapter>(i)) {} + + input_adapter(std::istream &&i) + : ia(std::make_shared<input_stream_adapter>(i)) {} + + template<typename CharT, + typename std::enable_if< + std::is_pointer<CharT>::value and + std::is_integral<typename std::remove_pointer<CharT>::type>::value and + sizeof(typename std::remove_pointer<CharT>::type) == 1, + int>::type = 0> + input_adapter(CharT b, std::size_t l) + : ia(std::make_shared<input_buffer_adapter>(reinterpret_cast<const char *>(b), l)) {} + + template<typename CharT, + typename std::enable_if< + std::is_pointer<CharT>::value and + std::is_integral<typename std::remove_pointer<CharT>::type>::value and + sizeof(typename std::remove_pointer<CharT>::type) == 1, + int>::type = 0> + input_adapter(CharT b) + : input_adapter(reinterpret_cast<const char *>(b), + std::strlen(reinterpret_cast<const char *>(b))) {} + + template<class IteratorType, + typename std::enable_if< + std::is_same<typename std::iterator_traits<IteratorType>::iterator_category, std::random_access_iterator_tag>::value, + int>::type = 0> + input_adapter(IteratorType first, IteratorType last) { + assert(std::accumulate( + first, last, std::pair<bool, int>(true, 0), + [&first](std::pair<bool, int> res, decltype(*first) val) { + res.first &= (val == *(std::next(std::addressof(*first), res.second++))); + return res; + }).first); + + static_assert( + sizeof(typename std::iterator_traits<IteratorType>::value_type) == 1, + "each element in the iterator range must have the size of 1 byte"); + + const auto len = static_cast<size_t>(std::distance(first, last)); + if(JSON_LIKELY(len > 0)) { + ia = std::make_shared<input_buffer_adapter>(reinterpret_cast<const char *>(&(*first)), len); + } else { + ia = std::make_shared<input_buffer_adapter>(nullptr, len); + } } - } - return return_type; - } - - void reset() noexcept { - token_buffer.clear(); - token_string.clear(); - token_string.push_back(std::char_traits<char>::to_char_type(current)); - } - - std::char_traits<char>::int_type get() { - ++chars_read; - current = ia->get_character(); - if(JSON_LIKELY(current != std::char_traits<char>::eof())) { - token_string.push_back(std::char_traits<char>::to_char_type(current)); - } - return current; - } - void unget() { - --chars_read; - if(JSON_LIKELY(current != std::char_traits<char>::eof())) { - ia->unget_character(); - assert(token_string.size() != 0); - token_string.pop_back(); - } - } - - void add(int c) { - token_buffer.push_back(std::char_traits<char>::to_char_type(c)); - } - -public: - constexpr number_integer_t get_number_integer() const noexcept { - return value_integer; - } - - constexpr number_unsigned_t get_number_unsigned() const noexcept { - return value_unsigned; - } + template<class T, std::size_t N> + input_adapter(T (&array)[N]) + : input_adapter(std::begin(array), std::end(array)) {} - constexpr number_float_t get_number_float() const noexcept { - return value_float; - } - - string_t &&move_string() { - return std::move(token_buffer); - } - - constexpr std::size_t get_position() const noexcept { - return chars_read; - } + template<class ContiguousContainer, typename + std::enable_if<not std::is_pointer<ContiguousContainer>::value and + std::is_base_of<std::random_access_iterator_tag, typename std::iterator_traits<decltype(std::begin( + std::declval<ContiguousContainer const>()))>::iterator_category>::value, + int>::type = 0> + input_adapter(const ContiguousContainer &c) + : input_adapter(std::begin(c), std::end(c)) {} - std::string get_token_string() const { - std::string result; - for(const auto c : token_string) { - if('\x00' <= c and c <= '\x1F') { - std::stringstream ss; - ss << "<U+" << std::setw(4) << std::uppercase << std::setfill('0') - << std::hex << static_cast<int>(c) << ">"; - result += ss.str(); - } else { - result.push_back(c); + operator input_adapter_t() { + return ia; } - } - return result; - } - - constexpr const char *get_error_message() const noexcept { - return error_message; - } - - token_type scan() { - do { - get(); - } while(current == ' ' or current == '\t' or current == '\n' or current == '\r'); - - switch(current) { - case '[': - return token_type::begin_array; - case ']': - return token_type::end_array; - case '{': - return token_type::begin_object; - case '}': - return token_type::end_object; - case ':': - return token_type::name_separator; - case ',': - return token_type::value_separator; - - case 't': - return scan_literal("true", 4, token_type::literal_true); - case 'f': - return scan_literal("false", 5, token_type::literal_false); - case 'n': - return scan_literal("null", 4, token_type::literal_null); - - case '\"': - return scan_string(); - - case '-': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - return scan_number(); - - case '\0': - case std::char_traits<char>::eof(): - return token_type::end_of_input; - - default: - error_message = "invalid literal"; - return token_type::parse_error; - } + private: + input_adapter_t ia = nullptr; + }; } - -private: - detail::input_adapter_t ia = nullptr; - - std::char_traits<char>::int_type current = std::char_traits<char>::eof(); - - std::size_t chars_read = 0; - - std::vector<char> token_string{}; - - string_t token_buffer{}; - - const char *error_message = ""; - - number_integer_t value_integer = 0; - number_unsigned_t value_unsigned = 0; - number_float_t value_float = 0; - - const char decimal_point_char = '.'; -}; -} } -#include <cmath> +#include <clocale> +#include <cstdlib> +#include <iomanip> +#include <sstream> namespace nlohmann { -namespace detail { -template<typename BasicJsonType> -class parser { - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using lexer_t = lexer<BasicJsonType>; - using token_type = typename lexer_t::token_type; - -public: - enum class parse_event_t : uint8_t { - object_start, - - object_end, - - array_start, - - array_end, - - key, - - value - }; - - using parser_callback_t = - std::function<bool(int depth, parse_event_t event, BasicJsonType &parsed)>; - - explicit parser(detail::input_adapter_t adapter, - const parser_callback_t cb = nullptr, - const bool allow_exceptions_ = true) - : callback(cb), m_lexer(adapter), allow_exceptions(allow_exceptions_) {} - - void parse(const bool strict, BasicJsonType &result) { - get_token(); - - parse_internal(true, result); - result.assert_invariant(); - - if(strict) { - get_token(); - expect(token_type::end_of_input); - } - - if(errored) { - result = value_t::discarded; - return; - } - - if(result.is_discarded()) { - result = nullptr; - } - } - - bool accept(const bool strict = true) { - get_token(); - - if(not accept_internal()) { - return false; - } - - return not strict or (get_token() == token_type::end_of_input); - } - -private: - void parse_internal(bool keep, BasicJsonType &result) { - assert(not errored); - - if(not result.is_discarded()) { - result.m_value.destroy(result.m_type); - result.m_type = value_t::discarded; - } - - switch(last_token) { - case token_type::begin_object: { - if(keep) { - if(callback) { - keep = callback(depth++, parse_event_t::object_start, result); - } - - if(not callback or keep) { - result.m_type = value_t::object; - result.m_value = value_t::object; - } - } - - get_token(); + namespace detail { + template<typename BasicJsonType> + class lexer { + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + + public: + enum class token_type { + uninitialized, + literal_true, + literal_false, + literal_null, + value_string, + value_unsigned, + value_integer, + value_float, + begin_array, + begin_object, + end_array, + end_object, + name_separator, + value_separator, + parse_error, + end_of_input, + literal_or_value + }; - if(last_token == token_type::end_object) { - if(keep and callback and not callback(--depth, parse_event_t::object_end, result)) { - result.m_value.destroy(result.m_type); - result.m_type = value_t::discarded; - } - break; + static const char *token_type_name(const token_type t) noexcept { + switch(t) { + case token_type::uninitialized: + return "<uninitialized>"; + case token_type::literal_true: + return "true literal"; + case token_type::literal_false: + return "false literal"; + case token_type::literal_null: + return "null literal"; + case token_type::value_string: + return "string literal"; + case lexer::token_type::value_unsigned: + case lexer::token_type::value_integer: + case lexer::token_type::value_float: + return "number literal"; + case token_type::begin_array: + return "'['"; + case token_type::begin_object: + return "'{'"; + case token_type::end_array: + return "']'"; + case token_type::end_object: + return "'}'"; + case token_type::name_separator: + return "':'"; + case token_type::value_separator: + return "','"; + case token_type::parse_error: + return "<parse error>"; + case token_type::end_of_input: + return "end of input"; + case token_type::literal_or_value: + return "'[', '{', or a literal"; + default: + return "unknown token"; } + } - string_t key; - BasicJsonType value; - while(true) { - if(not expect(token_type::value_string)) { - return; - } - key = m_lexer.move_string(); - - bool keep_tag = false; - if(keep) { - if(callback) { - BasicJsonType k(key); - keep_tag = callback(depth, parse_event_t::key, k); - } else { - keep_tag = true; - } - } - - get_token(); - if(not expect(token_type::name_separator)) { - return; - } - - get_token(); - value.m_value.destroy(value.m_type); - value.m_type = value_t::discarded; - parse_internal(keep, value); - - if(JSON_UNLIKELY(errored)) { - return; - } - - if(keep and keep_tag and not value.is_discarded()) { - result.m_value.object->emplace(std::move(key), std::move(value)); - } + explicit lexer(detail::input_adapter_t adapter) + : ia(std::move(adapter)), decimal_point_char(get_decimal_point()) {} - get_token(); - if(last_token == token_type::value_separator) { - get_token(); - continue; - } + lexer(const lexer &) = delete; - if(not expect(token_type::end_object)) { - return; - } - break; - } + lexer &operator=(lexer &) = delete; - if(keep and callback and not callback(--depth, parse_event_t::object_end, result)) { - result.m_value.destroy(result.m_type); - result.m_type = value_t::discarded; - } - break; + private: + static char get_decimal_point() noexcept { + const auto loc = localeconv(); + assert(loc != nullptr); + return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); } - case token_type::begin_array: { - if(keep) { - if(callback) { - keep = callback(depth++, parse_event_t::array_start, result); - } - - if(not callback or keep) { - result.m_type = value_t::array; - result.m_value = value_t::array; - } - } + int get_codepoint() { + assert(current == 'u'); + int codepoint = 0; - get_token(); + const auto factors = {12, 8, 4, 0}; + for(const auto factor : factors) { + get(); - if(last_token == token_type::end_array) { - if(callback and not callback(--depth, parse_event_t::array_end, result)) { - result.m_value.destroy(result.m_type); - result.m_type = value_t::discarded; + if(current >= '0' and current <= '9') { + codepoint += ((current-0x30) << factor); + } else if(current >= 'A' and current <= 'F') { + codepoint += ((current-0x37) << factor); + } else if(current >= 'a' and current <= 'f') { + codepoint += ((current-0x57) << factor); + } else { + return -1; } - break; } - BasicJsonType value; - while(true) { - value.m_value.destroy(value.m_type); - value.m_type = value_t::discarded; - parse_internal(keep, value); - - if(JSON_UNLIKELY(errored)) { - return; - } - - if(keep and not value.is_discarded()) { - result.m_value.array->push_back(std::move(value)); - } + assert(0x0000 <= codepoint and codepoint <= 0xFFFF); + return codepoint; + } - get_token(); - if(last_token == token_type::value_separator) { - get_token(); - continue; - } + bool next_byte_in_range(std::initializer_list<int> ranges) { + assert(ranges.size() == 2 or ranges.size() == 4 or ranges.size() == 6); + add(current); - if(not expect(token_type::end_array)) { - return; + for(auto range = ranges.begin(); range != ranges.end(); ++range) { + get(); + if(JSON_LIKELY(*range <= current and current <= *(++range))) { + add(current); + } else { + error_message = "invalid string: ill-formed UTF-8 byte"; + return false; } - break; - } - - if(keep and callback and not callback(--depth, parse_event_t::array_end, result)) { - result.m_value.destroy(result.m_type); - result.m_type = value_t::discarded; } - break; - } - - case token_type::literal_null: { - result.m_type = value_t::null; - break; - } - - case token_type::value_string: { - result.m_type = value_t::string; - result.m_value = m_lexer.move_string(); - break; - } - - case token_type::literal_true: { - result.m_type = value_t::boolean; - result.m_value = true; - break; - } - - case token_type::literal_false: { - result.m_type = value_t::boolean; - result.m_value = false; - break; - } - - case token_type::value_unsigned: { - result.m_type = value_t::number_unsigned; - result.m_value = m_lexer.get_number_unsigned(); - break; - } - case token_type::value_integer: { - result.m_type = value_t::number_integer; - result.m_value = m_lexer.get_number_integer(); - break; + return true; } - case token_type::value_float: { - result.m_type = value_t::number_float; - result.m_value = m_lexer.get_number_float(); + token_type scan_string() { + reset(); - if(JSON_UNLIKELY(not std::isfinite(result.m_value.number_float))) { - if(allow_exceptions) { - JSON_THROW(out_of_range::create(406, "number overflow parsing '"+ - m_lexer.get_token_string()+"'")); - } - expect(token_type::uninitialized); - } - break; - } + assert(current == '\"'); - case token_type::parse_error: { - if(not expect(token_type::uninitialized)) { - return; - } - break; - } + while(true) { + switch(get()) { + case std::char_traits<char>::eof(): { + error_message = "invalid string: missing closing quote"; + return token_type::parse_error; + } - default: { - if(not expect(token_type::literal_or_value)) { - return; - } - break; - } - } + case '\"': { + return token_type::value_string; + } - if(keep and callback and not callback(depth, parse_event_t::value, result)) { - result.m_value.destroy(result.m_type); - result.m_type = value_t::discarded; - } - } + case '\\': { + switch(get()) { + case '\"': + add('\"'); + break; - bool accept_internal() { - switch(last_token) { - case token_type::begin_object: { - get_token(); + case '\\': + add('\\'); + break; - if(last_token == token_type::end_object) { - return true; - } + case '/': + add('/'); + break; - while(true) { - if(last_token != token_type::value_string) { - return false; - } + case 'b': + add('\b'); + break; - get_token(); - if(last_token != token_type::name_separator) { - return false; - } + case 'f': + add('\f'); + break; - get_token(); - if(not accept_internal()) { - return false; - } + case 'n': + add('\n'); + break; - get_token(); - if(last_token == token_type::value_separator) { - get_token(); - continue; - } + case 'r': + add('\r'); + break; - return (last_token == token_type::end_object); - } - } + case 't': + add('\t'); + break; - case token_type::begin_array: { - get_token(); + case 'u': { + const int codepoint1 = get_codepoint(); + int codepoint = codepoint1; - if(last_token == token_type::end_array) { - return true; - } + if(JSON_UNLIKELY(codepoint1 == -1)) { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } - while(true) { - if(not accept_internal()) { - return false; - } + if(0xD800 <= codepoint1 and codepoint1 <= 0xDBFF) { + if(JSON_LIKELY(get() == '\\' and get() == 'u')) { + const int codepoint2 = get_codepoint(); - get_token(); - if(last_token == token_type::value_separator) { - get_token(); - continue; - } + if(JSON_UNLIKELY(codepoint2 == -1)) { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } - return (last_token == token_type::end_array); - } - } + if(JSON_LIKELY(0xDC00 <= codepoint2 and codepoint2 <= 0xDFFF)) { + codepoint = - case token_type::value_float: { - return std::isfinite(m_lexer.get_number_float()); - } + (codepoint1 << 10) - case token_type::literal_false: - case token_type::literal_null: - case token_type::literal_true: - case token_type::value_integer: - case token_type::value_string: - case token_type::value_unsigned: - return true; + +codepoint2 - default: - return false; - } - } + -0x35FDC00; + } else { + error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } else { + error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } else { + if(JSON_UNLIKELY(0xDC00 <= codepoint1 and codepoint1 <= 0xDFFF)) { + error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF"; + return token_type::parse_error; + } + } - token_type get_token() { - return (last_token = m_lexer.scan()); - } + assert(0x00 <= codepoint and codepoint <= 0x10FFFF); + + if(codepoint < 0x80) { + add(codepoint); + } else if(codepoint <= 0x7FF) { + add(0xC0 | (codepoint >> 6)); + add(0x80 | (codepoint & 0x3F)); + } else if(codepoint <= 0xFFFF) { + add(0xE0 | (codepoint >> 12)); + add(0x80 | ((codepoint >> 6) & 0x3F)); + add(0x80 | (codepoint & 0x3F)); + } else { + add(0xF0 | (codepoint >> 18)); + add(0x80 | ((codepoint >> 12) & 0x3F)); + add(0x80 | ((codepoint >> 6) & 0x3F)); + add(0x80 | (codepoint & 0x3F)); + } - bool expect(token_type t) { - if(JSON_UNLIKELY(t != last_token)) { - errored = true; - expected = t; - if(allow_exceptions) { - throw_exception(); - } else { - return false; - } - } + break; + } - return true; - } + default: + error_message = "invalid string: forbidden character after backslash"; + return token_type::parse_error; + } - [[noreturn]] void throw_exception() const { - std::string error_msg = "syntax error - "; - if(last_token == token_type::parse_error) { - error_msg += std::string(m_lexer.get_error_message())+"; last read: '"+ - m_lexer.get_token_string()+"'"; - } else { - error_msg += "unexpected "+std::string(lexer_t::token_type_name(last_token)); - } + break; + } - if(expected != token_type::uninitialized) { - error_msg += "; expected "+std::string(lexer_t::token_type_name(expected)); - } + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: { + error_message = "invalid string: control character must be escaped"; + return token_type::parse_error; + } - JSON_THROW(parse_error::create(101, m_lexer.get_position(), error_msg)); - } + case 0x20: + case 0x21: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5D: + case 0x5E: + case 0x5F: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: { + add(current); + break; + } -private: - int depth = 0; + case 0xC2: + case 0xC3: + case 0xC4: + case 0xC5: + case 0xC6: + case 0xC7: + case 0xC8: + case 0xC9: + case 0xCA: + case 0xCB: + case 0xCC: + case 0xCD: + case 0xCE: + case 0xCF: + case 0xD0: + case 0xD1: + case 0xD2: + case 0xD3: + case 0xD4: + case 0xD5: + case 0xD6: + case 0xD7: + case 0xD8: + case 0xD9: + case 0xDA: + case 0xDB: + case 0xDC: + case 0xDD: + case 0xDE: + case 0xDF: { + if(JSON_UNLIKELY(not next_byte_in_range({0x80, 0xBF}))) { + return token_type::parse_error; + } + break; + } - const parser_callback_t callback = nullptr; + case 0xE0: { + if(JSON_UNLIKELY(not(next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) { + return token_type::parse_error; + } + break; + } - token_type last_token = token_type::uninitialized; + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xEE: + case 0xEF: { + if(JSON_UNLIKELY(not(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) { + return token_type::parse_error; + } + break; + } - lexer_t m_lexer; + case 0xED: { + if(JSON_UNLIKELY(not(next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) { + return token_type::parse_error; + } + break; + } - bool errored = false; + case 0xF0: { + if(JSON_UNLIKELY(not(next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) { + return token_type::parse_error; + } + break; + } - token_type expected = token_type::uninitialized; + case 0xF1: + case 0xF2: + case 0xF3: { + if(JSON_UNLIKELY(not(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) { + return token_type::parse_error; + } + break; + } - const bool allow_exceptions = true; -}; -} -} + case 0xF4: { + if(JSON_UNLIKELY(not(next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) { + return token_type::parse_error; + } + break; + } -namespace nlohmann { -namespace detail { -class primitive_iterator_t { -private: - using difference_type = std::ptrdiff_t; - static constexpr difference_type begin_value = 0; - static constexpr difference_type end_value = begin_value+1; - - difference_type m_it = (std::numeric_limits<std::ptrdiff_t>::min)(); - -public: - constexpr difference_type get_value() const noexcept { - return m_it; - } + default: { + error_message = "invalid string: ill-formed UTF-8 byte"; + return token_type::parse_error; + } + } + } + } - void set_begin() noexcept { - m_it = begin_value; - } + static void strtof(float &f, const char *str, char **endptr) noexcept { + f = std::strtof(str, endptr); + } - void set_end() noexcept { - m_it = end_value; - } + static void strtof(double &f, const char *str, char **endptr) noexcept { + f = std::strtod(str, endptr); + } - constexpr bool is_begin() const noexcept { - return m_it == begin_value; - } + static void strtof(long double &f, const char *str, char **endptr) noexcept { + f = std::strtold(str, endptr); + } - constexpr bool is_end() const noexcept { - return m_it == end_value; - } + token_type scan_number() { + reset(); - friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { - return lhs.m_it == rhs.m_it; - } + token_type number_type = token_type::value_unsigned; - friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { - return lhs.m_it < rhs.m_it; - } + switch(current) { + case '-': { + add(current); + goto scan_number_minus; + } - primitive_iterator_t operator+(difference_type n) noexcept { - auto result = *this; - result += n; - return result; - } + case '0': { + add(current); + goto scan_number_zero; + } - friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { - return lhs.m_it-rhs.m_it; - } + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + add(current); + goto scan_number_any1; + } - primitive_iterator_t &operator++() noexcept { - ++m_it; - return *this; - } + default: { + assert(false); + } + } - primitive_iterator_t const operator++(int) noexcept { - auto result = *this; - m_it++; - return result; - } + scan_number_minus: - primitive_iterator_t &operator--() noexcept { - --m_it; - return *this; - } + number_type = token_type::value_integer; + switch(get()) { + case '0': { + add(current); + goto scan_number_zero; + } - primitive_iterator_t const operator--(int) noexcept { - auto result = *this; - m_it--; - return result; - } + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + add(current); + goto scan_number_any1; + } - primitive_iterator_t &operator+=(difference_type n) noexcept { - m_it += n; - return *this; - } + default: { + error_message = "invalid number; expected digit after '-'"; + return token_type::parse_error; + } + } - primitive_iterator_t &operator-=(difference_type n) noexcept { - m_it -= n; - return *this; - } -}; -} -} + scan_number_zero: -namespace nlohmann { -namespace detail { -template<typename BasicJsonType> -struct internal_iterator { - typename BasicJsonType::object_t::iterator object_iterator{}; + switch(get()) { + case '.': { + add(decimal_point_char); + goto scan_number_decimal1; + } - typename BasicJsonType::array_t::iterator array_iterator{}; + case 'e': + case 'E': { + add(current); + goto scan_number_exponent; + } - primitive_iterator_t primitive_iterator{}; -}; -} -} + default: + goto scan_number_done; + } -namespace nlohmann { -namespace detail { -template<typename IteratorType> -class iteration_proxy; + scan_number_any1: + + switch(get()) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + add(current); + goto scan_number_any1; + } -template<typename BasicJsonType> -class iter_impl { - friend iter_impl<typename std::conditional<std::is_const<BasicJsonType>::value, typename std::remove_const<BasicJsonType>::type, const BasicJsonType>::type>; - friend BasicJsonType; - friend iteration_proxy<iter_impl>; + case '.': { + add(decimal_point_char); + goto scan_number_decimal1; + } - using object_t = typename BasicJsonType::object_t; - using array_t = typename BasicJsonType::array_t; + case 'e': + case 'E': { + add(current); + goto scan_number_exponent; + } - static_assert(is_basic_json<typename std::remove_const<BasicJsonType>::type>::value, - "iter_impl only accepts (const) basic_json"); + default: + goto scan_number_done; + } -public: - using iterator_category = std::bidirectional_iterator_tag; + scan_number_decimal1: + + number_type = token_type::value_float; + switch(get()) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + add(current); + goto scan_number_decimal2; + } - using value_type = typename BasicJsonType::value_type; + default: { + error_message = "invalid number; expected digit after '.'"; + return token_type::parse_error; + } + } - using difference_type = typename BasicJsonType::difference_type; + scan_number_decimal2: + + switch(get()) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + add(current); + goto scan_number_decimal2; + } - using pointer = typename std::conditional<std::is_const<BasicJsonType>::value, - typename BasicJsonType::const_pointer, - typename BasicJsonType::pointer>::type; + case 'e': + case 'E': { + add(current); + goto scan_number_exponent; + } - using reference = - typename std::conditional<std::is_const<BasicJsonType>::value, - typename BasicJsonType::const_reference, - typename BasicJsonType::reference>::type; + default: + goto scan_number_done; + } - iter_impl() = default; + scan_number_exponent: - explicit iter_impl(pointer object) noexcept : m_object(object) { - assert(m_object != nullptr); + number_type = token_type::value_float; + switch(get()) { + case '+': + case '-': { + add(current); + goto scan_number_sign; + } - switch(m_object->m_type) { - case value_t::object: { - m_it.object_iterator = typename object_t::iterator(); - break; - } + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + add(current); + goto scan_number_any2; + } - case value_t::array: { - m_it.array_iterator = typename array_t::iterator(); - break; - } + default: { + error_message = + "invalid number; expected '+', '-', or digit after exponent"; + return token_type::parse_error; + } + } - default: { - m_it.primitive_iterator = primitive_iterator_t(); - break; - } - } - } + scan_number_sign: + + switch(get()) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + add(current); + goto scan_number_any2; + } - iter_impl(const iter_impl<typename std::remove_const<BasicJsonType>::type> &other) noexcept - : m_object(other.m_object), m_it(other.m_it) {} + default: { + error_message = "invalid number; expected digit after exponent sign"; + return token_type::parse_error; + } + } - iter_impl &operator=(const iter_impl<typename std::remove_const<BasicJsonType>::type> &other) noexcept { - m_object = other.m_object; - m_it = other.m_it; - return *this; - } + scan_number_any2: + + switch(get()) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + add(current); + goto scan_number_any2; + } -private: - void set_begin() noexcept { - assert(m_object != nullptr); + default: + goto scan_number_done; + } - switch(m_object->m_type) { - case value_t::object: { - m_it.object_iterator = m_object->m_value.object->begin(); - break; - } + scan_number_done: - case value_t::array: { - m_it.array_iterator = m_object->m_value.array->begin(); - break; - } + unget(); - case value_t::null: { - m_it.primitive_iterator.set_end(); - break; - } + char *endptr = nullptr; + errno = 0; - default: { - m_it.primitive_iterator.set_begin(); - break; - } - } - } + if(number_type == token_type::value_unsigned) { + const auto x = std::strtoull(token_buffer.data(), &endptr, 10); - void set_end() noexcept { - assert(m_object != nullptr); + assert(endptr == token_buffer.data()+token_buffer.size()); - switch(m_object->m_type) { - case value_t::object: { - m_it.object_iterator = m_object->m_value.object->end(); - break; - } + if(errno == 0) { + value_unsigned = static_cast<number_unsigned_t>(x); + if(value_unsigned == x) { + return token_type::value_unsigned; + } + } + } else if(number_type == token_type::value_integer) { + const auto x = std::strtoll(token_buffer.data(), &endptr, 10); - case value_t::array: { - m_it.array_iterator = m_object->m_value.array->end(); - break; - } + assert(endptr == token_buffer.data()+token_buffer.size()); - default: { - m_it.primitive_iterator.set_end(); - break; - } - } - } + if(errno == 0) { + value_integer = static_cast<number_integer_t>(x); + if(value_integer == x) { + return token_type::value_integer; + } + } + } -public: - reference operator*() const { - assert(m_object != nullptr); + strtof(value_float, token_buffer.data(), &endptr); - switch(m_object->m_type) { - case value_t::object: { - assert(m_it.object_iterator != m_object->m_value.object->end()); - return m_it.object_iterator->second; - } + assert(endptr == token_buffer.data()+token_buffer.size()); - case value_t::array: { - assert(m_it.array_iterator != m_object->m_value.array->end()); - return *m_it.array_iterator; + return token_type::value_float; } - case value_t::null: - JSON_THROW(invalid_iterator::create(214, "cannot get value")); - - default: { - if(JSON_LIKELY(m_it.primitive_iterator.is_begin())) { - return *m_object; + token_type scan_literal(const char *literal_text, const std::size_t length, + token_type return_type) { + assert(current == literal_text[0]); + for(std::size_t i = 1; i < length; ++i) { + if(JSON_UNLIKELY(get() != literal_text[i])) { + error_message = "invalid literal"; + return token_type::parse_error; + } } - - JSON_THROW(invalid_iterator::create(214, "cannot get value")); + return return_type; } - } - } - - pointer operator->() const { - assert(m_object != nullptr); - switch(m_object->m_type) { - case value_t::object: { - assert(m_it.object_iterator != m_object->m_value.object->end()); - return &(m_it.object_iterator->second); + void reset() noexcept { + token_buffer.clear(); + token_string.clear(); + token_string.push_back(std::char_traits<char>::to_char_type(current)); } - case value_t::array: { - assert(m_it.array_iterator != m_object->m_value.array->end()); - return &*m_it.array_iterator; + std::char_traits<char>::int_type get() { + ++chars_read; + current = ia->get_character(); + if(JSON_LIKELY(current != std::char_traits<char>::eof())) { + token_string.push_back(std::char_traits<char>::to_char_type(current)); + } + return current; } - default: { - if(JSON_LIKELY(m_it.primitive_iterator.is_begin())) { - return m_object; + void unget() { + --chars_read; + if(JSON_LIKELY(current != std::char_traits<char>::eof())) { + ia->unget_character(); + assert(token_string.size() != 0); + token_string.pop_back(); } - - JSON_THROW(invalid_iterator::create(214, "cannot get value")); } - } - } - iter_impl const operator++(int) { - auto result = *this; - ++(*this); - return result; - } - - iter_impl &operator++() { - assert(m_object != nullptr); - - switch(m_object->m_type) { - case value_t::object: { - std::advance(m_it.object_iterator, 1); - break; + void add(int c) { + token_buffer.push_back(std::char_traits<char>::to_char_type(c)); } - case value_t::array: { - std::advance(m_it.array_iterator, 1); - break; + public: + constexpr number_integer_t get_number_integer() const noexcept { + return value_integer; } - default: { - ++m_it.primitive_iterator; - break; + constexpr number_unsigned_t get_number_unsigned() const noexcept { + return value_unsigned; } - } - - return *this; - } - - iter_impl const operator--(int) { - auto result = *this; - --(*this); - return result; - } - iter_impl &operator--() { - assert(m_object != nullptr); - - switch(m_object->m_type) { - case value_t::object: { - std::advance(m_it.object_iterator, -1); - break; + constexpr number_float_t get_number_float() const noexcept { + return value_float; } - case value_t::array: { - std::advance(m_it.array_iterator, -1); - break; + string_t &&move_string() { + return std::move(token_buffer); } - default: { - --m_it.primitive_iterator; - break; + constexpr std::size_t get_position() const noexcept { + return chars_read; } - } - - return *this; - } - - bool operator==(const iter_impl &other) const { - if(JSON_UNLIKELY(m_object != other.m_object)) { - JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); - } - - assert(m_object != nullptr); - - switch(m_object->m_type) { - case value_t::object: - return (m_it.object_iterator == other.m_it.object_iterator); - - case value_t::array: - return (m_it.array_iterator == other.m_it.array_iterator); - - default: - return (m_it.primitive_iterator == other.m_it.primitive_iterator); - } - } - - bool operator!=(const iter_impl &other) const { - return not operator==(other); - } - - bool operator<(const iter_impl &other) const { - if(JSON_UNLIKELY(m_object != other.m_object)) { - JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); - } - - assert(m_object != nullptr); - - switch(m_object->m_type) { - case value_t::object: - JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators")); - - case value_t::array: - return (m_it.array_iterator < other.m_it.array_iterator); - - default: - return (m_it.primitive_iterator < other.m_it.primitive_iterator); - } - } - - bool operator<=(const iter_impl &other) const { - return not other.operator<(*this); - } - bool operator>(const iter_impl &other) const { - return not operator<=(other); - } - - bool operator>=(const iter_impl &other) const { - return not operator<(other); - } - - iter_impl &operator+=(difference_type i) { - assert(m_object != nullptr); - - switch(m_object->m_type) { - case value_t::object: - JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); + std::string get_token_string() const { + std::string result; + for(const auto c : token_string) { + if('\x00' <= c and c <= '\x1F') { + std::stringstream ss; + ss << "<U+" << std::setw(4) << std::uppercase << std::setfill('0') + << std::hex << static_cast<int>(c) << ">"; + result += ss.str(); + } else { + result.push_back(c); + } + } - case value_t::array: { - std::advance(m_it.array_iterator, i); - break; + return result; } - default: { - m_it.primitive_iterator += i; - break; + constexpr const char *get_error_message() const noexcept { + return error_message; } - } - return *this; - } + token_type scan() { + do { + get(); + } while(current == ' ' or current == '\t' or current == '\n' or current == '\r'); + + switch(current) { + case '[': + return token_type::begin_array; + case ']': + return token_type::end_array; + case '{': + return token_type::begin_object; + case '}': + return token_type::end_object; + case ':': + return token_type::name_separator; + case ',': + return token_type::value_separator; + + case 't': + return scan_literal("true", 4, token_type::literal_true); + case 'f': + return scan_literal("false", 5, token_type::literal_false); + case 'n': + return scan_literal("null", 4, token_type::literal_null); + + case '\"': + return scan_string(); + + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return scan_number(); + + case '\0': + case std::char_traits<char>::eof(): + return token_type::end_of_input; + + default: + error_message = "invalid literal"; + return token_type::parse_error; + } + } - iter_impl &operator-=(difference_type i) { - return operator+=(-i); - } + private: + detail::input_adapter_t ia = nullptr; - iter_impl operator+(difference_type i) const { - auto result = *this; - result += i; - return result; - } + std::char_traits<char>::int_type current = std::char_traits<char>::eof(); - friend iter_impl operator+(difference_type i, const iter_impl &it) { - auto result = it; - result += i; - return result; - } + std::size_t chars_read = 0; - iter_impl operator-(difference_type i) const { - auto result = *this; - result -= i; - return result; - } + std::vector<char> token_string{}; - difference_type operator-(const iter_impl &other) const { - assert(m_object != nullptr); + string_t token_buffer{}; - switch(m_object->m_type) { - case value_t::object: - JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); + const char *error_message = ""; - case value_t::array: - return m_it.array_iterator-other.m_it.array_iterator; + number_integer_t value_integer = 0; + number_unsigned_t value_unsigned = 0; + number_float_t value_float = 0; - default: - return m_it.primitive_iterator-other.m_it.primitive_iterator; - } + const char decimal_point_char = '.'; + }; } +} - reference operator[](difference_type n) const { - assert(m_object != nullptr); - - switch(m_object->m_type) { - case value_t::object: - JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators")); - - case value_t::array: - return *std::next(m_it.array_iterator, n); - - case value_t::null: - JSON_THROW(invalid_iterator::create(214, "cannot get value")); - - default: { - if(JSON_LIKELY(m_it.primitive_iterator.get_value() == -n)) { - return *m_object; - } - - JSON_THROW(invalid_iterator::create(214, "cannot get value")); - } - } - } +#include <cmath> - typename object_t::key_type key() const { - assert(m_object != nullptr); +namespace nlohmann { + namespace detail { + template<typename BasicJsonType> + class parser { + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using lexer_t = lexer<BasicJsonType>; + using token_type = typename lexer_t::token_type; - if(JSON_LIKELY(m_object->is_object())) { - return m_it.object_iterator->first; - } + public: + enum class parse_event_t : uint8_t { + object_start, - JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators")); - } + object_end, - reference value() const { - return operator*(); - } + array_start, -private: - pointer m_object = nullptr; + array_end, - internal_iterator<typename std::remove_const<BasicJsonType>::type> m_it; -}; -} -} + key, -namespace nlohmann { -namespace detail { -template<typename IteratorType> -class iteration_proxy { -private: - class iteration_proxy_internal { - private: - IteratorType anchor; + value + }; - std::size_t array_index = 0; + using parser_callback_t = + std::function<bool(int depth, parse_event_t event, BasicJsonType &parsed)>; - public: - explicit iteration_proxy_internal(IteratorType it) noexcept : anchor(it) {} + explicit parser(detail::input_adapter_t adapter, + const parser_callback_t cb = nullptr, + const bool allow_exceptions_ = true) + : callback(cb), m_lexer(adapter), allow_exceptions(allow_exceptions_) {} - iteration_proxy_internal &operator*() { - return *this; - } + void parse(const bool strict, BasicJsonType &result) { + get_token(); - iteration_proxy_internal &operator++() { - ++anchor; - ++array_index; + parse_internal(true, result); + result.assert_invariant(); - return *this; - } + if(strict) { + get_token(); + expect(token_type::end_of_input); + } - bool operator!=(const iteration_proxy_internal &o) const noexcept { - return anchor != o.anchor; - } + if(errored) { + result = value_t::discarded; + return; + } - std::string key() const { - assert(anchor.m_object != nullptr); + if(result.is_discarded()) { + result = nullptr; + } + } - switch(anchor.m_object->type()) { - case value_t::array: - return std::to_string(array_index); + bool accept(const bool strict = true) { + get_token(); - case value_t::object: - return anchor.key(); + if(not accept_internal()) { + return false; + } - default: - return ""; + return not strict or (get_token() == token_type::end_of_input); } - } - - typename IteratorType::reference value() const { - return anchor.value(); - } - }; - typename IteratorType::reference container; + private: + void parse_internal(bool keep, BasicJsonType &result) { + assert(not errored); -public: - explicit iteration_proxy(typename IteratorType::reference cont) noexcept - : container(cont) {} - - iteration_proxy_internal begin() noexcept { - return iteration_proxy_internal(container.begin()); - } + if(not result.is_discarded()) { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } - iteration_proxy_internal end() noexcept { - return iteration_proxy_internal(container.end()); - } -}; -} -} + switch(last_token) { + case token_type::begin_object: { + if(keep) { + if(callback) { + keep = callback(depth++, parse_event_t::object_start, result); + } -namespace nlohmann { -namespace detail { -template<typename Base> -class json_reverse_iterator : public std::reverse_iterator<Base> { -public: - using difference_type = std::ptrdiff_t; + if(not callback or keep) { + result.m_type = value_t::object; + result.m_value = value_t::object; + } + } - using base_iterator = std::reverse_iterator<Base>; + get_token(); - using reference = typename Base::reference; + if(last_token == token_type::end_object) { + if(keep and callback and not callback(--depth, parse_event_t::object_end, result)) { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } - json_reverse_iterator(const typename base_iterator::iterator_type &it) noexcept - : base_iterator(it) {} + string_t key; + BasicJsonType value; + while(true) { + if(not expect(token_type::value_string)) { + return; + } + key = m_lexer.move_string(); - json_reverse_iterator(const base_iterator &it) noexcept : base_iterator(it) {} + bool keep_tag = false; + if(keep) { + if(callback) { + BasicJsonType k(key); + keep_tag = callback(depth, parse_event_t::key, k); + } else { + keep_tag = true; + } + } - json_reverse_iterator const operator++(int) { - return static_cast<json_reverse_iterator>(base_iterator::operator++(1)); - } + get_token(); + if(not expect(token_type::name_separator)) { + return; + } - json_reverse_iterator &operator++() { - return static_cast<json_reverse_iterator &>(base_iterator::operator++()); - } + get_token(); + value.m_value.destroy(value.m_type); + value.m_type = value_t::discarded; + parse_internal(keep, value); - json_reverse_iterator const operator--(int) { - return static_cast<json_reverse_iterator>(base_iterator::operator--(1)); - } + if(JSON_UNLIKELY(errored)) { + return; + } - json_reverse_iterator &operator--() { - return static_cast<json_reverse_iterator &>(base_iterator::operator--()); - } + if(keep and keep_tag and not value.is_discarded()) { + result.m_value.object->emplace(std::move(key), std::move(value)); + } - json_reverse_iterator &operator+=(difference_type i) { - return static_cast<json_reverse_iterator &>(base_iterator::operator+=(i)); - } + get_token(); + if(last_token == token_type::value_separator) { + get_token(); + continue; + } - json_reverse_iterator operator+(difference_type i) const { - return static_cast<json_reverse_iterator>(base_iterator::operator+(i)); - } + if(not expect(token_type::end_object)) { + return; + } + break; + } - json_reverse_iterator operator-(difference_type i) const { - return static_cast<json_reverse_iterator>(base_iterator::operator-(i)); - } + if(keep and callback and not callback(--depth, parse_event_t::object_end, result)) { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } - difference_type operator-(const json_reverse_iterator &other) const { - return base_iterator(*this)-base_iterator(other); - } + case token_type::begin_array: { + if(keep) { + if(callback) { + keep = callback(depth++, parse_event_t::array_start, result); + } - reference operator[](difference_type n) const { - return *(this->operator+(n)); - } + if(not callback or keep) { + result.m_type = value_t::array; + result.m_value = value_t::array; + } + } - auto key() const -> decltype(std::declval<Base>().key()) { - auto it = --this->base(); - return it.key(); - } + get_token(); - reference value() const { - auto it = --this->base(); - return it.operator*(); - } -}; -} -} + if(last_token == token_type::end_array) { + if(callback and not callback(--depth, parse_event_t::array_end, result)) { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } -#include <ostream> + BasicJsonType value; + while(true) { + value.m_value.destroy(value.m_type); + value.m_type = value_t::discarded; + parse_internal(keep, value); -namespace nlohmann { -namespace detail { -template<typename CharType> -struct output_adapter_protocol { - virtual void write_character(CharType c) = 0; + if(JSON_UNLIKELY(errored)) { + return; + } - virtual void write_characters(const CharType *s, std::size_t length) = 0; + if(keep and not value.is_discarded()) { + result.m_value.array->push_back(std::move(value)); + } - virtual ~output_adapter_protocol() = default; -}; + get_token(); + if(last_token == token_type::value_separator) { + get_token(); + continue; + } -template<typename CharType> -using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>; + if(not expect(token_type::end_array)) { + return; + } + break; + } -template<typename CharType> -class output_vector_adapter : public output_adapter_protocol<CharType> { -public: - explicit output_vector_adapter(std::vector<CharType> &vec) : v(vec) {} + if(keep and callback and not callback(--depth, parse_event_t::array_end, result)) { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } - void write_character(CharType c) override { - v.push_back(c); - } + case token_type::literal_null: { + result.m_type = value_t::null; + break; + } - void write_characters(const CharType *s, std::size_t length) override { - std::copy(s, s+length, std::back_inserter(v)); - } + case token_type::value_string: { + result.m_type = value_t::string; + result.m_value = m_lexer.move_string(); + break; + } -private: - std::vector<CharType> &v; -}; + case token_type::literal_true: { + result.m_type = value_t::boolean; + result.m_value = true; + break; + } -template<typename CharType> -class output_stream_adapter : public output_adapter_protocol<CharType> { -public: - explicit output_stream_adapter(std::basic_ostream<CharType> &s) : stream(s) {} + case token_type::literal_false: { + result.m_type = value_t::boolean; + result.m_value = false; + break; + } - void write_character(CharType c) override { - stream.put(c); - } + case token_type::value_unsigned: { + result.m_type = value_t::number_unsigned; + result.m_value = m_lexer.get_number_unsigned(); + break; + } - void write_characters(const CharType *s, std::size_t length) override { - stream.write(s, static_cast<std::streamsize>(length)); - } + case token_type::value_integer: { + result.m_type = value_t::number_integer; + result.m_value = m_lexer.get_number_integer(); + break; + } -private: - std::basic_ostream<CharType> &stream; -}; + case token_type::value_float: { + result.m_type = value_t::number_float; + result.m_value = m_lexer.get_number_float(); -template<typename CharType, typename StringType = std::basic_string<CharType>> -class output_string_adapter : public output_adapter_protocol<CharType> { -public: - explicit output_string_adapter(StringType &s) : str(s) {} + if(JSON_UNLIKELY(not std::isfinite(result.m_value.number_float))) { + if(allow_exceptions) { + JSON_THROW(out_of_range::create(406, "number overflow parsing '"+ + m_lexer.get_token_string()+"'")); + } + expect(token_type::uninitialized); + } + break; + } - void write_character(CharType c) override { - str.push_back(c); - } + case token_type::parse_error: { + if(not expect(token_type::uninitialized)) { + return; + } + break; + } - void write_characters(const CharType *s, std::size_t length) override { - str.append(s, length); - } + default: { + if(not expect(token_type::literal_or_value)) { + return; + } + break; + } + } -private: - StringType &str; -}; + if(keep and callback and not callback(depth, parse_event_t::value, result)) { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + } -template<typename CharType, typename StringType = std::basic_string<CharType>> -class output_adapter { -public: - output_adapter(std::vector<CharType> &vec) - : oa(std::make_shared<output_vector_adapter<CharType>>(vec)) {} + bool accept_internal() { + switch(last_token) { + case token_type::begin_object: { + get_token(); - output_adapter(std::basic_ostream<CharType> &s) - : oa(std::make_shared<output_stream_adapter<CharType>>(s)) {} + if(last_token == token_type::end_object) { + return true; + } - output_adapter(StringType &s) - : oa(std::make_shared<output_string_adapter<CharType, StringType>>(s)) {} + while(true) { + if(last_token != token_type::value_string) { + return false; + } - operator output_adapter_t<CharType>() { - return oa; - } + get_token(); + if(last_token != token_type::name_separator) { + return false; + } -private: - output_adapter_t<CharType> oa = nullptr; -}; -} -} + get_token(); + if(not accept_internal()) { + return false; + } -namespace nlohmann { -namespace detail { -template<typename BasicJsonType> -class binary_reader { - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using string_t = typename BasicJsonType::string_t; - -public: - explicit binary_reader(input_adapter_t adapter) : ia(std::move(adapter)) { - assert(ia); - } + get_token(); + if(last_token == token_type::value_separator) { + get_token(); + continue; + } - BasicJsonType parse_cbor(const bool strict) { - const auto res = parse_cbor_internal(); - if(strict) { - get(); - expect_eof(); - } - return res; - } + return (last_token == token_type::end_object); + } + } - BasicJsonType parse_msgpack(const bool strict) { - const auto res = parse_msgpack_internal(); - if(strict) { - get(); - expect_eof(); - } - return res; - } + case token_type::begin_array: { + get_token(); - BasicJsonType parse_ubjson(const bool strict) { - const auto res = parse_ubjson_internal(); - if(strict) { - get_ignore_noop(); - expect_eof(); - } - return res; - } + if(last_token == token_type::end_array) { + return true; + } - static constexpr bool little_endianess(int num = 1) noexcept { - return (*reinterpret_cast<char *>(&num) == 1); - } + while(true) { + if(not accept_internal()) { + return false; + } -private: - BasicJsonType parse_cbor_internal(const bool get_char = true) { - switch(get_char ? get() : current) { - case std::char_traits<char>::eof(): - JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); - - case 0x00: - case 0x01: - case 0x02: - case 0x03: - case 0x04: - case 0x05: - case 0x06: - case 0x07: - case 0x08: - case 0x09: - case 0x0A: - case 0x0B: - case 0x0C: - case 0x0D: - case 0x0E: - case 0x0F: - case 0x10: - case 0x11: - case 0x12: - case 0x13: - case 0x14: - case 0x15: - case 0x16: - case 0x17: - return static_cast<number_unsigned_t>(current); - - case 0x18: - return get_number<uint8_t>(); - - case 0x19: - return get_number<uint16_t>(); - - case 0x1A: - return get_number<uint32_t>(); - - case 0x1B: - return get_number<uint64_t>(); - - case 0x20: - case 0x21: - case 0x22: - case 0x23: - case 0x24: - case 0x25: - case 0x26: - case 0x27: - case 0x28: - case 0x29: - case 0x2A: - case 0x2B: - case 0x2C: - case 0x2D: - case 0x2E: - case 0x2F: - case 0x30: - case 0x31: - case 0x32: - case 0x33: - case 0x34: - case 0x35: - case 0x36: - case 0x37: - return static_cast<int8_t>(0x20-1-current); - - case 0x38: { - return static_cast<number_integer_t>(-1)-get_number<uint8_t>(); - } - - case 0x39: { - return static_cast<number_integer_t>(-1)-get_number<uint16_t>(); - } - - case 0x3A: { - return static_cast<number_integer_t>(-1)-get_number<uint32_t>(); - } - - case 0x3B: { - return static_cast<number_integer_t>(-1)- - static_cast<number_integer_t>(get_number<uint64_t>()); - } - - case 0x60: - case 0x61: - case 0x62: - case 0x63: - case 0x64: - case 0x65: - case 0x66: - case 0x67: - case 0x68: - case 0x69: - case 0x6A: - case 0x6B: - case 0x6C: - case 0x6D: - case 0x6E: - case 0x6F: - case 0x70: - case 0x71: - case 0x72: - case 0x73: - case 0x74: - case 0x75: - case 0x76: - case 0x77: - case 0x78: - case 0x79: - case 0x7A: - case 0x7B: - case 0x7F: { - return get_cbor_string(); - } - - case 0x80: - case 0x81: - case 0x82: - case 0x83: - case 0x84: - case 0x85: - case 0x86: - case 0x87: - case 0x88: - case 0x89: - case 0x8A: - case 0x8B: - case 0x8C: - case 0x8D: - case 0x8E: - case 0x8F: - case 0x90: - case 0x91: - case 0x92: - case 0x93: - case 0x94: - case 0x95: - case 0x96: - case 0x97: { - return get_cbor_array(current & 0x1F); - } - - case 0x98: { - return get_cbor_array(get_number<uint8_t>()); - } - - case 0x99: { - return get_cbor_array(get_number<uint16_t>()); - } - - case 0x9A: { - return get_cbor_array(get_number<uint32_t>()); - } - - case 0x9B: { - return get_cbor_array(get_number<uint64_t>()); - } - - case 0x9F: { - BasicJsonType result = value_t::array; - while(get() != 0xFF) { - result.push_back(parse_cbor_internal(false)); - } - return result; - } + get_token(); + if(last_token == token_type::value_separator) { + get_token(); + continue; + } - case 0xA0: - case 0xA1: - case 0xA2: - case 0xA3: - case 0xA4: - case 0xA5: - case 0xA6: - case 0xA7: - case 0xA8: - case 0xA9: - case 0xAA: - case 0xAB: - case 0xAC: - case 0xAD: - case 0xAE: - case 0xAF: - case 0xB0: - case 0xB1: - case 0xB2: - case 0xB3: - case 0xB4: - case 0xB5: - case 0xB6: - case 0xB7: { - return get_cbor_object(current & 0x1F); - } + return (last_token == token_type::end_array); + } + } - case 0xB8: { - return get_cbor_object(get_number<uint8_t>()); - } + case token_type::value_float: { + return std::isfinite(m_lexer.get_number_float()); + } - case 0xB9: { - return get_cbor_object(get_number<uint16_t>()); - } + case token_type::literal_false: + case token_type::literal_null: + case token_type::literal_true: + case token_type::value_integer: + case token_type::value_string: + case token_type::value_unsigned: + return true; - case 0xBA: { - return get_cbor_object(get_number<uint32_t>()); + default: + return false; + } } - case 0xBB: { - return get_cbor_object(get_number<uint64_t>()); + token_type get_token() { + return (last_token = m_lexer.scan()); } - case 0xBF: { - BasicJsonType result = value_t::object; - while(get() != 0xFF) { - auto key = get_cbor_string(); - result[key] = parse_cbor_internal(); + bool expect(token_type t) { + if(JSON_UNLIKELY(t != last_token)) { + errored = true; + expected = t; + if(allow_exceptions) { + throw_exception(); + } else { + return false; + } } - return result; - } - case 0xF4: { - return false; - } - - case 0xF5: { return true; } - case 0xF6: { - return value_t::null; - } - - case 0xF9: { - const int byte1 = get(); - unexpect_eof(); - const int byte2 = get(); - unexpect_eof(); - - const int half = (byte1 << 8)+byte2; - const int exp = (half >> 10) & 0x1F; - const int mant = half & 0x3FF; - double val; - if(exp == 0) { - val = std::ldexp(mant, -24); - } else if(exp != 31) { - val = std::ldexp(mant+1024, exp-25); + [[noreturn]] void throw_exception() const { + std::string error_msg = "syntax error - "; + if(last_token == token_type::parse_error) { + error_msg += std::string(m_lexer.get_error_message())+"; last read: '"+ + m_lexer.get_token_string()+"'"; } else { - val = (mant == 0) ? std::numeric_limits<double>::infinity() - : std::numeric_limits<double>::quiet_NaN(); + error_msg += "unexpected "+std::string(lexer_t::token_type_name(last_token)); } - return (half & 0x8000) != 0 ? -val : val; - } - - case 0xFA: { - return get_number<float>(); - } - case 0xFB: { - return get_number<double>(); - } + if(expected != token_type::uninitialized) { + error_msg += "; expected "+std::string(lexer_t::token_type_name(expected)); + } - default: { - std::stringstream ss; - ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; - JSON_THROW(parse_error::create(112, chars_read, "error reading CBOR; last byte: 0x"+ss.str())); + JSON_THROW(parse_error::create(101, m_lexer.get_position(), error_msg)); } - } - } - - BasicJsonType parse_msgpack_internal() { - switch(get()) { - case std::char_traits<char>::eof(): - JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); - - case 0x00: - case 0x01: - case 0x02: - case 0x03: - case 0x04: - case 0x05: - case 0x06: - case 0x07: - case 0x08: - case 0x09: - case 0x0A: - case 0x0B: - case 0x0C: - case 0x0D: - case 0x0E: - case 0x0F: - case 0x10: - case 0x11: - case 0x12: - case 0x13: - case 0x14: - case 0x15: - case 0x16: - case 0x17: - case 0x18: - case 0x19: - case 0x1A: - case 0x1B: - case 0x1C: - case 0x1D: - case 0x1E: - case 0x1F: - case 0x20: - case 0x21: - case 0x22: - case 0x23: - case 0x24: - case 0x25: - case 0x26: - case 0x27: - case 0x28: - case 0x29: - case 0x2A: - case 0x2B: - case 0x2C: - case 0x2D: - case 0x2E: - case 0x2F: - case 0x30: - case 0x31: - case 0x32: - case 0x33: - case 0x34: - case 0x35: - case 0x36: - case 0x37: - case 0x38: - case 0x39: - case 0x3A: - case 0x3B: - case 0x3C: - case 0x3D: - case 0x3E: - case 0x3F: - case 0x40: - case 0x41: - case 0x42: - case 0x43: - case 0x44: - case 0x45: - case 0x46: - case 0x47: - case 0x48: - case 0x49: - case 0x4A: - case 0x4B: - case 0x4C: - case 0x4D: - case 0x4E: - case 0x4F: - case 0x50: - case 0x51: - case 0x52: - case 0x53: - case 0x54: - case 0x55: - case 0x56: - case 0x57: - case 0x58: - case 0x59: - case 0x5A: - case 0x5B: - case 0x5C: - case 0x5D: - case 0x5E: - case 0x5F: - case 0x60: - case 0x61: - case 0x62: - case 0x63: - case 0x64: - case 0x65: - case 0x66: - case 0x67: - case 0x68: - case 0x69: - case 0x6A: - case 0x6B: - case 0x6C: - case 0x6D: - case 0x6E: - case 0x6F: - case 0x70: - case 0x71: - case 0x72: - case 0x73: - case 0x74: - case 0x75: - case 0x76: - case 0x77: - case 0x78: - case 0x79: - case 0x7A: - case 0x7B: - case 0x7C: - case 0x7D: - case 0x7E: - case 0x7F: - return static_cast<number_unsigned_t>(current); - - case 0x80: - case 0x81: - case 0x82: - case 0x83: - case 0x84: - case 0x85: - case 0x86: - case 0x87: - case 0x88: - case 0x89: - case 0x8A: - case 0x8B: - case 0x8C: - case 0x8D: - case 0x8E: - case 0x8F: { - return get_msgpack_object(current & 0x0F); - } - - case 0x90: - case 0x91: - case 0x92: - case 0x93: - case 0x94: - case 0x95: - case 0x96: - case 0x97: - case 0x98: - case 0x99: - case 0x9A: - case 0x9B: - case 0x9C: - case 0x9D: - case 0x9E: - case 0x9F: { - return get_msgpack_array(current & 0x0F); - } - - case 0xA0: - case 0xA1: - case 0xA2: - case 0xA3: - case 0xA4: - case 0xA5: - case 0xA6: - case 0xA7: - case 0xA8: - case 0xA9: - case 0xAA: - case 0xAB: - case 0xAC: - case 0xAD: - case 0xAE: - case 0xAF: - case 0xB0: - case 0xB1: - case 0xB2: - case 0xB3: - case 0xB4: - case 0xB5: - case 0xB6: - case 0xB7: - case 0xB8: - case 0xB9: - case 0xBA: - case 0xBB: - case 0xBC: - case 0xBD: - case 0xBE: - case 0xBF: - return get_msgpack_string(); - - case 0xC0: - return value_t::null; - - case 0xC2: - return false; - - case 0xC3: - return true; - - case 0xCA: - return get_number<float>(); - - case 0xCB: - return get_number<double>(); - case 0xCC: - return get_number<uint8_t>(); + private: + int depth = 0; - case 0xCD: - return get_number<uint16_t>(); + const parser_callback_t callback = nullptr; - case 0xCE: - return get_number<uint32_t>(); + token_type last_token = token_type::uninitialized; - case 0xCF: - return get_number<uint64_t>(); + lexer_t m_lexer; - case 0xD0: - return get_number<int8_t>(); + bool errored = false; - case 0xD1: - return get_number<int16_t>(); + token_type expected = token_type::uninitialized; - case 0xD2: - return get_number<int32_t>(); + const bool allow_exceptions = true; + }; + } +} - case 0xD3: - return get_number<int64_t>(); +namespace nlohmann { + namespace detail { + class primitive_iterator_t { + private: + using difference_type = std::ptrdiff_t; + static constexpr difference_type begin_value = 0; + static constexpr difference_type end_value = begin_value+1; - case 0xD9: - case 0xDA: - case 0xDB: - return get_msgpack_string(); + difference_type m_it = (std::numeric_limits<std::ptrdiff_t>::min)(); - case 0xDC: { - return get_msgpack_array(get_number<uint16_t>()); + public: + constexpr difference_type get_value() const noexcept { + return m_it; } - case 0xDD: { - return get_msgpack_array(get_number<uint32_t>()); + void set_begin() noexcept { + m_it = begin_value; } - case 0xDE: { - return get_msgpack_object(get_number<uint16_t>()); + void set_end() noexcept { + m_it = end_value; } - case 0xDF: { - return get_msgpack_object(get_number<uint32_t>()); + constexpr bool is_begin() const noexcept { + return m_it == begin_value; } - case 0xE0: - case 0xE1: - case 0xE2: - case 0xE3: - case 0xE4: - case 0xE5: - case 0xE6: - case 0xE7: - case 0xE8: - case 0xE9: - case 0xEA: - case 0xEB: - case 0xEC: - case 0xED: - case 0xEE: - case 0xEF: - case 0xF0: - case 0xF1: - case 0xF2: - case 0xF3: - case 0xF4: - case 0xF5: - case 0xF6: - case 0xF7: - case 0xF8: - case 0xF9: - case 0xFA: - case 0xFB: - case 0xFC: - case 0xFD: - case 0xFE: - case 0xFF: - return static_cast<int8_t>(current); - - default: { - std::stringstream ss; - ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; - JSON_THROW(parse_error::create(112, chars_read, - "error reading MessagePack; last byte: 0x"+ss.str())); + constexpr bool is_end() const noexcept { + return m_it == end_value; } - } - } - - BasicJsonType parse_ubjson_internal(const bool get_char = true) { - return get_ubjson_value(get_char ? get_ignore_noop() : current); - } - - int get() { - ++chars_read; - return (current = ia->get_character()); - } - - int get_ignore_noop() { - do { - get(); - } while(current == 'N'); - return current; - } - - template<typename NumberType> - NumberType get_number() { - std::array<uint8_t, sizeof(NumberType)> vec; - for(std::size_t i = 0; i < sizeof(NumberType); ++i) { - get(); - unexpect_eof(); - - if(is_little_endian) { - vec[sizeof(NumberType)-i-1] = static_cast<uint8_t>(current); - } else { - vec[i] = static_cast<uint8_t>(current); + friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { + return lhs.m_it == rhs.m_it; } - } - - NumberType result; - std::memcpy(&result, vec.data(), sizeof(NumberType)); - return result; - } - - template<typename NumberType> - string_t get_string(const NumberType len) { - string_t result; - std::generate_n(std::back_inserter(result), len, [this]() { - get(); - unexpect_eof(); - return static_cast<char>(current); - }); - return result; - } - string_t get_cbor_string() { - unexpect_eof(); + friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { + return lhs.m_it < rhs.m_it; + } - switch(current) { - case 0x60: - case 0x61: - case 0x62: - case 0x63: - case 0x64: - case 0x65: - case 0x66: - case 0x67: - case 0x68: - case 0x69: - case 0x6A: - case 0x6B: - case 0x6C: - case 0x6D: - case 0x6E: - case 0x6F: - case 0x70: - case 0x71: - case 0x72: - case 0x73: - case 0x74: - case 0x75: - case 0x76: - case 0x77: { - return get_string(current & 0x1F); + primitive_iterator_t operator+(difference_type n) noexcept { + auto result = *this; + result += n; + return result; } - case 0x78: { - return get_string(get_number<uint8_t>()); + friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept { + return lhs.m_it-rhs.m_it; } - case 0x79: { - return get_string(get_number<uint16_t>()); + primitive_iterator_t &operator++() noexcept { + ++m_it; + return *this; } - case 0x7A: { - return get_string(get_number<uint32_t>()); + primitive_iterator_t const operator++(int) noexcept { + auto result = *this; + m_it++; + return result; } - case 0x7B: { - return get_string(get_number<uint64_t>()); + primitive_iterator_t &operator--() noexcept { + --m_it; + return *this; } - case 0x7F: { - string_t result; - while(get() != 0xFF) { - result.append(get_cbor_string()); - } + primitive_iterator_t const operator--(int) noexcept { + auto result = *this; + m_it--; return result; } - default: { - std::stringstream ss; - ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; - JSON_THROW(parse_error::create(113, chars_read, "expected a CBOR string; last byte: 0x"+ss.str())); + primitive_iterator_t &operator+=(difference_type n) noexcept { + m_it += n; + return *this; } - } - } - - template<typename NumberType> - BasicJsonType get_cbor_array(const NumberType len) { - BasicJsonType result = value_t::array; - std::generate_n(std::back_inserter(*result.m_value.array), len, [this]() { - return parse_cbor_internal(); - }); - return result; - } - - template<typename NumberType> - BasicJsonType get_cbor_object(const NumberType len) { - BasicJsonType result = value_t::object; - std::generate_n(std::inserter(*result.m_value.object, - result.m_value.object->end()), - len, [this]() { - get(); - auto key = get_cbor_string(); - auto val = parse_cbor_internal(); - return std::make_pair(std::move(key), std::move(val)); - }); - return result; - } - string_t get_msgpack_string() { - unexpect_eof(); - - switch(current) { - case 0xA0: - case 0xA1: - case 0xA2: - case 0xA3: - case 0xA4: - case 0xA5: - case 0xA6: - case 0xA7: - case 0xA8: - case 0xA9: - case 0xAA: - case 0xAB: - case 0xAC: - case 0xAD: - case 0xAE: - case 0xAF: - case 0xB0: - case 0xB1: - case 0xB2: - case 0xB3: - case 0xB4: - case 0xB5: - case 0xB6: - case 0xB7: - case 0xB8: - case 0xB9: - case 0xBA: - case 0xBB: - case 0xBC: - case 0xBD: - case 0xBE: - case 0xBF: { - return get_string(current & 0x1F); - } - - case 0xD9: { - return get_string(get_number<uint8_t>()); - } - - case 0xDA: { - return get_string(get_number<uint16_t>()); - } - - case 0xDB: { - return get_string(get_number<uint32_t>()); - } - - default: { - std::stringstream ss; - ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; - JSON_THROW(parse_error::create(113, chars_read, - "expected a MessagePack string; last byte: 0x"+ss.str())); + primitive_iterator_t &operator-=(difference_type n) noexcept { + m_it -= n; + return *this; } - } + }; } +} - template<typename NumberType> - BasicJsonType get_msgpack_array(const NumberType len) { - BasicJsonType result = value_t::array; - std::generate_n(std::back_inserter(*result.m_value.array), len, [this]() { - return parse_msgpack_internal(); - }); - return result; - } +namespace nlohmann { + namespace detail { + template<typename BasicJsonType> + struct internal_iterator { + typename BasicJsonType::object_t::iterator object_iterator{}; - template<typename NumberType> - BasicJsonType get_msgpack_object(const NumberType len) { - BasicJsonType result = value_t::object; - std::generate_n(std::inserter(*result.m_value.object, - result.m_value.object->end()), - len, [this]() { - get(); - auto key = get_msgpack_string(); - auto val = parse_msgpack_internal(); - return std::make_pair(std::move(key), std::move(val)); - }); - return result; - } + typename BasicJsonType::array_t::iterator array_iterator{}; - string_t get_ubjson_string(const bool get_char = true) { - if(get_char) { - get(); - } - - unexpect_eof(); - - switch(current) { - case 'U': - return get_string(get_number<uint8_t>()); - case 'i': - return get_string(get_number<int8_t>()); - case 'I': - return get_string(get_number<int16_t>()); - case 'l': - return get_string(get_number<int32_t>()); - case 'L': - return get_string(get_number<int64_t>()); - default: - std::stringstream ss; - ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; - JSON_THROW(parse_error::create(113, chars_read, - "expected a UBJSON string; last byte: 0x"+ss.str())); - } + primitive_iterator_t primitive_iterator{}; + }; } +} - std::pair<std::size_t, int> get_ubjson_size_type() { - std::size_t sz = string_t::npos; - int tc = 0; +namespace nlohmann { + namespace detail { + template<typename IteratorType> + class iteration_proxy; - get_ignore_noop(); + template<typename BasicJsonType> + class iter_impl { + friend iter_impl<typename std::conditional<std::is_const<BasicJsonType>::value, typename std::remove_const<BasicJsonType>::type, const BasicJsonType>::type>; + friend BasicJsonType; + friend iteration_proxy<iter_impl>; - if(current == '$') { - tc = get(); - unexpect_eof(); + using object_t = typename BasicJsonType::object_t; + using array_t = typename BasicJsonType::array_t; - get_ignore_noop(); - if(current != '#') { - std::stringstream ss; - ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; - JSON_THROW(parse_error::create(112, chars_read, - "expected '#' after UBJSON type information; last byte: 0x"+ss.str())); - } - sz = parse_ubjson_internal(); - } else if(current == '#') { - sz = parse_ubjson_internal(); - } + static_assert(is_basic_json<typename std::remove_const<BasicJsonType>::type>::value, + "iter_impl only accepts (const) basic_json"); - return std::make_pair(sz, tc); - } + public: + using iterator_category = std::bidirectional_iterator_tag; - BasicJsonType get_ubjson_value(const int prefix) { - switch(prefix) { - case std::char_traits<char>::eof(): - JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + using value_type = typename BasicJsonType::value_type; - case 'T': - return true; - case 'F': - return false; - - case 'Z': - return nullptr; - - case 'U': - return get_number<uint8_t>(); - case 'i': - return get_number<int8_t>(); - case 'I': - return get_number<int16_t>(); - case 'l': - return get_number<int32_t>(); - case 'L': - return get_number<int64_t>(); - case 'd': - return get_number<float>(); - case 'D': - return get_number<double>(); - - case 'C': { - get(); - unexpect_eof(); - if(JSON_UNLIKELY(current > 127)) { - std::stringstream ss; - ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; - JSON_THROW(parse_error::create(113, chars_read, - "byte after 'C' must be in range 0x00..0x7F; last byte: 0x"+ss.str())); - } - return string_t(1, static_cast<char>(current)); - } + using difference_type = typename BasicJsonType::difference_type; - case 'S': - return get_ubjson_string(); + using pointer = typename std::conditional<std::is_const<BasicJsonType>::value, + typename BasicJsonType::const_pointer, + typename BasicJsonType::pointer>::type; - case '[': - return get_ubjson_array(); + using reference = + typename std::conditional<std::is_const<BasicJsonType>::value, + typename BasicJsonType::const_reference, + typename BasicJsonType::reference>::type; - case '{': - return get_ubjson_object(); + iter_impl() = default; - default: - std::stringstream ss; - ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; - JSON_THROW(parse_error::create(112, chars_read, - "error reading UBJSON; last byte: 0x"+ss.str())); - } - } + explicit iter_impl(pointer object) noexcept : m_object(object) { + assert(m_object != nullptr); - BasicJsonType get_ubjson_array() { - BasicJsonType result = value_t::array; - const auto size_and_type = get_ubjson_size_type(); + switch(m_object->m_type) { + case value_t::object: { + m_it.object_iterator = typename object_t::iterator(); + break; + } - if(size_and_type.first != string_t::npos) { - if(JSON_UNLIKELY(size_and_type.first > result.max_size())) { - JSON_THROW(out_of_range::create(408, - "excessive array size: "+std::to_string(size_and_type.first))); - } + case value_t::array: { + m_it.array_iterator = typename array_t::iterator(); + break; + } - if(size_and_type.second != 0) { - if(size_and_type.second != 'N') { - std::generate_n(std::back_inserter(*result.m_value.array), - size_and_type.first, [this, size_and_type]() { - return get_ubjson_value(size_and_type.second); - }); + default: { + m_it.primitive_iterator = primitive_iterator_t(); + break; + } } - } else { - std::generate_n(std::back_inserter(*result.m_value.array), - size_and_type.first, [this]() { - return parse_ubjson_internal(); - }); } - } else { - while(current != ']') { - result.push_back(parse_ubjson_internal(false)); - get_ignore_noop(); - } - } - return result; - } - - BasicJsonType get_ubjson_object() { - BasicJsonType result = value_t::object; - const auto size_and_type = get_ubjson_size_type(); - - if(size_and_type.first != string_t::npos) { - if(JSON_UNLIKELY(size_and_type.first > result.max_size())) { - JSON_THROW(out_of_range::create(408, - "excessive object size: "+std::to_string(size_and_type.first))); - } + iter_impl(const iter_impl<typename std::remove_const<BasicJsonType>::type> &other) noexcept + : m_object(other.m_object), m_it(other.m_it) {} - if(size_and_type.second != 0) { - std::generate_n(std::inserter(*result.m_value.object, - result.m_value.object->end()), - size_and_type.first, [this, size_and_type]() { - auto key = get_ubjson_string(); - auto val = get_ubjson_value(size_and_type.second); - return std::make_pair(std::move(key), std::move(val)); - }); - } else { - std::generate_n(std::inserter(*result.m_value.object, - result.m_value.object->end()), - size_and_type.first, [this]() { - auto key = get_ubjson_string(); - auto val = parse_ubjson_internal(); - return std::make_pair(std::move(key), std::move(val)); - }); - } - } else { - while(current != '}') { - auto key = get_ubjson_string(false); - result[std::move(key)] = parse_ubjson_internal(); - get_ignore_noop(); + iter_impl &operator=(const iter_impl<typename std::remove_const<BasicJsonType>::type> &other) noexcept { + m_object = other.m_object; + m_it = other.m_it; + return *this; } - } - - return result; - } - void expect_eof() const { - if(JSON_UNLIKELY(current != std::char_traits<char>::eof())) { - JSON_THROW(parse_error::create(110, chars_read, "expected end of input")); - } - } + private: + void set_begin() noexcept { + assert(m_object != nullptr); - void unexpect_eof() const { - if(JSON_UNLIKELY(current == std::char_traits<char>::eof())) { - JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); - } - } + switch(m_object->m_type) { + case value_t::object: { + m_it.object_iterator = m_object->m_value.object->begin(); + break; + } -private: - input_adapter_t ia = nullptr; + case value_t::array: { + m_it.array_iterator = m_object->m_value.array->begin(); + break; + } - int current = std::char_traits<char>::eof(); + case value_t::null: { + m_it.primitive_iterator.set_end(); + break; + } - std::size_t chars_read = 0; + default: { + m_it.primitive_iterator.set_begin(); + break; + } + } + } - const bool is_little_endian = little_endianess(); -}; -} -} + void set_end() noexcept { + assert(m_object != nullptr); -namespace nlohmann { -namespace detail { -template<typename BasicJsonType, typename CharType> -class binary_writer { -public: - explicit binary_writer(output_adapter_t<CharType> adapter) : oa(adapter) { - assert(oa); - } + switch(m_object->m_type) { + case value_t::object: { + m_it.object_iterator = m_object->m_value.object->end(); + break; + } - void write_cbor(const BasicJsonType &j) { - switch(j.type()) { - case value_t::null: { - oa->write_character(static_cast<CharType>(0xF6)); - break; - } - - case value_t::boolean: { - oa->write_character(j.m_value.boolean - ? static_cast<CharType>(0xF5) - : static_cast<CharType>(0xF4)); - break; - } - - case value_t::number_integer: { - if(j.m_value.number_integer >= 0) { - if(j.m_value.number_integer <= 0x17) { - write_number(static_cast<uint8_t>(j.m_value.number_integer)); - } else if(j.m_value.number_integer <= (std::numeric_limits<uint8_t>::max) ()) { - oa->write_character(static_cast<CharType>(0x18)); - write_number(static_cast<uint8_t>(j.m_value.number_integer)); - } else if(j.m_value.number_integer <= (std::numeric_limits<uint16_t>::max) ()) { - oa->write_character(static_cast<CharType>(0x19)); - write_number(static_cast<uint16_t>(j.m_value.number_integer)); - } else if(j.m_value.number_integer <= (std::numeric_limits<uint32_t>::max) ()) { - oa->write_character(static_cast<CharType>(0x1A)); - write_number(static_cast<uint32_t>(j.m_value.number_integer)); - } else { - oa->write_character(static_cast<CharType>(0x1B)); - write_number(static_cast<uint64_t>(j.m_value.number_integer)); + case value_t::array: { + m_it.array_iterator = m_object->m_value.array->end(); + break; } - } else { - const auto positive_number = -1-j.m_value.number_integer; - if(j.m_value.number_integer >= -24) { - write_number(static_cast<uint8_t>(0x20+positive_number)); - } else if(positive_number <= (std::numeric_limits<uint8_t>::max) ()) { - oa->write_character(static_cast<CharType>(0x38)); - write_number(static_cast<uint8_t>(positive_number)); - } else if(positive_number <= (std::numeric_limits<uint16_t>::max) ()) { - oa->write_character(static_cast<CharType>(0x39)); - write_number(static_cast<uint16_t>(positive_number)); - } else if(positive_number <= (std::numeric_limits<uint32_t>::max) ()) { - oa->write_character(static_cast<CharType>(0x3A)); - write_number(static_cast<uint32_t>(positive_number)); - } else { - oa->write_character(static_cast<CharType>(0x3B)); - write_number(static_cast<uint64_t>(positive_number)); + + default: { + m_it.primitive_iterator.set_end(); + break; } } - break; } - case value_t::number_unsigned: { - if(j.m_value.number_unsigned <= 0x17) { - write_number(static_cast<uint8_t>(j.m_value.number_unsigned)); - } else if(j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max) ()) { - oa->write_character(static_cast<CharType>(0x18)); - write_number(static_cast<uint8_t>(j.m_value.number_unsigned)); - } else if(j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max) ()) { - oa->write_character(static_cast<CharType>(0x19)); - write_number(static_cast<uint16_t>(j.m_value.number_unsigned)); - } else if(j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max) ()) { - oa->write_character(static_cast<CharType>(0x1A)); - write_number(static_cast<uint32_t>(j.m_value.number_unsigned)); - } else { - oa->write_character(static_cast<CharType>(0x1B)); - write_number(static_cast<uint64_t>(j.m_value.number_unsigned)); - } - break; - } - - case value_t::number_float: { - oa->write_character(static_cast<CharType>(0xFB)); - write_number(j.m_value.number_float); - break; - } - - case value_t::string: { - const auto N = j.m_value.string->size(); - if(N <= 0x17) { - write_number(static_cast<uint8_t>(0x60+N)); - } else if(N <= (std::numeric_limits<uint8_t>::max) ()) { - oa->write_character(static_cast<CharType>(0x78)); - write_number(static_cast<uint8_t>(N)); - } else if(N <= (std::numeric_limits<uint16_t>::max) ()) { - oa->write_character(static_cast<CharType>(0x79)); - write_number(static_cast<uint16_t>(N)); - } else if(N <= (std::numeric_limits<uint32_t>::max) ()) { - oa->write_character(static_cast<CharType>(0x7A)); - write_number(static_cast<uint32_t>(N)); - } else if(N <= (std::numeric_limits<uint64_t>::max) ()) { - oa->write_character(static_cast<CharType>(0x7B)); - write_number(static_cast<uint64_t>(N)); - } - - oa->write_characters( - reinterpret_cast<const CharType *>(j.m_value.string->c_str()), - j.m_value.string->size()); - break; - } - - case value_t::array: { - const auto N = j.m_value.array->size(); - if(N <= 0x17) { - write_number(static_cast<uint8_t>(0x80+N)); - } else if(N <= (std::numeric_limits<uint8_t>::max) ()) { - oa->write_character(static_cast<CharType>(0x98)); - write_number(static_cast<uint8_t>(N)); - } else if(N <= (std::numeric_limits<uint16_t>::max) ()) { - oa->write_character(static_cast<CharType>(0x99)); - write_number(static_cast<uint16_t>(N)); - } else if(N <= (std::numeric_limits<uint32_t>::max) ()) { - oa->write_character(static_cast<CharType>(0x9A)); - write_number(static_cast<uint32_t>(N)); - } else if(N <= (std::numeric_limits<uint64_t>::max) ()) { - oa->write_character(static_cast<CharType>(0x9B)); - write_number(static_cast<uint64_t>(N)); - } - - for(const auto &el : *j.m_value.array) { - write_cbor(el); - } - break; - } - - case value_t::object: { - const auto N = j.m_value.object->size(); - if(N <= 0x17) { - write_number(static_cast<uint8_t>(0xA0+N)); - } else if(N <= (std::numeric_limits<uint8_t>::max) ()) { - oa->write_character(static_cast<CharType>(0xB8)); - write_number(static_cast<uint8_t>(N)); - } else if(N <= (std::numeric_limits<uint16_t>::max) ()) { - oa->write_character(static_cast<CharType>(0xB9)); - write_number(static_cast<uint16_t>(N)); - } else if(N <= (std::numeric_limits<uint32_t>::max) ()) { - oa->write_character(static_cast<CharType>(0xBA)); - write_number(static_cast<uint32_t>(N)); - } else if(N <= (std::numeric_limits<uint64_t>::max) ()) { - oa->write_character(static_cast<CharType>(0xBB)); - write_number(static_cast<uint64_t>(N)); - } - - for(const auto &el : *j.m_value.object) { - write_cbor(el.first); - write_cbor(el.second); - } - break; - } - - default: - break; - } - } + public: + reference operator*() const { + assert(m_object != nullptr); - void write_msgpack(const BasicJsonType &j) { - switch(j.type()) { - case value_t::null: { - oa->write_character(static_cast<CharType>(0xC0)); - break; - } - - case value_t::boolean: { - oa->write_character(j.m_value.boolean - ? static_cast<CharType>(0xC3) - : static_cast<CharType>(0xC2)); - break; - } - - case value_t::number_integer: { - if(j.m_value.number_integer >= 0) { - if(j.m_value.number_unsigned < 128) { - write_number(static_cast<uint8_t>(j.m_value.number_integer)); - } else if(j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max) ()) { - oa->write_character(static_cast<CharType>(0xCC)); - write_number(static_cast<uint8_t>(j.m_value.number_integer)); - } else if(j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max) ()) { - oa->write_character(static_cast<CharType>(0xCD)); - write_number(static_cast<uint16_t>(j.m_value.number_integer)); - } else if(j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max) ()) { - oa->write_character(static_cast<CharType>(0xCE)); - write_number(static_cast<uint32_t>(j.m_value.number_integer)); - } else if(j.m_value.number_unsigned <= (std::numeric_limits<uint64_t>::max) ()) { - oa->write_character(static_cast<CharType>(0xCF)); - write_number(static_cast<uint64_t>(j.m_value.number_integer)); + switch(m_object->m_type) { + case value_t::object: { + assert(m_it.object_iterator != m_object->m_value.object->end()); + return m_it.object_iterator->second; } - } else { - if(j.m_value.number_integer >= -32) { - write_number(static_cast<int8_t>(j.m_value.number_integer)); - } else if(j.m_value.number_integer >= (std::numeric_limits<int8_t>::min) () and - j.m_value.number_integer <= (std::numeric_limits<int8_t>::max) ()) { - oa->write_character(static_cast<CharType>(0xD0)); - write_number(static_cast<int8_t>(j.m_value.number_integer)); - } else if(j.m_value.number_integer >= (std::numeric_limits<int16_t>::min) () and - j.m_value.number_integer <= (std::numeric_limits<int16_t>::max) ()) { - oa->write_character(static_cast<CharType>(0xD1)); - write_number(static_cast<int16_t>(j.m_value.number_integer)); - } else if(j.m_value.number_integer >= (std::numeric_limits<int32_t>::min) () and - j.m_value.number_integer <= (std::numeric_limits<int32_t>::max) ()) { - oa->write_character(static_cast<CharType>(0xD2)); - write_number(static_cast<int32_t>(j.m_value.number_integer)); - } else if(j.m_value.number_integer >= (std::numeric_limits<int64_t>::min) () and - j.m_value.number_integer <= (std::numeric_limits<int64_t>::max) ()) { - oa->write_character(static_cast<CharType>(0xD3)); - write_number(static_cast<int64_t>(j.m_value.number_integer)); - } - } - break; - } - - case value_t::number_unsigned: { - if(j.m_value.number_unsigned < 128) { - write_number(static_cast<uint8_t>(j.m_value.number_integer)); - } else if(j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max) ()) { - oa->write_character(static_cast<CharType>(0xCC)); - write_number(static_cast<uint8_t>(j.m_value.number_integer)); - } else if(j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max) ()) { - oa->write_character(static_cast<CharType>(0xCD)); - write_number(static_cast<uint16_t>(j.m_value.number_integer)); - } else if(j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max) ()) { - oa->write_character(static_cast<CharType>(0xCE)); - write_number(static_cast<uint32_t>(j.m_value.number_integer)); - } else if(j.m_value.number_unsigned <= (std::numeric_limits<uint64_t>::max) ()) { - oa->write_character(static_cast<CharType>(0xCF)); - write_number(static_cast<uint64_t>(j.m_value.number_integer)); - } - break; - } - - case value_t::number_float: { - oa->write_character(static_cast<CharType>(0xCB)); - write_number(j.m_value.number_float); - break; - } - - case value_t::string: { - const auto N = j.m_value.string->size(); - if(N <= 31) { - write_number(static_cast<uint8_t>(0xA0 | N)); - } else if(N <= (std::numeric_limits<uint8_t>::max) ()) { - oa->write_character(static_cast<CharType>(0xD9)); - write_number(static_cast<uint8_t>(N)); - } else if(N <= (std::numeric_limits<uint16_t>::max) ()) { - oa->write_character(static_cast<CharType>(0xDA)); - write_number(static_cast<uint16_t>(N)); - } else if(N <= (std::numeric_limits<uint32_t>::max) ()) { - oa->write_character(static_cast<CharType>(0xDB)); - write_number(static_cast<uint32_t>(N)); - } - - oa->write_characters( - reinterpret_cast<const CharType *>(j.m_value.string->c_str()), - j.m_value.string->size()); - break; - } - - case value_t::array: { - const auto N = j.m_value.array->size(); - if(N <= 15) { - write_number(static_cast<uint8_t>(0x90 | N)); - } else if(N <= (std::numeric_limits<uint16_t>::max) ()) { - oa->write_character(static_cast<CharType>(0xDC)); - write_number(static_cast<uint16_t>(N)); - } else if(N <= (std::numeric_limits<uint32_t>::max) ()) { - oa->write_character(static_cast<CharType>(0xDD)); - write_number(static_cast<uint32_t>(N)); - } - - for(const auto &el : *j.m_value.array) { - write_msgpack(el); - } - break; - } - - case value_t::object: { - const auto N = j.m_value.object->size(); - if(N <= 15) { - write_number(static_cast<uint8_t>(0x80 | (N & 0xF))); - } else if(N <= (std::numeric_limits<uint16_t>::max) ()) { - oa->write_character(static_cast<CharType>(0xDE)); - write_number(static_cast<uint16_t>(N)); - } else if(N <= (std::numeric_limits<uint32_t>::max) ()) { - oa->write_character(static_cast<CharType>(0xDF)); - write_number(static_cast<uint32_t>(N)); - } - - for(const auto &el : *j.m_value.object) { - write_msgpack(el.first); - write_msgpack(el.second); - } - break; - } - - default: - break; - } - } - void write_ubjson(const BasicJsonType &j, const bool use_count, - const bool use_type, const bool add_prefix = true) { - switch(j.type()) { - case value_t::null: { - if(add_prefix) { - oa->write_character(static_cast<CharType>('Z')); + case value_t::array: { + assert(m_it.array_iterator != m_object->m_value.array->end()); + return *m_it.array_iterator; + } + + case value_t::null: + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + + default: { + if(JSON_LIKELY(m_it.primitive_iterator.is_begin())) { + return *m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } } - break; } - case value_t::boolean: { - if(add_prefix) - oa->write_character(j.m_value.boolean - ? static_cast<CharType>('T') - : static_cast<CharType>('F')); - break; - } + pointer operator->() const { + assert(m_object != nullptr); - case value_t::number_integer: { - write_number_with_ubjson_prefix(j.m_value.number_integer, add_prefix); - break; - } + switch(m_object->m_type) { + case value_t::object: { + assert(m_it.object_iterator != m_object->m_value.object->end()); + return &(m_it.object_iterator->second); + } - case value_t::number_unsigned: { - write_number_with_ubjson_prefix(j.m_value.number_unsigned, add_prefix); - break; - } + case value_t::array: { + assert(m_it.array_iterator != m_object->m_value.array->end()); + return &*m_it.array_iterator; + } - case value_t::number_float: { - write_number_with_ubjson_prefix(j.m_value.number_float, add_prefix); - break; - } + default: { + if(JSON_LIKELY(m_it.primitive_iterator.is_begin())) { + return m_object; + } - case value_t::string: { - if(add_prefix) { - oa->write_character(static_cast<CharType>('S')); + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } } - write_number_with_ubjson_prefix(j.m_value.string->size(), true); - oa->write_characters( - reinterpret_cast<const CharType *>(j.m_value.string->c_str()), - j.m_value.string->size()); - break; } - case value_t::array: { - if(add_prefix) { - oa->write_character(static_cast<CharType>('[')); - } + iter_impl const operator++(int) { + auto result = *this; + ++(*this); + return result; + } - bool prefix_required = true; - if(use_type and not j.m_value.array->empty()) { - assert(use_count); - const char first_prefix = ubjson_prefix(j.front()); - const bool same_prefix = std::all_of(j.begin()+1, j.end(), - [this, first_prefix](const BasicJsonType &v) { - return ubjson_prefix(v) == first_prefix; - }); + iter_impl &operator++() { + assert(m_object != nullptr); - if(same_prefix) { - prefix_required = false; - oa->write_character(static_cast<CharType>('$')); - oa->write_character(static_cast<CharType>(first_prefix)); + switch(m_object->m_type) { + case value_t::object: { + std::advance(m_it.object_iterator, 1); + break; } - } - if(use_count) { - oa->write_character(static_cast<CharType>('#')); - write_number_with_ubjson_prefix(j.m_value.array->size(), true); - } - - for(const auto &el : *j.m_value.array) { - write_ubjson(el, use_count, use_type, prefix_required); - } + case value_t::array: { + std::advance(m_it.array_iterator, 1); + break; + } - if(not use_count) { - oa->write_character(static_cast<CharType>(']')); + default: { + ++m_it.primitive_iterator; + break; + } } - break; + return *this; } - case value_t::object: { - if(add_prefix) { - oa->write_character(static_cast<CharType>('{')); - } + iter_impl const operator--(int) { + auto result = *this; + --(*this); + return result; + } - bool prefix_required = true; - if(use_type and not j.m_value.object->empty()) { - assert(use_count); - const char first_prefix = ubjson_prefix(j.front()); - const bool same_prefix = std::all_of(j.begin(), j.end(), - [this, first_prefix](const BasicJsonType &v) { - return ubjson_prefix(v) == first_prefix; - }); + iter_impl &operator--() { + assert(m_object != nullptr); - if(same_prefix) { - prefix_required = false; - oa->write_character(static_cast<CharType>('$')); - oa->write_character(static_cast<CharType>(first_prefix)); + switch(m_object->m_type) { + case value_t::object: { + std::advance(m_it.object_iterator, -1); + break; } - } - - if(use_count) { - oa->write_character(static_cast<CharType>('#')); - write_number_with_ubjson_prefix(j.m_value.object->size(), true); - } - for(const auto &el : *j.m_value.object) { - write_number_with_ubjson_prefix(el.first.size(), true); - oa->write_characters( - reinterpret_cast<const CharType *>(el.first.c_str()), - el.first.size()); - write_ubjson(el.second, use_count, use_type, prefix_required); - } + case value_t::array: { + std::advance(m_it.array_iterator, -1); + break; + } - if(not use_count) { - oa->write_character(static_cast<CharType>('}')); + default: { + --m_it.primitive_iterator; + break; + } } - break; + return *this; } - default: - break; - } - } - -private: - template<typename NumberType> - void write_number(const NumberType n) { - std::array<CharType, sizeof(NumberType)> vec; - std::memcpy(vec.data(), &n, sizeof(NumberType)); - - if(is_little_endian) { - std::reverse(vec.begin(), vec.end()); - } - - oa->write_characters(vec.data(), sizeof(NumberType)); - } - - template<typename NumberType, typename std::enable_if< - std::is_floating_point<NumberType>::value, int>::type = 0> - void write_number_with_ubjson_prefix(const NumberType n, - const bool add_prefix) { - if(add_prefix) { - oa->write_character(static_cast<CharType>('D')); - } - write_number(n); - } + bool operator==(const iter_impl &other) const { + if(JSON_UNLIKELY(m_object != other.m_object)) { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); + } - template<typename NumberType, typename std::enable_if< - std::is_unsigned<NumberType>::value, int>::type = 0> - void write_number_with_ubjson_prefix(const NumberType n, - const bool add_prefix) { - if(n <= static_cast<uint64_t>((std::numeric_limits<int8_t>::max) ())) { - if(add_prefix) { - oa->write_character(static_cast<CharType>('i')); - } - write_number(static_cast<uint8_t>(n)); - } else if(n <= (std::numeric_limits<uint8_t>::max) ()) { - if(add_prefix) { - oa->write_character(static_cast<CharType>('U')); - } - write_number(static_cast<uint8_t>(n)); - } else if(n <= static_cast<uint64_t>((std::numeric_limits<int16_t>::max) ())) { - if(add_prefix) { - oa->write_character(static_cast<CharType>('I')); - } - write_number(static_cast<int16_t>(n)); - } else if(n <= static_cast<uint64_t>((std::numeric_limits<int32_t>::max) ())) { - if(add_prefix) { - oa->write_character(static_cast<CharType>('l')); - } - write_number(static_cast<int32_t>(n)); - } else if(n <= static_cast<uint64_t>((std::numeric_limits<int64_t>::max) ())) { - if(add_prefix) { - oa->write_character(static_cast<CharType>('L')); - } - write_number(static_cast<int64_t>(n)); - } else { - JSON_THROW(out_of_range::create(407, "number overflow serializing "+std::to_string(n))); - } - } + assert(m_object != nullptr); - template<typename NumberType, typename std::enable_if< - std::is_signed<NumberType>::value and - not std::is_floating_point<NumberType>::value, int>::type = 0> - void write_number_with_ubjson_prefix(const NumberType n, - const bool add_prefix) { - if((std::numeric_limits<int8_t>::min) () <= n and n <= (std::numeric_limits<int8_t>::max) ()) { - if(add_prefix) { - oa->write_character(static_cast<CharType>('i')); - } - write_number(static_cast<int8_t>(n)); - } else if(static_cast<int64_t>((std::numeric_limits<uint8_t>::min) ()) <= n and - n <= static_cast<int64_t>((std::numeric_limits<uint8_t>::max) ())) { - if(add_prefix) { - oa->write_character(static_cast<CharType>('U')); - } - write_number(static_cast<uint8_t>(n)); - } else if((std::numeric_limits<int16_t>::min) () <= n and n <= (std::numeric_limits<int16_t>::max) ()) { - if(add_prefix) { - oa->write_character(static_cast<CharType>('I')); - } - write_number(static_cast<int16_t>(n)); - } else if((std::numeric_limits<int32_t>::min) () <= n and n <= (std::numeric_limits<int32_t>::max) ()) { - if(add_prefix) { - oa->write_character(static_cast<CharType>('l')); - } - write_number(static_cast<int32_t>(n)); - } else if((std::numeric_limits<int64_t>::min) () <= n and n <= (std::numeric_limits<int64_t>::max) ()) { - if(add_prefix) { - oa->write_character(static_cast<CharType>('L')); - } - write_number(static_cast<int64_t>(n)); - } else { - JSON_THROW(out_of_range::create(407, "number overflow serializing "+std::to_string(n))); - } + switch(m_object->m_type) { + case value_t::object: + return (m_it.object_iterator == other.m_it.object_iterator); - } + case value_t::array: + return (m_it.array_iterator == other.m_it.array_iterator); - char ubjson_prefix(const BasicJsonType &j) const noexcept { - switch(j.type()) { - case value_t::null: - return 'Z'; - - case value_t::boolean: - return j.m_value.boolean ? 'T' : 'F'; - - case value_t::number_integer: { - if((std::numeric_limits<int8_t>::min) () <= j.m_value.number_integer and - j.m_value.number_integer <= (std::numeric_limits<int8_t>::max) ()) { - return 'i'; - } else if((std::numeric_limits<uint8_t>::min) () <= j.m_value.number_integer and - j.m_value.number_integer <= (std::numeric_limits<uint8_t>::max) ()) { - return 'U'; - } else if((std::numeric_limits<int16_t>::min) () <= j.m_value.number_integer and - j.m_value.number_integer <= (std::numeric_limits<int16_t>::max) ()) { - return 'I'; - } else if((std::numeric_limits<int32_t>::min) () <= j.m_value.number_integer and - j.m_value.number_integer <= (std::numeric_limits<int32_t>::max) ()) { - return 'l'; - } else { - return 'L'; + default: + return (m_it.primitive_iterator == other.m_it.primitive_iterator); } } - case value_t::number_unsigned: { - if(j.m_value.number_unsigned <= (std::numeric_limits<int8_t>::max) ()) { - return 'i'; - } else if(j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max) ()) { - return 'U'; - } else if(j.m_value.number_unsigned <= (std::numeric_limits<int16_t>::max) ()) { - return 'I'; - } else if(j.m_value.number_unsigned <= (std::numeric_limits<int32_t>::max) ()) { - return 'l'; - } else { - return 'L'; - } + bool operator!=(const iter_impl &other) const { + return not operator==(other); } - case value_t::number_float: - return 'D'; + bool operator<(const iter_impl &other) const { + if(JSON_UNLIKELY(m_object != other.m_object)) { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); + } - case value_t::string: - return 'S'; + assert(m_object != nullptr); - case value_t::array: - return '['; + switch(m_object->m_type) { + case value_t::object: + JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators")); - case value_t::object: - return '{'; + case value_t::array: + return (m_it.array_iterator < other.m_it.array_iterator); - default: - return 'N'; - } - } + default: + return (m_it.primitive_iterator < other.m_it.primitive_iterator); + } + } -private: - const bool is_little_endian = binary_reader<BasicJsonType>::little_endianess(); + bool operator<=(const iter_impl &other) const { + return not other.operator<(*this); + } - output_adapter_t<CharType> oa = nullptr; -}; -} -} + bool operator>(const iter_impl &other) const { + return not operator<=(other); + } -#include <cstdio> + bool operator>=(const iter_impl &other) const { + return not operator<(other); + } -namespace nlohmann { -namespace detail { -namespace dtoa_impl { -template<typename Target, typename Source> -Target reinterpret_bits(const Source source) { - static_assert(sizeof(Target) == sizeof(Source), "size mismatch"); - - Target target; - std::memcpy(&target, &source, sizeof(Source)); - return target; -} + iter_impl &operator+=(difference_type i) { + assert(m_object != nullptr); -struct diyfp { - static constexpr int kPrecision = 64; + switch(m_object->m_type) { + case value_t::object: + JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); - uint64_t f; - int e; + case value_t::array: { + std::advance(m_it.array_iterator, i); + break; + } - constexpr diyfp() noexcept : f(0), e(0) {} + default: { + m_it.primitive_iterator += i; + break; + } + } - constexpr diyfp(uint64_t f_, int e_) noexcept : f(f_), e(e_) {} + return *this; + } - static diyfp sub(const diyfp &x, const diyfp &y) noexcept { - assert(x.e == y.e); - assert(x.f >= y.f); + iter_impl &operator-=(difference_type i) { + return operator+=(-i); + } - return diyfp(x.f-y.f, x.e); - } + iter_impl operator+(difference_type i) const { + auto result = *this; + result += i; + return result; + } - static diyfp mul(const diyfp &x, const diyfp &y) noexcept { - static_assert(kPrecision == 64, "internal error"); + friend iter_impl operator+(difference_type i, const iter_impl &it) { + auto result = it; + result += i; + return result; + } - // + iter_impl operator-(difference_type i) const { + auto result = *this; + result -= i; + return result; + } - // + difference_type operator-(const iter_impl &other) const { + assert(m_object != nullptr); - // + switch(m_object->m_type) { + case value_t::object: + JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); - // + case value_t::array: + return m_it.array_iterator-other.m_it.array_iterator; - // + default: + return m_it.primitive_iterator-other.m_it.primitive_iterator; + } + } - const uint64_t u_lo = x.f & 0xFFFFFFFF; - const uint64_t u_hi = x.f >> 32; - const uint64_t v_lo = y.f & 0xFFFFFFFF; - const uint64_t v_hi = y.f >> 32; + reference operator[](difference_type n) const { + assert(m_object != nullptr); - const uint64_t p0 = u_lo * v_lo; - const uint64_t p1 = u_lo * v_hi; - const uint64_t p2 = u_hi * v_lo; - const uint64_t p3 = u_hi * v_hi; + switch(m_object->m_type) { + case value_t::object: + JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators")); - const uint64_t p0_hi = p0 >> 32; - const uint64_t p1_lo = p1 & 0xFFFFFFFF; - const uint64_t p1_hi = p1 >> 32; - const uint64_t p2_lo = p2 & 0xFFFFFFFF; - const uint64_t p2_hi = p2 >> 32; + case value_t::array: + return *std::next(m_it.array_iterator, n); - uint64_t Q = p0_hi+p1_lo+p2_lo; + case value_t::null: + JSON_THROW(invalid_iterator::create(214, "cannot get value")); - // + default: { + if(JSON_LIKELY(m_it.primitive_iterator.get_value() == -n)) { + return *m_object; + } - // + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } - Q += uint64_t{1} << (64-32-1); + typename object_t::key_type key() const { + assert(m_object != nullptr); - const uint64_t h = p3+p2_hi+p1_hi+(Q >> 32); + if(JSON_LIKELY(m_object->is_object())) { + return m_it.object_iterator->first; + } - return diyfp(h, x.e+y.e+64); - } + JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators")); + } - static diyfp normalize(diyfp x) noexcept { - assert(x.f != 0); + reference value() const { + return operator*(); + } - while((x.f >> 63) == 0) { - x.f <<= 1; - x.e--; - } + private: + pointer m_object = nullptr; - return x; + internal_iterator<typename std::remove_const<BasicJsonType>::type> m_it; + }; } +} - static diyfp normalize_to(const diyfp &x, const int target_exponent) noexcept { - const int delta = x.e-target_exponent; - - assert(delta >= 0); - assert(((x.f << delta) >> delta) == x.f); - - return diyfp(x.f << delta, target_exponent); - } -}; +namespace nlohmann { + namespace detail { + template<typename IteratorType> + class iteration_proxy { + private: + class iteration_proxy_internal { + private: + IteratorType anchor; -struct boundaries { - diyfp w; - diyfp minus; - diyfp plus; -}; + std::size_t array_index = 0; -template<typename FloatType> -boundaries compute_boundaries(FloatType value) { - assert(std::isfinite(value)); - assert(value > 0); + public: + explicit iteration_proxy_internal(IteratorType it) noexcept : anchor(it) {} - // + iteration_proxy_internal &operator*() { + return *this; + } - static_assert(std::numeric_limits<FloatType>::is_iec559, - "internal error: dtoa_short requires an IEEE-754 floating-point implementation"); + iteration_proxy_internal &operator++() { + ++anchor; + ++array_index; - constexpr int kPrecision = std::numeric_limits<FloatType>::digits; - constexpr int kBias = std::numeric_limits<FloatType>::max_exponent-1+(kPrecision-1); - constexpr int kMinExp = 1-kBias; - constexpr uint64_t kHiddenBit = uint64_t{1} << (kPrecision-1); + return *this; + } - using bits_type = typename std::conditional<kPrecision == 24, uint32_t, uint64_t>::type; + bool operator!=(const iteration_proxy_internal &o) const noexcept { + return anchor != o.anchor; + } - const uint64_t bits = reinterpret_bits<bits_type>(value); - const uint64_t E = bits >> (kPrecision-1); - const uint64_t F = bits & (kHiddenBit-1); + std::string key() const { + assert(anchor.m_object != nullptr); - const bool is_denormal = (E == 0); - const diyfp v = is_denormal - ? diyfp(F, kMinExp) - : diyfp(F+kHiddenBit, static_cast<int>(E)-kBias); + switch(anchor.m_object->type()) { + case value_t::array: + return std::to_string(array_index); - // + case value_t::object: + return anchor.key(); - // + default: + return ""; + } + } - // + typename IteratorType::reference value() const { + return anchor.value(); + } + }; - // + typename IteratorType::reference container; - // + public: + explicit iteration_proxy(typename IteratorType::reference cont) noexcept + : container(cont) {} - // + iteration_proxy_internal begin() noexcept { + return iteration_proxy_internal(container.begin()); + } - const bool lower_boundary_is_closer = (F == 0 and E > 1); - const diyfp m_plus = diyfp(2 * v.f+1, v.e-1); - const diyfp m_minus = lower_boundary_is_closer - ? diyfp(4 * v.f-1, v.e-2) - : diyfp(2 * v.f-1, v.e-1); + iteration_proxy_internal end() noexcept { + return iteration_proxy_internal(container.end()); + } + }; + } +} - const diyfp w_plus = diyfp::normalize(m_plus); +namespace nlohmann { + namespace detail { + template<typename Base> + class json_reverse_iterator : public std::reverse_iterator<Base> { + public: + using difference_type = std::ptrdiff_t; - const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e); + using base_iterator = std::reverse_iterator<Base>; - return {diyfp::normalize(v), w_minus, w_plus}; -} + using reference = typename Base::reference; -// + json_reverse_iterator(const typename base_iterator::iterator_type &it) noexcept + : base_iterator(it) {} -// + json_reverse_iterator(const base_iterator &it) noexcept : base_iterator(it) {} -// + json_reverse_iterator const operator++(int) { + return static_cast<json_reverse_iterator>(base_iterator::operator++(1)); + } -// + json_reverse_iterator &operator++() { + return static_cast<json_reverse_iterator &>(base_iterator::operator++()); + } -// + json_reverse_iterator const operator--(int) { + return static_cast<json_reverse_iterator>(base_iterator::operator--(1)); + } -// + json_reverse_iterator &operator--() { + return static_cast<json_reverse_iterator &>(base_iterator::operator--()); + } -// + json_reverse_iterator &operator+=(difference_type i) { + return static_cast<json_reverse_iterator &>(base_iterator::operator+=(i)); + } -// + json_reverse_iterator operator+(difference_type i) const { + return static_cast<json_reverse_iterator>(base_iterator::operator+(i)); + } -// + json_reverse_iterator operator-(difference_type i) const { + return static_cast<json_reverse_iterator>(base_iterator::operator-(i)); + } -// + difference_type operator-(const json_reverse_iterator &other) const { + return base_iterator(*this)-base_iterator(other); + } -// + reference operator[](difference_type n) const { + return *(this->operator+(n)); + } -// + auto key() const -> decltype(std::declval<Base>().key()) { + auto it = --this->base(); + return it.key(); + } -// + reference value() const { + auto it = --this->base(); + return it.operator*(); + } + }; + } +} -// +#include <ostream> -// +namespace nlohmann { + namespace detail { + template<typename CharType> + struct output_adapter_protocol { + virtual void write_character(CharType c) = 0; -// + virtual void write_characters(const CharType *s, std::size_t length) = 0; -// + virtual ~output_adapter_protocol() = default; + }; -// + template<typename CharType> + using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>; -// + template<typename CharType> + class output_vector_adapter : public output_adapter_protocol<CharType> { + public: + explicit output_vector_adapter(std::vector<CharType> &vec) : v(vec) {} -// + void write_character(CharType c) override { + v.push_back(c); + } -constexpr int kAlpha = -60; -constexpr int kGamma = -32; + void write_characters(const CharType *s, std::size_t length) override { + std::copy(s, s+length, std::back_inserter(v)); + } -struct cached_power { - uint64_t f; - int e; - int k; -}; + private: + std::vector<CharType> &v; + }; -inline cached_power get_cached_power_for_binary_exponent(int e) { - // + template<typename CharType> + class output_stream_adapter : public output_adapter_protocol<CharType> { + public: + explicit output_stream_adapter(std::basic_ostream<CharType> &s) : stream(s) {} - // + void write_character(CharType c) override { + stream.put(c); + } - // + void write_characters(const CharType *s, std::size_t length) override { + stream.write(s, static_cast<std::streamsize>(length)); + } - // + private: + std::basic_ostream<CharType> &stream; + }; - // + template<typename CharType, typename StringType = std::basic_string<CharType>> + class output_string_adapter : public output_adapter_protocol<CharType> { + public: + explicit output_string_adapter(StringType &s) : str(s) {} - // + void write_character(CharType c) override { + str.push_back(c); + } - // + void write_characters(const CharType *s, std::size_t length) override { + str.append(s, length); + } - // + private: + StringType &str; + }; - // + template<typename CharType, typename StringType = std::basic_string<CharType>> + class output_adapter { + public: + output_adapter(std::vector<CharType> &vec) + : oa(std::make_shared<output_vector_adapter<CharType>>(vec)) {} - // + output_adapter(std::basic_ostream<CharType> &s) + : oa(std::make_shared<output_stream_adapter<CharType>>(s)) {} - // + output_adapter(StringType &s) + : oa(std::make_shared<output_string_adapter<CharType, StringType>>(s)) {} - // + operator output_adapter_t<CharType>() { + return oa; + } - // + private: + output_adapter_t<CharType> oa = nullptr; + }; + } +} - constexpr int kCachedPowersSize = 79; - constexpr int kCachedPowersMinDecExp = -300; - constexpr int kCachedPowersDecStep = 8; +#include <cstdio> - static constexpr cached_power kCachedPowers[] = - { - {0xAB70FE17C79AC6CA, -1060, -300}, - {0xFF77B1FCBEBCDC4F, -1034, -292}, - {0xBE5691EF416BD60C, -1007, -284}, - {0x8DD01FAD907FFC3C, -980, -276}, - {0xD3515C2831559A83, -954, -268}, - {0x9D71AC8FADA6C9B5, -927, -260}, - {0xEA9C227723EE8BCB, -901, -252}, - {0xAECC49914078536D, -874, -244}, - {0x823C12795DB6CE57, -847, -236}, - {0xC21094364DFB5637, -821, -228}, - {0x9096EA6F3848984F, -794, -220}, - {0xD77485CB25823AC7, -768, -212}, - {0xA086CFCD97BF97F4, -741, -204}, - {0xEF340A98172AACE5, -715, -196}, - {0xB23867FB2A35B28E, -688, -188}, - {0x84C8D4DFD2C63F3B, -661, -180}, - {0xC5DD44271AD3CDBA, -635, -172}, - {0x936B9FCEBB25C996, -608, -164}, - {0xDBAC6C247D62A584, -582, -156}, - {0xA3AB66580D5FDAF6, -555, -148}, - {0xF3E2F893DEC3F126, -529, -140}, - {0xB5B5ADA8AAFF80B8, -502, -132}, - {0x87625F056C7C4A8B, -475, -124}, - {0xC9BCFF6034C13053, -449, -116}, - {0x964E858C91BA2655, -422, -108}, - {0xDFF9772470297EBD, -396, -100}, - {0xA6DFBD9FB8E5B88F, -369, -92}, - {0xF8A95FCF88747D94, -343, -84}, - {0xB94470938FA89BCF, -316, -76}, - {0x8A08F0F8BF0F156B, -289, -68}, - {0xCDB02555653131B6, -263, -60}, - {0x993FE2C6D07B7FAC, -236, -52}, - {0xE45C10C42A2B3B06, -210, -44}, - {0xAA242499697392D3, -183, -36}, - {0xFD87B5F28300CA0E, -157, -28}, - {0xBCE5086492111AEB, -130, -20}, - {0x8CBCCC096F5088CC, -103, -12}, - {0xD1B71758E219652C, -77, -4}, - {0x9C40000000000000, -50, 4}, - {0xE8D4A51000000000, -24, 12}, - {0xAD78EBC5AC620000, 3, 20}, - {0x813F3978F8940984, 30, 28}, - {0xC097CE7BC90715B3, 56, 36}, - {0x8F7E32CE7BEA5C70, 83, 44}, - {0xD5D238A4ABE98068, 109, 52}, - {0x9F4F2726179A2245, 136, 60}, - {0xED63A231D4C4FB27, 162, 68}, - {0xB0DE65388CC8ADA8, 189, 76}, - {0x83C7088E1AAB65DB, 216, 84}, - {0xC45D1DF942711D9A, 242, 92}, - {0x924D692CA61BE758, 269, 100}, - {0xDA01EE641A708DEA, 295, 108}, - {0xA26DA3999AEF774A, 322, 116}, - {0xF209787BB47D6B85, 348, 124}, - {0xB454E4A179DD1877, 375, 132}, - {0x865B86925B9BC5C2, 402, 140}, - {0xC83553C5C8965D3D, 428, 148}, - {0x952AB45CFA97A0B3, 455, 156}, - {0xDE469FBD99A05FE3, 481, 164}, - {0xA59BC234DB398C25, 508, 172}, - {0xF6C69A72A3989F5C, 534, 180}, - {0xB7DCBF5354E9BECE, 561, 188}, - {0x88FCF317F22241E2, 588, 196}, - {0xCC20CE9BD35C78A5, 614, 204}, - {0x98165AF37B2153DF, 641, 212}, - {0xE2A0B5DC971F303A, 667, 220}, - {0xA8D9D1535CE3B396, 694, 228}, - {0xFB9B7CD9A4A7443C, 720, 236}, - {0xBB764C4CA7A44410, 747, 244}, - {0x8BAB8EEFB6409C1A, 774, 252}, - {0xD01FEF10A657842C, 800, 260}, - {0x9B10A4E5E9913129, 827, 268}, - {0xE7109BFBA19C0C9D, 853, 276}, - {0xAC2820D9623BF429, 880, 284}, - {0x80444B5E7AA7CF85, 907, 292}, - {0xBF21E44003ACDD2D, 933, 300}, - {0x8E679C2F5E44FF8F, 960, 308}, - {0xD433179D9C8CB841, 986, 316}, - {0x9E19DB92B4E31BA9, 1013, 324}, - }; +namespace nlohmann { + namespace detail { + namespace dtoa_impl { + template<typename Target, typename Source> + Target reinterpret_bits(const Source source) { + static_assert(sizeof(Target) == sizeof(Source), "size mismatch"); - assert(e >= -1500); - assert(e <= 1500); - const int f = kAlpha-e-1; - const int k = (f * 78913) / (1 << 18)+(f > 0); + Target target; + std::memcpy(&target, &source, sizeof(Source)); + return target; + } - const int index = (-kCachedPowersMinDecExp+k+(kCachedPowersDecStep-1)) / kCachedPowersDecStep; - assert(index >= 0); - assert(index < kCachedPowersSize); - static_cast<void>(kCachedPowersSize); + struct diyfp { + static constexpr int kPrecision = 64; - const cached_power cached = kCachedPowers[index]; - assert(kAlpha <= cached.e+e+64); - assert(kGamma >= cached.e+e+64); + uint64_t f; + int e; - return cached; -} + constexpr diyfp() noexcept : f(0), e(0) {} -inline int find_largest_pow10(const uint32_t n, uint32_t &pow10) { - if(n >= 1000000000) { - pow10 = 1000000000; - return 10; - } else if(n >= 100000000) { - pow10 = 100000000; - return 9; - } else if(n >= 10000000) { - pow10 = 10000000; - return 8; - } else if(n >= 1000000) { - pow10 = 1000000; - return 7; - } else if(n >= 100000) { - pow10 = 100000; - return 6; - } else if(n >= 10000) { - pow10 = 10000; - return 5; - } else if(n >= 1000) { - pow10 = 1000; - return 4; - } else if(n >= 100) { - pow10 = 100; - return 3; - } else if(n >= 10) { - pow10 = 10; - return 2; - } else { - pow10 = 1; - return 1; - } -} + constexpr diyfp(uint64_t f_, int e_) noexcept : f(f_), e(e_) {} -inline void grisu2_round(char *buf, int len, uint64_t dist, uint64_t delta, - uint64_t rest, uint64_t ten_k) { - assert(len >= 1); - assert(dist <= delta); - assert(rest <= delta); - assert(ten_k > 0); + static diyfp sub(const diyfp &x, const diyfp &y) noexcept { + assert(x.e == y.e); + assert(x.f >= y.f); - // + return diyfp(x.f-y.f, x.e); + } - // + static diyfp mul(const diyfp &x, const diyfp &y) noexcept { + static_assert(kPrecision == 64, "internal error"); - while(rest < dist - and delta-rest >= ten_k - and (rest+ten_k < dist or dist-rest > rest+ten_k-dist)) { - assert(buf[len-1] != '0'); - buf[len-1]--; - rest += ten_k; - } -} + const uint64_t u_lo = x.f & 0xFFFFFFFF; + const uint64_t u_hi = x.f >> 32; + const uint64_t v_lo = y.f & 0xFFFFFFFF; + const uint64_t v_hi = y.f >> 32; -inline void grisu2_digit_gen(char *buffer, int &length, int &decimal_exponent, - diyfp M_minus, diyfp w, diyfp M_plus) { - static_assert(kAlpha >= -60, "internal error"); - static_assert(kGamma <= -32, "internal error"); + const uint64_t p0 = u_lo * v_lo; + const uint64_t p1 = u_lo * v_hi; + const uint64_t p2 = u_hi * v_lo; + const uint64_t p3 = u_hi * v_hi; - // + const uint64_t p0_hi = p0 >> 32; + const uint64_t p1_lo = p1 & 0xFFFFFFFF; + const uint64_t p1_hi = p1 >> 32; + const uint64_t p2_lo = p2 & 0xFFFFFFFF; + const uint64_t p2_hi = p2 >> 32; - // + uint64_t Q = p0_hi+p1_lo+p2_lo; - assert(M_plus.e >= kAlpha); - assert(M_plus.e <= kGamma); + Q += uint64_t{1} << (64-32-1); - uint64_t delta = diyfp::sub(M_plus, M_minus).f; - uint64_t dist = diyfp::sub(M_plus, w).f; + const uint64_t h = p3+p2_hi+p1_hi+(Q >> 32); - // + return diyfp(h, x.e+y.e+64); + } - const diyfp one(uint64_t{1} << -M_plus.e, M_plus.e); + static diyfp normalize(diyfp x) noexcept { + assert(x.f != 0); - uint32_t p1 = static_cast<uint32_t>(M_plus.f >> -one.e); - uint64_t p2 = M_plus.f & (one.f-1); + while((x.f >> 63) == 0) { + x.f <<= 1; + x.e--; + } - // + return x; + } - assert(p1 > 0); + static diyfp normalize_to(const diyfp &x, const int target_exponent) noexcept { + const int delta = x.e-target_exponent; - uint32_t pow10; - const int k = find_largest_pow10(p1, pow10); + assert(delta >= 0); + assert(((x.f << delta) >> delta) == x.f); - // + return diyfp(x.f << delta, target_exponent); + } + }; - // + struct boundaries { + diyfp w; + diyfp minus; + diyfp plus; + }; - // + template<typename FloatType> + boundaries compute_boundaries(FloatType value) { + assert(std::isfinite(value)); + assert(value > 0); - // + static_assert(std::numeric_limits<FloatType>::is_iec559, + "internal error: dtoa_short requires an IEEE-754 floating-point implementation"); - // + constexpr int kPrecision = std::numeric_limits<FloatType>::digits; + constexpr int kBias = std::numeric_limits<FloatType>::max_exponent-1+(kPrecision-1); + constexpr int kMinExp = 1-kBias; + constexpr uint64_t kHiddenBit = uint64_t{1} << (kPrecision-1); - // + using bits_type = typename std::conditional<kPrecision == 24, uint32_t, uint64_t>::type; - int n = k; - while(n > 0) { - // - const uint32_t d = p1 / pow10; - const uint32_t r = p1 % pow10; - // + const uint64_t bits = reinterpret_bits<bits_type>(value); + const uint64_t E = bits >> (kPrecision-1); + const uint64_t F = bits & (kHiddenBit-1); - // - assert(d <= 9); - buffer[length++] = static_cast<char>('0'+d); - // + const bool is_denormal = (E == 0); + const diyfp v = is_denormal + ? diyfp(F, kMinExp) + : diyfp(F+kHiddenBit, static_cast<int>(E)-kBias); - // - p1 = r; - n--; - // + const bool lower_boundary_is_closer = (F == 0 and E > 1); + const diyfp m_plus = diyfp(2 * v.f+1, v.e-1); + const diyfp m_minus = lower_boundary_is_closer + ? diyfp(4 * v.f-1, v.e-2) + : diyfp(2 * v.f-1, v.e-1); - // + const diyfp w_plus = diyfp::normalize(m_plus); - // + const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e); - // + return {diyfp::normalize(v), w_minus, w_plus}; + } - const uint64_t rest = (uint64_t{p1} << -one.e)+p2; - if(rest <= delta) { - decimal_exponent += n; + constexpr int kAlpha = -60; + constexpr int kGamma = -32; - // + struct cached_power { + uint64_t f; + int e; + int k; + }; - // + inline cached_power get_cached_power_for_binary_exponent(int e) { + constexpr int kCachedPowersSize = 79; + constexpr int kCachedPowersMinDecExp = -300; + constexpr int kCachedPowersDecStep = 8; - // - const uint64_t ten_n = uint64_t{pow10} << -one.e; - grisu2_round(buffer, length, dist, delta, rest, ten_n); + static constexpr cached_power kCachedPowers[] = + { + {0xAB70FE17C79AC6CA, -1060, -300}, + {0xFF77B1FCBEBCDC4F, -1034, -292}, + {0xBE5691EF416BD60C, -1007, -284}, + {0x8DD01FAD907FFC3C, -980, -276}, + {0xD3515C2831559A83, -954, -268}, + {0x9D71AC8FADA6C9B5, -927, -260}, + {0xEA9C227723EE8BCB, -901, -252}, + {0xAECC49914078536D, -874, -244}, + {0x823C12795DB6CE57, -847, -236}, + {0xC21094364DFB5637, -821, -228}, + {0x9096EA6F3848984F, -794, -220}, + {0xD77485CB25823AC7, -768, -212}, + {0xA086CFCD97BF97F4, -741, -204}, + {0xEF340A98172AACE5, -715, -196}, + {0xB23867FB2A35B28E, -688, -188}, + {0x84C8D4DFD2C63F3B, -661, -180}, + {0xC5DD44271AD3CDBA, -635, -172}, + {0x936B9FCEBB25C996, -608, -164}, + {0xDBAC6C247D62A584, -582, -156}, + {0xA3AB66580D5FDAF6, -555, -148}, + {0xF3E2F893DEC3F126, -529, -140}, + {0xB5B5ADA8AAFF80B8, -502, -132}, + {0x87625F056C7C4A8B, -475, -124}, + {0xC9BCFF6034C13053, -449, -116}, + {0x964E858C91BA2655, -422, -108}, + {0xDFF9772470297EBD, -396, -100}, + {0xA6DFBD9FB8E5B88F, -369, -92}, + {0xF8A95FCF88747D94, -343, -84}, + {0xB94470938FA89BCF, -316, -76}, + {0x8A08F0F8BF0F156B, -289, -68}, + {0xCDB02555653131B6, -263, -60}, + {0x993FE2C6D07B7FAC, -236, -52}, + {0xE45C10C42A2B3B06, -210, -44}, + {0xAA242499697392D3, -183, -36}, + {0xFD87B5F28300CA0E, -157, -28}, + {0xBCE5086492111AEB, -130, -20}, + {0x8CBCCC096F5088CC, -103, -12}, + {0xD1B71758E219652C, -77, -4}, + {0x9C40000000000000, -50, 4}, + {0xE8D4A51000000000, -24, 12}, + {0xAD78EBC5AC620000, 3, 20}, + {0x813F3978F8940984, 30, 28}, + {0xC097CE7BC90715B3, 56, 36}, + {0x8F7E32CE7BEA5C70, 83, 44}, + {0xD5D238A4ABE98068, 109, 52}, + {0x9F4F2726179A2245, 136, 60}, + {0xED63A231D4C4FB27, 162, 68}, + {0xB0DE65388CC8ADA8, 189, 76}, + {0x83C7088E1AAB65DB, 216, 84}, + {0xC45D1DF942711D9A, 242, 92}, + {0x924D692CA61BE758, 269, 100}, + {0xDA01EE641A708DEA, 295, 108}, + {0xA26DA3999AEF774A, 322, 116}, + {0xF209787BB47D6B85, 348, 124}, + {0xB454E4A179DD1877, 375, 132}, + {0x865B86925B9BC5C2, 402, 140}, + {0xC83553C5C8965D3D, 428, 148}, + {0x952AB45CFA97A0B3, 455, 156}, + {0xDE469FBD99A05FE3, 481, 164}, + {0xA59BC234DB398C25, 508, 172}, + {0xF6C69A72A3989F5C, 534, 180}, + {0xB7DCBF5354E9BECE, 561, 188}, + {0x88FCF317F22241E2, 588, 196}, + {0xCC20CE9BD35C78A5, 614, 204}, + {0x98165AF37B2153DF, 641, 212}, + {0xE2A0B5DC971F303A, 667, 220}, + {0xA8D9D1535CE3B396, 694, 228}, + {0xFB9B7CD9A4A7443C, 720, 236}, + {0xBB764C4CA7A44410, 747, 244}, + {0x8BAB8EEFB6409C1A, 774, 252}, + {0xD01FEF10A657842C, 800, 260}, + {0x9B10A4E5E9913129, 827, 268}, + {0xE7109BFBA19C0C9D, 853, 276}, + {0xAC2820D9623BF429, 880, 284}, + {0x80444B5E7AA7CF85, 907, 292}, + {0xBF21E44003ACDD2D, 933, 300}, + {0x8E679C2F5E44FF8F, 960, 308}, + {0xD433179D9C8CB841, 986, 316}, + {0x9E19DB92B4E31BA9, 1013, 324}, + }; + + assert(e >= -1500); + assert(e <= 1500); + const int f = kAlpha-e-1; + const int k = (f * 78913) / (1 << 18)+(f > 0); + + const int index = (-kCachedPowersMinDecExp+k+(kCachedPowersDecStep-1)) / kCachedPowersDecStep; + assert(index >= 0); + assert(index < kCachedPowersSize); + static_cast<void>(kCachedPowersSize); + + const cached_power cached = kCachedPowers[index]; + assert(kAlpha <= cached.e+e+64); + assert(kGamma >= cached.e+e+64); + + return cached; + } + + inline int find_largest_pow10(const uint32_t n, uint32_t &pow10) { + if(n >= 1000000000) { + pow10 = 1000000000; + return 10; + } else if(n >= 100000000) { + pow10 = 100000000; + return 9; + } else if(n >= 10000000) { + pow10 = 10000000; + return 8; + } else if(n >= 1000000) { + pow10 = 1000000; + return 7; + } else if(n >= 100000) { + pow10 = 100000; + return 6; + } else if(n >= 10000) { + pow10 = 10000; + return 5; + } else if(n >= 1000) { + pow10 = 1000; + return 4; + } else if(n >= 100) { + pow10 = 100; + return 3; + } else if(n >= 10) { + pow10 = 10; + return 2; + } else { + pow10 = 1; + return 1; + } + } - return; - } + inline void grisu2_round(char *buf, int len, uint64_t dist, uint64_t delta, + uint64_t rest, uint64_t ten_k) { + assert(len >= 1); + assert(dist <= delta); + assert(rest <= delta); + assert(ten_k > 0); - pow10 /= 10; - // + while(rest < dist + and delta-rest >= ten_k + and (rest+ten_k < dist or dist-rest > rest+ten_k-dist)) { + assert(buf[len-1] != '0'); + buf[len-1]--; + rest += ten_k; + } + } - } + inline void grisu2_digit_gen(char *buffer, int &length, int &decimal_exponent, + diyfp M_minus, diyfp w, diyfp M_plus) { + static_assert(kAlpha >= -60, "internal error"); + static_assert(kGamma <= -32, "internal error"); - // + assert(M_plus.e >= kAlpha); + assert(M_plus.e <= kGamma); - // + uint64_t delta = diyfp::sub(M_plus, M_minus).f; + uint64_t dist = diyfp::sub(M_plus, w).f; - // + const diyfp one(uint64_t{1} << -M_plus.e, M_plus.e); - // + uint32_t p1 = static_cast<uint32_t>(M_plus.f >> -one.e); + uint64_t p2 = M_plus.f & (one.f-1); - // + assert(p1 > 0); - // + uint32_t pow10; + const int k = find_largest_pow10(p1, pow10); - // + int n = k; + while(n > 0) { + const uint32_t d = p1 / pow10; + const uint32_t r = p1 % pow10; - // + assert(d <= 9); + buffer[length++] = static_cast<char>('0'+d); - // + p1 = r; + n--; - // + const uint64_t rest = (uint64_t{p1} << -one.e)+p2; + if(rest <= delta) { + decimal_exponent += n; - // + const uint64_t ten_n = uint64_t{pow10} << -one.e; + grisu2_round(buffer, length, dist, delta, rest, ten_n); - // + return; + } - // + pow10 /= 10; - // + } - assert(p2 > delta); + assert(p2 > delta); - int m = 0; - for(;;) { - // - assert(p2 <= UINT64_MAX / 10); - p2 *= 10; - const uint64_t d = p2 >> -one.e; - const uint64_t r = p2 & (one.f-1); - // + int m = 0; + for(;;) { + assert(p2 <= UINT64_MAX / 10); + p2 *= 10; + const uint64_t d = p2 >> -one.e; + const uint64_t r = p2 & (one.f-1); - // - assert(d <= 9); - buffer[length++] = static_cast<char>('0'+d); - // + assert(d <= 9); + buffer[length++] = static_cast<char>('0'+d); - // - p2 = r; - m++; - // + p2 = r; + m++; - // + delta *= 10; + dist *= 10; + if(p2 <= delta) { + break; + } + } - delta *= 10; - dist *= 10; - if(p2 <= delta) { - break; - } - } + decimal_exponent -= m; - decimal_exponent -= m; + const uint64_t ten_m = one.f; + grisu2_round(buffer, length, dist, delta, p2, ten_m); - // + } - // - const uint64_t ten_m = one.f; - grisu2_round(buffer, length, dist, delta, p2, ten_m); + inline void grisu2(char *buf, int &len, int &decimal_exponent, + diyfp m_minus, diyfp v, diyfp m_plus) { + assert(m_plus.e == m_minus.e); + assert(m_plus.e == v.e); - // + const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e); - // + const diyfp c_minus_k(cached.f, cached.e); - // + const diyfp w = diyfp::mul(v, c_minus_k); + const diyfp w_minus = diyfp::mul(m_minus, c_minus_k); + const diyfp w_plus = diyfp::mul(m_plus, c_minus_k); -} + const diyfp M_minus(w_minus.f+1, w_minus.e); + const diyfp M_plus(w_plus.f-1, w_plus.e); -inline void grisu2(char *buf, int &len, int &decimal_exponent, - diyfp m_minus, diyfp v, diyfp m_plus) { - assert(m_plus.e == m_minus.e); - assert(m_plus.e == v.e); + decimal_exponent = -cached.k; - // + grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus); + } - // + template<typename FloatType> + void grisu2(char *buf, int &len, int &decimal_exponent, FloatType value) { + static_assert(diyfp::kPrecision >= std::numeric_limits<FloatType>::digits+3, + "internal error: not enough precision"); - const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e); + assert(std::isfinite(value)); + assert(value > 0); - const diyfp c_minus_k(cached.f, cached.e); +#if 0 + const boundaries w = compute_boundaries(static_cast<double>(value)); +#else + const boundaries w = compute_boundaries(value); +#endif - const diyfp w = diyfp::mul(v, c_minus_k); - const diyfp w_minus = diyfp::mul(m_minus, c_minus_k); - const diyfp w_plus = diyfp::mul(m_plus, c_minus_k); + grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus); + } - // + inline char *append_exponent(char *buf, int e) { + assert(e > -1000); + assert(e < 1000); - // + if(e < 0) { + e = -e; + *buf++ = '-'; + } else { + *buf++ = '+'; + } - // + uint32_t k = static_cast<uint32_t>(e); + if(k < 10) { + *buf++ = '0'; + *buf++ = static_cast<char>('0'+k); + } else if(k < 100) { + *buf++ = static_cast<char>('0'+k / 10); + k %= 10; + *buf++ = static_cast<char>('0'+k); + } else { + *buf++ = static_cast<char>('0'+k / 100); + k %= 100; + *buf++ = static_cast<char>('0'+k / 10); + k %= 10; + *buf++ = static_cast<char>('0'+k); + } - // + return buf; + } - // + inline char *format_buffer(char *buf, int len, int decimal_exponent, + int min_exp, int max_exp) { + assert(min_exp < 0); + assert(max_exp > 0); - // + const int k = len; + const int n = len+decimal_exponent; - const diyfp M_minus(w_minus.f+1, w_minus.e); - const diyfp M_plus(w_plus.f-1, w_plus.e); + if(k <= n and n <= max_exp) { + std::memset(buf+k, '0', static_cast<size_t>(n-k)); - decimal_exponent = -cached.k; + buf[n+0] = '.'; + buf[n+1] = '0'; + return buf+(n+2); + } - grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus); -} + if(0 < n and n <= max_exp) { + assert(k > n); -template<typename FloatType> -void grisu2(char *buf, int &len, int &decimal_exponent, FloatType value) { - static_assert(diyfp::kPrecision >= std::numeric_limits<FloatType>::digits+3, - "internal error: not enough precision"); + std::memmove(buf+(n+1), buf+n, static_cast<size_t>(k-n)); + buf[n] = '.'; + return buf+(k+1); + } - assert(std::isfinite(value)); - assert(value > 0); + if(min_exp < n and n <= 0) { + std::memmove(buf+(2+ -n), buf, static_cast<size_t>(k)); + buf[0] = '0'; + buf[1] = '.'; + std::memset(buf+2, '0', static_cast<size_t>(-n)); + return buf+(2+(-n)+k); + } - // + if(k == 1) { + buf += 1; + } else { + std::memmove(buf+2, buf+1, static_cast<size_t>(k-1)); + buf[1] = '.'; + buf += 1+k; + } - // + *buf++ = 'e'; + return append_exponent(buf, n-1); + } -#if 0 - const boundaries w = compute_boundaries(static_cast<double>(value)); -#else - const boundaries w = compute_boundaries(value); -#endif + } - grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus); -} + template<typename FloatType> + char *to_chars(char *first, char *last, FloatType value) { + static_cast<void>(last); + assert(std::isfinite(value)); -inline char *append_exponent(char *buf, int e) { - assert(e > -1000); - assert(e < 1000); + if(std::signbit(value)) { + value = -value; + *first++ = '-'; + } - if(e < 0) { - e = -e; - *buf++ = '-'; - } else { - *buf++ = '+'; - } + if(value == 0) { + *first++ = '0'; - uint32_t k = static_cast<uint32_t>(e); - if(k < 10) { - *buf++ = '0'; - *buf++ = static_cast<char>('0'+k); - } else if(k < 100) { - *buf++ = static_cast<char>('0'+k / 10); - k %= 10; - *buf++ = static_cast<char>('0'+k); - } else { - *buf++ = static_cast<char>('0'+k / 100); - k %= 100; - *buf++ = static_cast<char>('0'+k / 10); - k %= 10; - *buf++ = static_cast<char>('0'+k); - } + *first++ = '.'; + *first++ = '0'; + return first; + } - return buf; -} + assert(last-first >= std::numeric_limits<FloatType>::max_digits10); -inline char *format_buffer(char *buf, int len, int decimal_exponent, - int min_exp, int max_exp) { - assert(min_exp < 0); - assert(max_exp > 0); + int len = 0; + int decimal_exponent = 0; + dtoa_impl::grisu2(first, len, decimal_exponent, value); - const int k = len; - const int n = len+decimal_exponent; + assert(len <= std::numeric_limits<FloatType>::max_digits10); - if(k <= n and n <= max_exp) { - std::memset(buf+k, '0', static_cast<size_t>(n-k)); + constexpr int kMinExp = -4; - buf[n+0] = '.'; - buf[n+1] = '0'; - return buf+(n+2); - } + constexpr int kMaxExp = std::numeric_limits<FloatType>::digits10; - if(0 < n and n <= max_exp) { - assert(k > n); + assert(last-first >= kMaxExp+2); + assert(last-first >= 2+(-kMinExp-1)+std::numeric_limits<FloatType>::max_digits10); + assert(last-first >= std::numeric_limits<FloatType>::max_digits10+6); - std::memmove(buf+(n+1), buf+n, static_cast<size_t>(k-n)); - buf[n] = '.'; - return buf+(k+1); - } + return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp); + } - if(min_exp < n and n <= 0) { - std::memmove(buf+(2+ -n), buf, static_cast<size_t>(k)); - buf[0] = '0'; - buf[1] = '.'; - std::memset(buf+2, '0', static_cast<size_t>(-n)); - return buf+(2+(-n)+k); } +} - if(k == 1) { - buf += 1; - } else { - std::memmove(buf+2, buf+1, static_cast<size_t>(k-1)); - buf[1] = '.'; - buf += 1+k; - } +namespace nlohmann { + namespace detail { + template<typename BasicJsonType> + class serializer { + using string_t = typename BasicJsonType::string_t; + using number_float_t = typename BasicJsonType::number_float_t; + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + static constexpr uint8_t UTF8_ACCEPT = 0; + static constexpr uint8_t UTF8_REJECT = 1; + + public: + serializer(output_adapter_t<char> s, const char ichar) + : o(std::move(s)), loc(std::localeconv()), + thousands_sep(loc->thousands_sep == nullptr ? '\0' : *(loc->thousands_sep)), + decimal_point(loc->decimal_point == nullptr ? '\0' : *(loc->decimal_point)), + indent_char(ichar), indent_string(512, indent_char) {} + + serializer(const serializer &) = delete; + + serializer &operator=(const serializer &) = delete; + + void dump(const BasicJsonType &val, const bool pretty_print, + const bool ensure_ascii, + const unsigned int indent_step, + const unsigned int current_indent = 0) { + switch(val.m_type) { + case value_t::object: { + if(val.m_value.object->empty()) { + o->write_characters("{}", 2); + return; + } - *buf++ = 'e'; - return append_exponent(buf, n-1); -} + if(pretty_print) { + o->write_characters("{\n", 2); -} + const auto new_indent = current_indent+indent_step; + if(JSON_UNLIKELY(indent_string.size() < new_indent)) { + indent_string.resize(indent_string.size() * 2, ' '); + } -template<typename FloatType> -char *to_chars(char *first, char *last, FloatType value) { - static_cast<void>(last); - assert(std::isfinite(value)); + auto i = val.m_value.object->cbegin(); + for(std::size_t cnt = 0; cnt < val.m_value.object->size()-1; ++cnt, ++i) { + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } - if(std::signbit(value)) { - value = -value; - *first++ = '-'; - } + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character('}'); + } else { + o->write_character('{'); + + auto i = val.m_value.object->cbegin(); + for(std::size_t cnt = 0; cnt < val.m_value.object->size()-1; ++cnt, ++i) { + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } - if(value == 0) { - *first++ = '0'; + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); - *first++ = '.'; - *first++ = '0'; - return first; - } + o->write_character('}'); + } - assert(last-first >= std::numeric_limits<FloatType>::max_digits10); + return; + } - int len = 0; - int decimal_exponent = 0; - dtoa_impl::grisu2(first, len, decimal_exponent, value); + case value_t::array: { + if(val.m_value.array->empty()) { + o->write_characters("[]", 2); + return; + } - assert(len <= std::numeric_limits<FloatType>::max_digits10); + if(pretty_print) { + o->write_characters("[\n", 2); - constexpr int kMinExp = -4; + const auto new_indent = current_indent+indent_step; + if(JSON_UNLIKELY(indent_string.size() < new_indent)) { + indent_string.resize(indent_string.size() * 2, ' '); + } - constexpr int kMaxExp = std::numeric_limits<FloatType>::digits10; + for(auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend()-1; ++i) { + o->write_characters(indent_string.c_str(), new_indent); + dump(*i, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } - assert(last-first >= kMaxExp+2); - assert(last-first >= 2+(-kMinExp-1)+std::numeric_limits<FloatType>::max_digits10); - assert(last-first >= std::numeric_limits<FloatType>::max_digits10+6); + assert(not val.m_value.array->empty()); + o->write_characters(indent_string.c_str(), new_indent); + dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent); - return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp); -} + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character(']'); + } else { + o->write_character('['); -} -} + for(auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend()-1; ++i) { + dump(*i, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } -namespace nlohmann { -namespace detail { -template<typename BasicJsonType> -class serializer { - using string_t = typename BasicJsonType::string_t; - using number_float_t = typename BasicJsonType::number_float_t; - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - static constexpr uint8_t UTF8_ACCEPT = 0; - static constexpr uint8_t UTF8_REJECT = 1; - -public: - serializer(output_adapter_t<char> s, const char ichar) - : o(std::move(s)), loc(std::localeconv()), - thousands_sep(loc->thousands_sep == nullptr ? '\0' : *(loc->thousands_sep)), - decimal_point(loc->decimal_point == nullptr ? '\0' : *(loc->decimal_point)), - indent_char(ichar), indent_string(512, indent_char) {} - - serializer(const serializer &) = delete; - - serializer &operator=(const serializer &) = delete; - - void dump(const BasicJsonType &val, const bool pretty_print, - const bool ensure_ascii, - const unsigned int indent_step, - const unsigned int current_indent = 0) { - switch(val.m_type) { - case value_t::object: { - if(val.m_value.object->empty()) { - o->write_characters("{}", 2); - return; - } + assert(not val.m_value.array->empty()); + dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent); - if(pretty_print) { - o->write_characters("{\n", 2); + o->write_character(']'); + } - const auto new_indent = current_indent+indent_step; - if(JSON_UNLIKELY(indent_string.size() < new_indent)) { - indent_string.resize(indent_string.size() * 2, ' '); + return; } - auto i = val.m_value.object->cbegin(); - for(std::size_t cnt = 0; cnt < val.m_value.object->size()-1; ++cnt, ++i) { - o->write_characters(indent_string.c_str(), new_indent); + case value_t::string: { o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); - o->write_characters("\": ", 3); - dump(i->second, true, ensure_ascii, indent_step, new_indent); - o->write_characters(",\n", 2); - } - - assert(i != val.m_value.object->cend()); - assert(std::next(i) == val.m_value.object->cend()); - o->write_characters(indent_string.c_str(), new_indent); - o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); - o->write_characters("\": ", 3); - dump(i->second, true, ensure_ascii, indent_step, new_indent); - - o->write_character('\n'); - o->write_characters(indent_string.c_str(), current_indent); - o->write_character('}'); - } else { - o->write_character('{'); - - auto i = val.m_value.object->cbegin(); - for(std::size_t cnt = 0; cnt < val.m_value.object->size()-1; ++cnt, ++i) { + dump_escaped(*val.m_value.string, ensure_ascii); o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); - o->write_characters("\":", 2); - dump(i->second, false, ensure_ascii, indent_step, current_indent); - o->write_character(','); + return; } - assert(i != val.m_value.object->cend()); - assert(std::next(i) == val.m_value.object->cend()); - o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); - o->write_characters("\":", 2); - dump(i->second, false, ensure_ascii, indent_step, current_indent); - - o->write_character('}'); - } - - return; - } - - case value_t::array: { - if(val.m_value.array->empty()) { - o->write_characters("[]", 2); - return; - } - - if(pretty_print) { - o->write_characters("[\n", 2); - - const auto new_indent = current_indent+indent_step; - if(JSON_UNLIKELY(indent_string.size() < new_indent)) { - indent_string.resize(indent_string.size() * 2, ' '); + case value_t::boolean: { + if(val.m_value.boolean) { + o->write_characters("true", 4); + } else { + o->write_characters("false", 5); + } + return; } - for(auto i = val.m_value.array->cbegin(); - i != val.m_value.array->cend()-1; ++i) { - o->write_characters(indent_string.c_str(), new_indent); - dump(*i, true, ensure_ascii, indent_step, new_indent); - o->write_characters(",\n", 2); + case value_t::number_integer: { + dump_integer(val.m_value.number_integer); + return; } - assert(not val.m_value.array->empty()); - o->write_characters(indent_string.c_str(), new_indent); - dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent); - - o->write_character('\n'); - o->write_characters(indent_string.c_str(), current_indent); - o->write_character(']'); - } else { - o->write_character('['); - - for(auto i = val.m_value.array->cbegin(); - i != val.m_value.array->cend()-1; ++i) { - dump(*i, false, ensure_ascii, indent_step, current_indent); - o->write_character(','); + case value_t::number_unsigned: { + dump_integer(val.m_value.number_unsigned); + return; } - assert(not val.m_value.array->empty()); - dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent); - - o->write_character(']'); - } - - return; - } + case value_t::number_float: { + dump_float(val.m_value.number_float); + return; + } - case value_t::string: { - o->write_character('\"'); - dump_escaped(*val.m_value.string, ensure_ascii); - o->write_character('\"'); - return; - } + case value_t::discarded: { + o->write_characters("<discarded>", 11); + return; + } - case value_t::boolean: { - if(val.m_value.boolean) { - o->write_characters("true", 4); - } else { - o->write_characters("false", 5); + case value_t::null: { + o->write_characters("null", 4); + return; + } } - return; } - case value_t::number_integer: { - dump_integer(val.m_value.number_integer); - return; - } - - case value_t::number_unsigned: { - dump_integer(val.m_value.number_unsigned); - return; - } + private: + void dump_escaped(const string_t &s, const bool ensure_ascii) { + uint32_t codepoint; + uint8_t state = UTF8_ACCEPT; + std::size_t bytes = 0; - case value_t::number_float: { - dump_float(val.m_value.number_float); - return; - } + for(std::size_t i = 0; i < s.size(); ++i) { + const auto byte = static_cast<uint8_t>(s[i]); - case value_t::discarded: { - o->write_characters("<discarded>", 11); - return; - } + switch(decode(state, codepoint, byte)) { + case UTF8_ACCEPT: { + switch(codepoint) { + case 0x08: { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'b'; + break; + } - case value_t::null: { - o->write_characters("null", 4); - return; - } - } - } + case 0x09: { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 't'; + break; + } -private: - void dump_escaped(const string_t &s, const bool ensure_ascii) { - uint32_t codepoint; - uint8_t state = UTF8_ACCEPT; - std::size_t bytes = 0; - - for(std::size_t i = 0; i < s.size(); ++i) { - const auto byte = static_cast<uint8_t>(s[i]); - - switch(decode(state, codepoint, byte)) { - case UTF8_ACCEPT: { - switch(codepoint) { - case 0x08: { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 'b'; - break; - } + case 0x0A: { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'n'; + break; + } - case 0x09: { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 't'; - break; - } + case 0x0C: { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'f'; + break; + } - case 0x0A: { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 'n'; - break; - } + case 0x0D: { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'r'; + break; + } - case 0x0C: { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 'f'; - break; - } + case 0x22: { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\"'; + break; + } - case 0x0D: { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 'r'; - break; - } + case 0x5C: { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\\'; + break; + } + + default: { + if((codepoint <= 0x1F) or (ensure_ascii and (codepoint >= 0x7F))) { + if(codepoint <= 0xFFFF) { + std::snprintf(string_buffer.data()+bytes, 7, "\\u%04x", + static_cast<uint16_t>(codepoint)); + bytes += 6; + } else { + std::snprintf(string_buffer.data()+bytes, 13, "\\u%04x\\u%04x", + static_cast<uint16_t>(0xD7C0+(codepoint >> 10)), + static_cast<uint16_t>(0xDC00+(codepoint & 0x3FF))); + bytes += 12; + } + } else { + string_buffer[bytes++] = s[i]; + } + break; + } + } - case 0x22: { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = '\"'; + if(string_buffer.size()-bytes < 13) { + o->write_characters(string_buffer.data(), bytes); + bytes = 0; + } break; } - case 0x5C: { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = '\\'; - break; + case UTF8_REJECT: { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << static_cast<int>(byte); + JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index "+std::to_string(i)+": 0x"+ss.str())); } default: { - if((codepoint <= 0x1F) or (ensure_ascii and (codepoint >= 0x7F))) { - if(codepoint <= 0xFFFF) { - std::snprintf(string_buffer.data()+bytes, 7, "\\u%04x", - static_cast<uint16_t>(codepoint)); - bytes += 6; - } else { - std::snprintf(string_buffer.data()+bytes, 13, "\\u%04x\\u%04x", - static_cast<uint16_t>(0xD7C0+(codepoint >> 10)), - static_cast<uint16_t>(0xDC00+(codepoint & 0x3FF))); - bytes += 12; - } - } else { + if(not ensure_ascii) { string_buffer[bytes++] = s[i]; } break; } } + } - if(string_buffer.size()-bytes < 13) { + if(JSON_LIKELY(state == UTF8_ACCEPT)) { + if(bytes > 0) { o->write_characters(string_buffer.data(), bytes); - bytes = 0; } - break; - } - - case UTF8_REJECT: { + } else { std::stringstream ss; - ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << static_cast<int>(byte); - JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index "+std::to_string(i)+": 0x"+ss.str())); + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex + << static_cast<int>(static_cast<uint8_t>(s.back())); + JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x"+ss.str())); } + } - default: { - if(not ensure_ascii) { - string_buffer[bytes++] = s[i]; - } - break; + template<typename NumberType, detail::enable_if_t< + std::is_same<NumberType, number_unsigned_t>::value or + std::is_same<NumberType, number_integer_t>::value, + int> = 0> + void dump_integer(NumberType x) { + if(x == 0) { + o->write_character('0'); + return; } - } - } - if(JSON_LIKELY(state == UTF8_ACCEPT)) { - if(bytes > 0) { - o->write_characters(string_buffer.data(), bytes); - } - } else { - std::stringstream ss; - ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex - << static_cast<int>(static_cast<uint8_t>(s.back())); - JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x"+ss.str())); - } - } + const bool is_negative = (x <= 0) and (x != 0); + std::size_t i = 0; - template<typename NumberType, detail::enable_if_t< - std::is_same<NumberType, number_unsigned_t>::value or - std::is_same<NumberType, number_integer_t>::value, - int> = 0> - void dump_integer(NumberType x) { - if(x == 0) { - o->write_character('0'); - return; - } + while(x != 0) { + assert(i < number_buffer.size()-1); - const bool is_negative = (x <= 0) and (x != 0); - std::size_t i = 0; + const auto digit = std::labs(static_cast<long>(x % 10)); + number_buffer[i++] = static_cast<char>('0'+digit); + x /= 10; + } - while(x != 0) { - assert(i < number_buffer.size()-1); + if(is_negative) { + assert(i < number_buffer.size()-2); + number_buffer[i++] = '-'; + } - const auto digit = std::labs(static_cast<long>(x % 10)); - number_buffer[i++] = static_cast<char>('0'+digit); - x /= 10; - } + std::reverse(number_buffer.begin(), number_buffer.begin()+i); + o->write_characters(number_buffer.data(), i); + } - if(is_negative) { - assert(i < number_buffer.size()-2); - number_buffer[i++] = '-'; - } + void dump_float(number_float_t x) { + if(not std::isfinite(x)) { + o->write_characters("null", 4); + return; + } - std::reverse(number_buffer.begin(), number_buffer.begin()+i); - o->write_characters(number_buffer.data(), i); - } + static constexpr bool is_ieee_single_or_double + = (std::numeric_limits<number_float_t>::is_iec559 and std::numeric_limits<number_float_t>::digits == 24 and + std::numeric_limits<number_float_t>::max_exponent == 128) or + (std::numeric_limits<number_float_t>::is_iec559 and std::numeric_limits<number_float_t>::digits == 53 and + std::numeric_limits<number_float_t>::max_exponent == 1024); - void dump_float(number_float_t x) { - if(not std::isfinite(x)) { - o->write_characters("null", 4); - return; - } + dump_float(x, std::integral_constant<bool, is_ieee_single_or_double>()); + } - // + void dump_float(number_float_t x, std::true_type) { + char *begin = number_buffer.data(); + char *end = ::nlohmann::detail::to_chars(begin, begin+number_buffer.size(), x); - static constexpr bool is_ieee_single_or_double - = (std::numeric_limits<number_float_t>::is_iec559 and std::numeric_limits<number_float_t>::digits == 24 and - std::numeric_limits<number_float_t>::max_exponent == 128) or - (std::numeric_limits<number_float_t>::is_iec559 and std::numeric_limits<number_float_t>::digits == 53 and - std::numeric_limits<number_float_t>::max_exponent == 1024); + o->write_characters(begin, static_cast<size_t>(end-begin)); + } - dump_float(x, std::integral_constant<bool, is_ieee_single_or_double>()); - } + void dump_float(number_float_t x, std::false_type) { + static constexpr auto d = std::numeric_limits<number_float_t>::max_digits10; - void dump_float(number_float_t x, std::true_type) { - char *begin = number_buffer.data(); - char *end = ::nlohmann::detail::to_chars(begin, begin+number_buffer.size(), x); + std::ptrdiff_t len = snprintf(number_buffer.data(), number_buffer.size(), "%.*g", d, x); - o->write_characters(begin, static_cast<size_t>(end-begin)); - } + assert(len > 0); - void dump_float(number_float_t x, std::false_type) { - static constexpr auto d = std::numeric_limits<number_float_t>::max_digits10; + assert(static_cast<std::size_t>(len) < number_buffer.size()); - std::ptrdiff_t len = snprintf(number_buffer.data(), number_buffer.size(), "%.*g", d, x); + if(thousands_sep != '\0') { + const auto end = std::remove(number_buffer.begin(), + number_buffer.begin()+len, thousands_sep); + std::fill(end, number_buffer.end(), '\0'); + assert((end-number_buffer.begin()) <= len); + len = (end-number_buffer.begin()); + } - assert(len > 0); + if(decimal_point != '\0' and decimal_point != '.') { + const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point); + if(dec_pos != number_buffer.end()) { + *dec_pos = '.'; + } + } - assert(static_cast<std::size_t>(len) < number_buffer.size()); + o->write_characters(number_buffer.data(), static_cast<std::size_t>(len)); - if(thousands_sep != '\0') { - const auto end = std::remove(number_buffer.begin(), - number_buffer.begin()+len, thousands_sep); - std::fill(end, number_buffer.end(), '\0'); - assert((end-number_buffer.begin()) <= len); - len = (end-number_buffer.begin()); - } + const bool value_is_int_like = + std::none_of(number_buffer.begin(), number_buffer.begin()+len+1, + [](char c) { + return (c == '.' or c == 'e'); + }); - if(decimal_point != '\0' and decimal_point != '.') { - const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point); - if(dec_pos != number_buffer.end()) { - *dec_pos = '.'; + if(value_is_int_like) { + o->write_characters(".0", 2); + } } - } - - o->write_characters(number_buffer.data(), static_cast<std::size_t>(len)); - const bool value_is_int_like = - std::none_of(number_buffer.begin(), number_buffer.begin()+len+1, - [](char c) { - return (c == '.' or c == 'e'); - }); - - if(value_is_int_like) { - o->write_characters(".0", 2); - } - } - - static uint8_t decode(uint8_t &state, uint32_t &codep, const uint8_t byte) noexcept { - static const std::array<uint8_t, 400> utf8d = - { + static uint8_t decode(uint8_t &state, uint32_t &codep, const uint8_t byte) noexcept { + static const std::array<uint8_t, 400> utf8d = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, - 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, - 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, - 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, - 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 - } - }; + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, + 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, + 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, + 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, + 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + } + }; - const uint8_t type = utf8d[byte]; + const uint8_t type = utf8d[byte]; - codep = (state != UTF8_ACCEPT) - ? (byte & 0x3fu) | (codep << 6) - : static_cast<uint32_t>(0xff >> type) & (byte); + codep = (state != UTF8_ACCEPT) + ? (byte & 0x3fu) | (codep << 6) + : static_cast<uint32_t>(0xff >> type) & (byte); - state = utf8d[256u+state * 16u+type]; - return state; - } + state = utf8d[256u+state * 16u+type]; + return state; + } -private: - output_adapter_t<char> o = nullptr; + private: + output_adapter_t<char> o = nullptr; - std::array<char, 64> number_buffer{{}}; + std::array<char, 64> number_buffer{{}}; - const std::lconv *loc = nullptr; + const std::lconv *loc = nullptr; - const char thousands_sep = '\0'; + const char thousands_sep = '\0'; - const char decimal_point = '\0'; + const char decimal_point = '\0'; - std::array<char, 512> string_buffer{{}}; + std::array<char, 512> string_buffer{{}}; - const char indent_char; + const char indent_char; - string_t indent_string; -}; -} + string_t indent_string; + }; + } } namespace nlohmann { -namespace detail { -template<typename BasicJsonType> -class json_ref { -public: - using value_type = BasicJsonType; + namespace detail { + template<typename BasicJsonType> + class json_ref { + public: + using value_type = BasicJsonType; - json_ref(value_type &&value) - : owned_value(std::move(value)), value_ref(&owned_value), is_rvalue(true) {} + json_ref(value_type &&value) + : owned_value(std::move(value)), value_ref(&owned_value), is_rvalue(true) {} - json_ref(const value_type &value) - : value_ref(const_cast<value_type *>(&value)), is_rvalue(false) {} + json_ref(const value_type &value) + : value_ref(const_cast<value_type *>(&value)), is_rvalue(false) {} - json_ref(std::initializer_list<json_ref> init) - : owned_value(init), value_ref(&owned_value), is_rvalue(true) {} + json_ref(std::initializer_list<json_ref> init) + : owned_value(init), value_ref(&owned_value), is_rvalue(true) {} - template<class... Args> - json_ref(Args &&... args) - : owned_value(std::forward<Args>(args)...), value_ref(&owned_value), is_rvalue(true) {} + template<class... Args> + json_ref(Args &&... args) + : owned_value(std::forward<Args>(args)...), value_ref(&owned_value), is_rvalue(true) {} - json_ref(json_ref &&) = default; + json_ref(json_ref &&) = default; - json_ref(const json_ref &) = delete; + json_ref(const json_ref &) = delete; - json_ref &operator=(const json_ref &) = delete; + json_ref &operator=(const json_ref &) = delete; - value_type moved_or_copied() const { - if(is_rvalue) { - return std::move(*value_ref); - } - return *value_ref; - } + value_type moved_or_copied() const { + if(is_rvalue) { + return std::move(*value_ref); + } + return *value_ref; + } - value_type const &operator*() const { - return *static_cast<value_type const *>(value_ref); - } + value_type const &operator*() const { + return *static_cast<value_type const *>(value_ref); + } - value_type const *operator->() const { - return static_cast<value_type const *>(value_ref); - } + value_type const *operator->() const { + return static_cast<value_type const *>(value_ref); + } -private: - mutable value_type owned_value = nullptr; - value_type *value_ref = nullptr; - const bool is_rvalue; -}; -} + private: + mutable value_type owned_value = nullptr; + value_type *value_ref = nullptr; + const bool is_rvalue; + }; + } } namespace nlohmann { -template<typename BasicJsonType> -class json_pointer { - NLOHMANN_BASIC_JSON_TPL_DECLARATION - friend - class basic_json; + template<typename BasicJsonType> + class json_pointer { + NLOHMANN_BASIC_JSON_TPL_DECLARATION + friend + class basic_json; -public: - explicit json_pointer(const std::string &s = "") - : reference_tokens(split(s)) {} + public: + explicit json_pointer(const std::string &s = "") + : reference_tokens(split(s)) {} - std::string to_string() const noexcept { - return std::accumulate(reference_tokens.begin(), reference_tokens.end(), - std::string{}, - [](const std::string &a, const std::string &b) { - return a+"/"+escape(b); - }); - } + std::string to_string() const noexcept { + return std::accumulate(reference_tokens.begin(), reference_tokens.end(), + std::string{}, + [](const std::string &a, const std::string &b) { + return a+"/"+escape(b); + }); + } - operator std::string() const { - return to_string(); - } + operator std::string() const { + return to_string(); + } - static int array_index(const std::string &s) { - std::size_t processed_chars = 0; - const int res = std::stoi(s, &processed_chars); + static int array_index(const std::string &s) { + std::size_t processed_chars = 0; + const int res = std::stoi(s, &processed_chars); - if(JSON_UNLIKELY(processed_chars != s.size())) { - JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '"+s+"'")); + if(JSON_UNLIKELY(processed_chars != s.size())) { + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '"+s+"'")); + } + + return res; } - return res; - } + private: + std::string pop_back() { + if(JSON_UNLIKELY(is_root())) { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); + } -private: - std::string pop_back() { - if(JSON_UNLIKELY(is_root())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); + auto last = reference_tokens.back(); + reference_tokens.pop_back(); + return last; } - auto last = reference_tokens.back(); - reference_tokens.pop_back(); - return last; - } + bool is_root() const { + return reference_tokens.empty(); + } - bool is_root() const { - return reference_tokens.empty(); - } + json_pointer top() const { + if(JSON_UNLIKELY(is_root())) { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); + } - json_pointer top() const { - if(JSON_UNLIKELY(is_root())) { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); + json_pointer result = *this; + result.reference_tokens = {reference_tokens[0]}; + return result; } - json_pointer result = *this; - result.reference_tokens = {reference_tokens[0]}; - return result; - } + BasicJsonType &get_and_create(BasicJsonType &j) const { + using size_type = typename BasicJsonType::size_type; + auto result = &j; - BasicJsonType &get_and_create(BasicJsonType &j) const { - using size_type = typename BasicJsonType::size_type; - auto result = &j; + for(const auto &reference_token : reference_tokens) { + switch(result->m_type) { + case detail::value_t::null: { + if(reference_token == "0") { + result = &result->operator[](0); + } else { + result = &result->operator[](reference_token); + } + break; + } - for(const auto &reference_token : reference_tokens) { - switch(result->m_type) { - case detail::value_t::null: { - if(reference_token == "0") { - result = &result->operator[](0); - } else { + case detail::value_t::object: { result = &result->operator[](reference_token); + break; } - break; - } - - case detail::value_t::object: { - result = &result->operator[](reference_token); - break; - } - case detail::value_t::array: { - JSON_TRY { - result = &result->operator[](static_cast<size_type>(array_index(reference_token))); - } - JSON_CATCH(std::invalid_argument &) { - JSON_THROW(detail::parse_error::create(109, 0, "array index '"+reference_token+"' is not a number")); + case detail::value_t::array: { + JSON_TRY { + result = &result->operator[](static_cast<size_type>(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument &) { + JSON_THROW(detail::parse_error::create(109, 0, "array index '"+reference_token+"' is not a number")); + } + break; } - break; - } - default: - JSON_THROW(detail::type_error::create(313, "invalid value to unflatten")); + default: + JSON_THROW(detail::type_error::create(313, "invalid value to unflatten")); + } } - } - return *result; - } - - BasicJsonType &get_unchecked(BasicJsonType *ptr) const { - using size_type = typename BasicJsonType::size_type; - for(const auto &reference_token : reference_tokens) { - if(ptr->m_type == detail::value_t::null) { - const bool nums = - std::all_of(reference_token.begin(), reference_token.end(), - [](const char x) { - return (x >= '0' and x <= '9'); - }); + return *result; + } - *ptr = (nums or reference_token == "-") - ? detail::value_t::array - : detail::value_t::object; - } + BasicJsonType &get_unchecked(BasicJsonType *ptr) const { + using size_type = typename BasicJsonType::size_type; + for(const auto &reference_token : reference_tokens) { + if(ptr->m_type == detail::value_t::null) { + const bool nums = + std::all_of(reference_token.begin(), reference_token.end(), + [](const char x) { + return (x >= '0' and x <= '9'); + }); - switch(ptr->m_type) { - case detail::value_t::object: { - ptr = &ptr->operator[](reference_token); - break; + *ptr = (nums or reference_token == "-") + ? detail::value_t::array + : detail::value_t::object; } - case detail::value_t::array: { - if(JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) { - JSON_THROW(detail::parse_error::create(106, 0, - "array index '"+reference_token+ - "' must not begin with '0'")); + switch(ptr->m_type) { + case detail::value_t::object: { + ptr = &ptr->operator[](reference_token); + break; } - if(reference_token == "-") { - ptr = &ptr->operator[](ptr->m_value.array->size()); - } else { - JSON_TRY { - ptr = &ptr->operator[]( - static_cast<size_type>(array_index(reference_token))); + case detail::value_t::array: { + if(JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '"+reference_token+ + "' must not begin with '0'")); } - JSON_CATCH(std::invalid_argument &) { - JSON_THROW(detail::parse_error::create(109, 0, "array index '"+reference_token+"' is not a number")); + + if(reference_token == "-") { + ptr = &ptr->operator[](ptr->m_value.array->size()); + } else { + JSON_TRY { + ptr = &ptr->operator[]( + static_cast<size_type>(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument &) { + JSON_THROW(detail::parse_error::create(109, 0, "array index '"+reference_token+"' is not a number")); + } } + break; } - break; - } - default: - JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '"+reference_token+"'")); + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '"+reference_token+"'")); + } } + + return *ptr; } - return *ptr; - } + BasicJsonType &get_checked(BasicJsonType *ptr) const { + using size_type = typename BasicJsonType::size_type; + for(const auto &reference_token : reference_tokens) { + switch(ptr->m_type) { + case detail::value_t::object: { + ptr = &ptr->at(reference_token); + break; + } - BasicJsonType &get_checked(BasicJsonType *ptr) const { - using size_type = typename BasicJsonType::size_type; - for(const auto &reference_token : reference_tokens) { - switch(ptr->m_type) { - case detail::value_t::object: { - ptr = &ptr->at(reference_token); - break; - } + case detail::value_t::array: { + if(JSON_UNLIKELY(reference_token == "-")) { + JSON_THROW(detail::out_of_range::create(402, + "array index '-' ("+std::to_string(ptr->m_value.array->size())+ + ") is out of range")); + } - case detail::value_t::array: { - if(JSON_UNLIKELY(reference_token == "-")) { - JSON_THROW(detail::out_of_range::create(402, - "array index '-' ("+std::to_string(ptr->m_value.array->size())+ - ") is out of range")); - } + if(JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '"+reference_token+ + "' must not begin with '0'")); + } - if(JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) { - JSON_THROW(detail::parse_error::create(106, 0, - "array index '"+reference_token+ - "' must not begin with '0'")); + JSON_TRY { + ptr = &ptr->at(static_cast<size_type>(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument &) { + JSON_THROW(detail::parse_error::create(109, 0, "array index '"+reference_token+"' is not a number")); + } + break; } - JSON_TRY { - ptr = &ptr->at(static_cast<size_type>(array_index(reference_token))); - } - JSON_CATCH(std::invalid_argument &) { - JSON_THROW(detail::parse_error::create(109, 0, "array index '"+reference_token+"' is not a number")); - } - break; + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '"+reference_token+"'")); } - - default: - JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '"+reference_token+"'")); } + + return *ptr; } - return *ptr; - } + const BasicJsonType &get_unchecked(const BasicJsonType *ptr) const { + using size_type = typename BasicJsonType::size_type; + for(const auto &reference_token : reference_tokens) { + switch(ptr->m_type) { + case detail::value_t::object: { + ptr = &ptr->operator[](reference_token); + break; + } - const BasicJsonType &get_unchecked(const BasicJsonType *ptr) const { - using size_type = typename BasicJsonType::size_type; - for(const auto &reference_token : reference_tokens) { - switch(ptr->m_type) { - case detail::value_t::object: { - ptr = &ptr->operator[](reference_token); - break; - } + case detail::value_t::array: { + if(JSON_UNLIKELY(reference_token == "-")) { + JSON_THROW(detail::out_of_range::create(402, + "array index '-' ("+std::to_string(ptr->m_value.array->size())+ + ") is out of range")); + } - case detail::value_t::array: { - if(JSON_UNLIKELY(reference_token == "-")) { - JSON_THROW(detail::out_of_range::create(402, - "array index '-' ("+std::to_string(ptr->m_value.array->size())+ - ") is out of range")); - } + if(JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '"+reference_token+ + "' must not begin with '0'")); + } - if(JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) { - JSON_THROW(detail::parse_error::create(106, 0, - "array index '"+reference_token+ - "' must not begin with '0'")); + JSON_TRY { + ptr = &ptr->operator[]( + static_cast<size_type>(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument &) { + JSON_THROW(detail::parse_error::create(109, 0, "array index '"+reference_token+"' is not a number")); + } + break; } - JSON_TRY { - ptr = &ptr->operator[]( - static_cast<size_type>(array_index(reference_token))); - } - JSON_CATCH(std::invalid_argument &) { - JSON_THROW(detail::parse_error::create(109, 0, "array index '"+reference_token+"' is not a number")); - } - break; + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '"+reference_token+"'")); } - - default: - JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '"+reference_token+"'")); } + + return *ptr; } - return *ptr; - } + const BasicJsonType &get_checked(const BasicJsonType *ptr) const { + using size_type = typename BasicJsonType::size_type; + for(const auto &reference_token : reference_tokens) { + switch(ptr->m_type) { + case detail::value_t::object: { + ptr = &ptr->at(reference_token); + break; + } - const BasicJsonType &get_checked(const BasicJsonType *ptr) const { - using size_type = typename BasicJsonType::size_type; - for(const auto &reference_token : reference_tokens) { - switch(ptr->m_type) { - case detail::value_t::object: { - ptr = &ptr->at(reference_token); - break; - } + case detail::value_t::array: { + if(JSON_UNLIKELY(reference_token == "-")) { + JSON_THROW(detail::out_of_range::create(402, + "array index '-' ("+std::to_string(ptr->m_value.array->size())+ + ") is out of range")); + } - case detail::value_t::array: { - if(JSON_UNLIKELY(reference_token == "-")) { - JSON_THROW(detail::out_of_range::create(402, - "array index '-' ("+std::to_string(ptr->m_value.array->size())+ - ") is out of range")); - } + if(JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '"+reference_token+ + "' must not begin with '0'")); + } - if(JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) { - JSON_THROW(detail::parse_error::create(106, 0, - "array index '"+reference_token+ - "' must not begin with '0'")); + JSON_TRY { + ptr = &ptr->at(static_cast<size_type>(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument &) { + JSON_THROW(detail::parse_error::create(109, 0, "array index '"+reference_token+"' is not a number")); + } + break; } - JSON_TRY { - ptr = &ptr->at(static_cast<size_type>(array_index(reference_token))); - } - JSON_CATCH(std::invalid_argument &) { - JSON_THROW(detail::parse_error::create(109, 0, "array index '"+reference_token+"' is not a number")); - } - break; + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '"+reference_token+"'")); } - - default: - JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '"+reference_token+"'")); } - } - return *ptr; - } + return *ptr; + } - static std::vector<std::string> split(const std::string &reference_string) { - std::vector<std::string> result; + static std::vector<std::string> split(const std::string &reference_string) { + std::vector<std::string> result; - if(reference_string.empty()) { - return result; - } + if(reference_string.empty()) { + return result; + } - if(JSON_UNLIKELY(reference_string[0] != '/')) { - JSON_THROW(detail::parse_error::create(107, 1, - "JSON pointer must be empty or begin with '/' - was: '"+ - reference_string+"'")); - } + if(JSON_UNLIKELY(reference_string[0] != '/')) { + JSON_THROW(detail::parse_error::create(107, 1, + "JSON pointer must be empty or begin with '/' - was: '"+ + reference_string+"'")); + } - for( + for( - std::size_t slash = reference_string.find_first_of('/', 1), + std::size_t slash = reference_string.find_first_of('/', 1), - start = 1; + start = 1; - start != 0; + start != 0; - start = slash+1, + start = slash+1, - slash = reference_string.find_first_of('/', start)) { - auto reference_token = reference_string.substr(start, slash-start); + slash = reference_string.find_first_of('/', start)) { + auto reference_token = reference_string.substr(start, slash-start); - for(std::size_t pos = reference_token.find_first_of('~'); - pos != std::string::npos; - pos = reference_token.find_first_of('~', pos+1)) { - assert(reference_token[pos] == '~'); + for(std::size_t pos = reference_token.find_first_of('~'); + pos != std::string::npos; + pos = reference_token.find_first_of('~', pos+1)) { + assert(reference_token[pos] == '~'); - if(JSON_UNLIKELY(pos == reference_token.size()-1 or - (reference_token[pos+1] != '0' and - reference_token[pos+1] != '1'))) { - JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'")); + if(JSON_UNLIKELY(pos == reference_token.size()-1 or + (reference_token[pos+1] != '0' and + reference_token[pos+1] != '1'))) { + JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'")); + } } + + unescape(reference_token); + result.push_back(reference_token); } - unescape(reference_token); - result.push_back(reference_token); + return result; } - return result; - } - - static void replace_substring(std::string &s, const std::string &f, - const std::string &t) { - assert(not f.empty()); - for(auto pos = s.find(f); - pos != std::string::npos; - s.replace(pos, f.size(), t), - pos = s.find(f, pos+t.size())) {} - } + static void replace_substring(std::string &s, const std::string &f, + const std::string &t) { + assert(not f.empty()); + for(auto pos = s.find(f); + pos != std::string::npos; + s.replace(pos, f.size(), t), + pos = s.find(f, pos+t.size())) {} + } - static std::string escape(std::string s) { - replace_substring(s, "~", "~0"); - replace_substring(s, "/", "~1"); - return s; - } + static std::string escape(std::string s) { + replace_substring(s, "~", "~0"); + replace_substring(s, "/", "~1"); + return s; + } - static void unescape(std::string &s) { - replace_substring(s, "~1", "/"); - replace_substring(s, "~0", "~"); - } + static void unescape(std::string &s) { + replace_substring(s, "~1", "/"); + replace_substring(s, "~0", "~"); + } - static void flatten(const std::string &reference_string, - const BasicJsonType &value, - BasicJsonType &result) { - switch(value.m_type) { - case detail::value_t::array: { - if(value.m_value.array->empty()) { - result[reference_string] = nullptr; - } else { - for(std::size_t i = 0; i < value.m_value.array->size(); ++i) { - flatten(reference_string+"/"+std::to_string(i), - value.m_value.array->operator[](i), result); + static void flatten(const std::string &reference_string, + const BasicJsonType &value, + BasicJsonType &result) { + switch(value.m_type) { + case detail::value_t::array: { + if(value.m_value.array->empty()) { + result[reference_string] = nullptr; + } else { + for(std::size_t i = 0; i < value.m_value.array->size(); ++i) { + flatten(reference_string+"/"+std::to_string(i), + value.m_value.array->operator[](i), result); + } } + break; } - break; - } - case detail::value_t::object: { - if(value.m_value.object->empty()) { - result[reference_string] = nullptr; - } else { - for(const auto &element : *value.m_value.object) { - flatten(reference_string+"/"+escape(element.first), element.second, result); + case detail::value_t::object: { + if(value.m_value.object->empty()) { + result[reference_string] = nullptr; + } else { + for(const auto &element : *value.m_value.object) { + flatten(reference_string+"/"+escape(element.first), element.second, result); + } } + break; + } + + default: { + result[reference_string] = value; + break; } - break; } + } - default: { - result[reference_string] = value; - break; + static BasicJsonType + unflatten(const BasicJsonType &value) { + if(JSON_UNLIKELY(not value.is_object())) { + JSON_THROW(detail::type_error::create(314, "only objects can be unflattened")); } - } - } - static BasicJsonType - unflatten(const BasicJsonType &value) { - if(JSON_UNLIKELY(not value.is_object())) { - JSON_THROW(detail::type_error::create(314, "only objects can be unflattened")); - } + BasicJsonType result; - BasicJsonType result; + for(const auto &element : *value.m_value.object) { + if(JSON_UNLIKELY(not element.second.is_primitive())) { + JSON_THROW(detail::type_error::create(315, "values in object must be primitive")); + } - for(const auto &element : *value.m_value.object) { - if(JSON_UNLIKELY(not element.second.is_primitive())) { - JSON_THROW(detail::type_error::create(315, "values in object must be primitive")); + json_pointer(element.first).get_and_create(result) = element.second; } - json_pointer(element.first).get_and_create(result) = element.second; + return result; } - return result; - } - - friend bool operator==(json_pointer const &lhs, - json_pointer const &rhs) noexcept { - return (lhs.reference_tokens == rhs.reference_tokens); - } + friend bool operator==(json_pointer const &lhs, + json_pointer const &rhs) noexcept { + return (lhs.reference_tokens == rhs.reference_tokens); + } - friend bool operator!=(json_pointer const &lhs, - json_pointer const &rhs) noexcept { - return not(lhs == rhs); - } + friend bool operator!=(json_pointer const &lhs, + json_pointer const &rhs) noexcept { + return not(lhs == rhs); + } - std::vector<std::string> reference_tokens; -}; + std::vector<std::string> reference_tokens; + }; } namespace nlohmann { -template<typename, typename> -struct adl_serializer { - template<typename BasicJsonType, typename ValueType> - static void from_json(BasicJsonType &&j, ValueType &val) noexcept( - noexcept(::nlohmann::from_json(std::forward<BasicJsonType>(j), val))) { - ::nlohmann::from_json(std::forward<BasicJsonType>(j), val); - } + template<typename, typename> + struct adl_serializer { + template<typename BasicJsonType, typename ValueType> + static void from_json(BasicJsonType &&j, ValueType &val) noexcept( + noexcept(::nlohmann::from_json(std::forward<BasicJsonType>(j), val))) { + ::nlohmann::from_json(std::forward<BasicJsonType>(j), val); + } - template<typename BasicJsonType, typename ValueType> - static void to_json(BasicJsonType &j, ValueType &&val) noexcept( - noexcept(::nlohmann::to_json(j, std::forward<ValueType>(val)))) { - ::nlohmann::to_json(j, std::forward<ValueType>(val)); - } -}; + template<typename BasicJsonType, typename ValueType> + static void to_json(BasicJsonType &j, ValueType &&val) noexcept( + noexcept(::nlohmann::to_json(j, std::forward<ValueType>(val)))) { + ::nlohmann::to_json(j, std::forward<ValueType>(val)); + } + }; } namespace nlohmann { -NLOHMANN_BASIC_JSON_TPL_DECLARATION -class basic_json { -private: - template<detail::value_t> friend - struct detail::external_constructor; - friend ::nlohmann::json_pointer<basic_json>; - friend ::nlohmann::detail::parser<basic_json>; - friend ::nlohmann::detail::serializer<basic_json>; - - template<typename BasicJsonType> - friend - class ::nlohmann::detail::iter_impl; - - template<typename BasicJsonType, typename CharType> - friend - class ::nlohmann::detail::binary_writer; - - template<typename BasicJsonType> - friend - class ::nlohmann::detail::binary_reader; + NLOHMANN_BASIC_JSON_TPL_DECLARATION + class basic_json { + private: + template<detail::value_t> friend + struct detail::external_constructor; + friend ::nlohmann::json_pointer<basic_json>; + friend ::nlohmann::detail::parser<basic_json>; + friend ::nlohmann::detail::serializer<basic_json>; - using basic_json_t = NLOHMANN_BASIC_JSON_TPL; + template<typename BasicJsonType> + friend + class ::nlohmann::detail::iter_impl; - using lexer = ::nlohmann::detail::lexer<basic_json>; - using parser = ::nlohmann::detail::parser<basic_json>; + using basic_json_t = NLOHMANN_BASIC_JSON_TPL; - using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t; - template<typename BasicJsonType> - using internal_iterator = ::nlohmann::detail::internal_iterator<BasicJsonType>; - template<typename BasicJsonType> - using iter_impl = ::nlohmann::detail::iter_impl<BasicJsonType>; - template<typename Iterator> - using iteration_proxy = ::nlohmann::detail::iteration_proxy<Iterator>; - template<typename Base> using json_reverse_iterator = ::nlohmann::detail::json_reverse_iterator<Base>; + using lexer = ::nlohmann::detail::lexer<basic_json>; + using parser = ::nlohmann::detail::parser<basic_json>; - template<typename CharType> - using output_adapter_t = ::nlohmann::detail::output_adapter_t<CharType>; + using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t; + template<typename BasicJsonType> + using internal_iterator = ::nlohmann::detail::internal_iterator<BasicJsonType>; + template<typename BasicJsonType> + using iter_impl = ::nlohmann::detail::iter_impl<BasicJsonType>; + template<typename Iterator> + using iteration_proxy = ::nlohmann::detail::iteration_proxy<Iterator>; + template<typename Base> using json_reverse_iterator = ::nlohmann::detail::json_reverse_iterator<Base>; - using binary_reader = ::nlohmann::detail::binary_reader<basic_json>; - template<typename CharType> using binary_writer = ::nlohmann::detail::binary_writer<basic_json, CharType>; + template<typename CharType> + using output_adapter_t = ::nlohmann::detail::output_adapter_t<CharType>; - using serializer = ::nlohmann::detail::serializer<basic_json>; + using serializer = ::nlohmann::detail::serializer<basic_json>; -public: - using value_t = detail::value_t; + public: + using value_t = detail::value_t; - using json_pointer = ::nlohmann::json_pointer<basic_json>; - template<typename T, typename SFINAE> - using json_serializer = JSONSerializer<T, SFINAE>; + using json_pointer = ::nlohmann::json_pointer<basic_json>; + template<typename T, typename SFINAE> + using json_serializer = JSONSerializer<T, SFINAE>; - using initializer_list_t = std::initializer_list<detail::json_ref<basic_json>>; + using initializer_list_t = std::initializer_list<detail::json_ref<basic_json>>; - using exception = detail::exception; + using exception = detail::exception; - using parse_error = detail::parse_error; + using parse_error = detail::parse_error; - using invalid_iterator = detail::invalid_iterator; + using invalid_iterator = detail::invalid_iterator; - using type_error = detail::type_error; + using type_error = detail::type_error; - using out_of_range = detail::out_of_range; + using out_of_range = detail::out_of_range; - using other_error = detail::other_error; + using other_error = detail::other_error; - using value_type = basic_json; + using value_type = basic_json; - using reference = value_type &; + using reference = value_type &; - using const_reference = const value_type &; + using const_reference = const value_type &; - using difference_type = std::ptrdiff_t; + using difference_type = std::ptrdiff_t; - using size_type = std::size_t; + using size_type = std::size_t; - using allocator_type = AllocatorType<basic_json>; + using allocator_type = AllocatorType<basic_json>; - using pointer = typename std::allocator_traits<allocator_type>::pointer; + using pointer = typename std::allocator_traits<allocator_type>::pointer; - using const_pointer = typename std::allocator_traits<allocator_type>::const_pointer; + using const_pointer = typename std::allocator_traits<allocator_type>::const_pointer; - using iterator = iter_impl<basic_json>; + using iterator = iter_impl<basic_json>; - using const_iterator = iter_impl<const basic_json>; + using const_iterator = iter_impl<const basic_json>; - using reverse_iterator = json_reverse_iterator<typename basic_json::iterator>; + using reverse_iterator = json_reverse_iterator<typename basic_json::iterator>; - using const_reverse_iterator = json_reverse_iterator<typename basic_json::const_iterator>; + using const_reverse_iterator = json_reverse_iterator<typename basic_json::const_iterator>; - static allocator_type get_allocator() { - return allocator_type(); - } + static allocator_type get_allocator() { + return allocator_type(); + } - static basic_json meta() { - basic_json result; + static basic_json meta() { + basic_json result; - result["copyright"] = "(C) 2013-2017 Niels Lohmann"; - result["name"] = "JSON for Modern C++"; - result["version"]["string"] = - std::to_string(NLOHMANN_JSON_VERSION_MAJOR)+"."+ - std::to_string(NLOHMANN_JSON_VERSION_MINOR)+"."+ - std::to_string(NLOHMANN_JSON_VERSION_PATCH); - result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR; - result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR; - result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH; + result["copyright"] = "(C) 2013-2017 Niels Lohmann"; + result["name"] = "JSON for Modern C++"; + result["version"]["string"] = + std::to_string(NLOHMANN_JSON_VERSION_MAJOR)+"."+ + std::to_string(NLOHMANN_JSON_VERSION_MINOR)+"."+ + std::to_string(NLOHMANN_JSON_VERSION_PATCH); + result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR; + result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR; + result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH; #ifdef _WIN32 - result["platform"] = "win32"; + result["platform"] = "win32"; #elif defined __linux__ - result["platform"] = "linux"; + result["platform"] = "linux"; #elif defined __APPLE__ result["platform"] = "apple"; #elif defined __unix__ @@ -6579,15 +4729,15 @@ class basic_json { #endif #if defined(__ICC) || defined(__INTEL_COMPILER) - result["compiler"] = {{"family", "icc"}, {"version", __INTEL_COMPILER}}; + result["compiler"] = {{"family", "icc"}, {"version", __INTEL_COMPILER}}; #elif defined(__clang__) - result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}}; + result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}}; #elif defined(__GNUC__) || defined(__GNUG__) - result["compiler"] = {{"family", "gcc"}, - {"version", std::to_string(__GNUC__)+"."+std::to_string(__GNUC_MINOR__)+"."+ - std::to_string(__GNUC_PATCHLEVEL__)}}; + result["compiler"] = {{"family", "gcc"}, + {"version", std::to_string(__GNUC__)+"."+std::to_string(__GNUC_MINOR__)+"."+ + std::to_string(__GNUC_PATCHLEVEL__)}}; #elif defined(__HP_cc) || defined(__HP_aCC) - result["compiler"] = "hp" + result["compiler"] = "hp" #elif defined(__IBMCPP__) result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}}; #elif defined(_MSC_VER) @@ -6601,2213 +4751,2135 @@ class basic_json { #endif #ifdef __cplusplus - result["compiler"]["c++"] = std::to_string(__cplusplus); + result["compiler"]["c++"] = std::to_string(__cplusplus); #else - result["compiler"]["c++"] = "unknown"; + result["compiler"]["c++"] = "unknown"; #endif - return result; - } + return result; + } #if defined(JSON_HAS_CPP_14) - using object_comparator_t = std::less<>; + using object_comparator_t = std::less<>; #else - using object_comparator_t = std::less<StringType>; + using object_comparator_t = std::less<StringType>; #endif - using object_t = ObjectType<StringType, - basic_json, - object_comparator_t, - AllocatorType<std::pair<const StringType, - basic_json>>>; + using object_t = ObjectType<StringType, + basic_json, + object_comparator_t, + AllocatorType<std::pair<const StringType, + basic_json>>>; - using array_t = ArrayType<basic_json, AllocatorType<basic_json>>; + using array_t = ArrayType<basic_json, AllocatorType<basic_json>>; - using string_t = StringType; + using string_t = StringType; - using boolean_t = BooleanType; + using boolean_t = BooleanType; - using number_integer_t = NumberIntegerType; + using number_integer_t = NumberIntegerType; - using number_unsigned_t = NumberUnsignedType; + using number_unsigned_t = NumberUnsignedType; - using number_float_t = NumberFloatType; + using number_float_t = NumberFloatType; -private: - template<typename T, typename... Args> - static T *create(Args &&... args) { - AllocatorType<T> alloc; - using AllocatorTraits = std::allocator_traits<AllocatorType<T>>; + private: + template<typename T, typename... Args> + static T *create(Args &&... args) { + AllocatorType<T> alloc; + using AllocatorTraits = std::allocator_traits<AllocatorType<T>>; - auto deleter = [&](T *object) { - AllocatorTraits::deallocate(alloc, object, 1); - }; - std::unique_ptr<T, decltype(deleter)> object(AllocatorTraits::allocate(alloc, 1), deleter); - AllocatorTraits::construct(alloc, object.get(), std::forward<Args>(args)...); - assert(object != nullptr); - return object.release(); - } + auto deleter = [&](T *object) { + AllocatorTraits::deallocate(alloc, object, 1); + }; + std::unique_ptr<T, decltype(deleter)> object(AllocatorTraits::allocate(alloc, 1), deleter); + AllocatorTraits::construct(alloc, object.get(), std::forward<Args>(args)...); + assert(object != nullptr); + return object.release(); + } - union json_value { - object_t *object; + union json_value { + object_t *object; - array_t *array; + array_t *array; - string_t *string; + string_t *string; - boolean_t boolean; + boolean_t boolean; - number_integer_t number_integer; + number_integer_t number_integer; - number_unsigned_t number_unsigned; + number_unsigned_t number_unsigned; - number_float_t number_float; + number_float_t number_float; - json_value() = default; + json_value() = default; - json_value(boolean_t v) noexcept : boolean(v) {} + json_value(boolean_t v) noexcept : boolean(v) {} - json_value(number_integer_t v) noexcept : number_integer(v) {} + json_value(number_integer_t v) noexcept : number_integer(v) {} - json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} + json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} - json_value(number_float_t v) noexcept : number_float(v) {} + json_value(number_float_t v) noexcept : number_float(v) {} - json_value(value_t t) { - switch(t) { - case value_t::object: { - object = create<object_t>(); - break; - } + json_value(value_t t) { + switch(t) { + case value_t::object: { + object = create<object_t>(); + break; + } - case value_t::array: { - array = create<array_t>(); - break; - } + case value_t::array: { + array = create<array_t>(); + break; + } - case value_t::string: { - string = create<string_t>(""); - break; - } + case value_t::string: { + string = create<string_t>(""); + break; + } - case value_t::boolean: { - boolean = boolean_t(false); - break; - } + case value_t::boolean: { + boolean = boolean_t(false); + break; + } - case value_t::number_integer: { - number_integer = number_integer_t(0); - break; - } + case value_t::number_integer: { + number_integer = number_integer_t(0); + break; + } - case value_t::number_unsigned: { - number_unsigned = number_unsigned_t(0); - break; - } + case value_t::number_unsigned: { + number_unsigned = number_unsigned_t(0); + break; + } - case value_t::number_float: { - number_float = number_float_t(0.0); - break; - } + case value_t::number_float: { + number_float = number_float_t(0.0); + break; + } - case value_t::null: { - object = nullptr; - break; - } + case value_t::null: { + object = nullptr; + break; + } - default: { - object = nullptr; - if(JSON_UNLIKELY(t == value_t::null)) { - JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.1.2")); + default: { + object = nullptr; + if(JSON_UNLIKELY(t == value_t::null)) { + JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.1.2")); + } + break; } - break; } } - } - json_value(const string_t &value) { - string = create<string_t>(value); - } + json_value(const string_t &value) { + string = create<string_t>(value); + } - json_value(string_t &&value) { - string = create<string_t>(std::move(value)); - } + json_value(string_t &&value) { + string = create<string_t>(std::move(value)); + } - json_value(const object_t &value) { - object = create<object_t>(value); - } + json_value(const object_t &value) { + object = create<object_t>(value); + } - json_value(object_t &&value) { - object = create<object_t>(std::move(value)); - } + json_value(object_t &&value) { + object = create<object_t>(std::move(value)); + } - json_value(const array_t &value) { - array = create<array_t>(value); - } + json_value(const array_t &value) { + array = create<array_t>(value); + } - json_value(array_t &&value) { - array = create<array_t>(std::move(value)); - } + json_value(array_t &&value) { + array = create<array_t>(std::move(value)); + } - void destroy(value_t t) noexcept { - switch(t) { - case value_t::object: { - AllocatorType<object_t> alloc; - std::allocator_traits<decltype(alloc)>::destroy(alloc, object); - std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1); - break; - } + void destroy(value_t t) noexcept { + switch(t) { + case value_t::object: { + AllocatorType<object_t> alloc; + std::allocator_traits<decltype(alloc)>::destroy(alloc, object); + std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1); + break; + } - case value_t::array: { - AllocatorType<array_t> alloc; - std::allocator_traits<decltype(alloc)>::destroy(alloc, array); - std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1); - break; - } + case value_t::array: { + AllocatorType<array_t> alloc; + std::allocator_traits<decltype(alloc)>::destroy(alloc, array); + std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1); + break; + } - case value_t::string: { - AllocatorType<string_t> alloc; - std::allocator_traits<decltype(alloc)>::destroy(alloc, string); - std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1); - break; - } + case value_t::string: { + AllocatorType<string_t> alloc; + std::allocator_traits<decltype(alloc)>::destroy(alloc, string); + std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1); + break; + } - default: { - break; + default: { + break; + } } } - } - }; - - void assert_invariant() const noexcept { - assert(m_type != value_t::object or m_value.object != nullptr); - assert(m_type != value_t::array or m_value.array != nullptr); - assert(m_type != value_t::string or m_value.string != nullptr); - } - -public: - using parse_event_t = typename parser::parse_event_t; - - using parser_callback_t = typename parser::parser_callback_t; - - basic_json(const value_t v) - : m_type(v), m_value(v) { - assert_invariant(); - } - - basic_json(std::nullptr_t = nullptr) noexcept - : basic_json(value_t::null) { - assert_invariant(); - } - - template<typename CompatibleType, - typename U = detail::uncvref_t<CompatibleType>, - detail::enable_if_t< - detail::is_compatible_type<basic_json_t, U>::value, int> = 0> - basic_json(CompatibleType &&val) noexcept(noexcept( - JSONSerializer<U>::to_json(std::declval<basic_json_t &>(), - std::forward<CompatibleType>(val)))) { - JSONSerializer<U>::to_json(*this, std::forward<CompatibleType>(val)); - assert_invariant(); - } + }; - template<typename BasicJsonType, - detail::enable_if_t< - detail::is_basic_json<BasicJsonType>::value and not std::is_same<basic_json, BasicJsonType>::value, int> = 0> - basic_json(const BasicJsonType &val) { - using other_boolean_t = typename BasicJsonType::boolean_t; - using other_number_float_t = typename BasicJsonType::number_float_t; - using other_number_integer_t = typename BasicJsonType::number_integer_t; - using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using other_string_t = typename BasicJsonType::string_t; - using other_object_t = typename BasicJsonType::object_t; - using other_array_t = typename BasicJsonType::array_t; - - switch(val.type()) { - case value_t::boolean: - JSONSerializer<other_boolean_t>::to_json(*this, val.template get<other_boolean_t>()); - break; - case value_t::number_float: - JSONSerializer<other_number_float_t>::to_json(*this, val.template get<other_number_float_t>()); - break; - case value_t::number_integer: - JSONSerializer<other_number_integer_t>::to_json(*this, val.template get<other_number_integer_t>()); - break; - case value_t::number_unsigned: - JSONSerializer<other_number_unsigned_t>::to_json(*this, val.template get<other_number_unsigned_t>()); - break; - case value_t::string: - JSONSerializer<other_string_t>::to_json(*this, val.template get_ref<const other_string_t &>()); - break; - case value_t::object: - JSONSerializer<other_object_t>::to_json(*this, val.template get_ref<const other_object_t &>()); - break; - case value_t::array: - JSONSerializer<other_array_t>::to_json(*this, val.template get_ref<const other_array_t &>()); - break; - case value_t::null: - *this = nullptr; - break; - case value_t::discarded: - m_type = value_t::discarded; - break; - } - assert_invariant(); - } + void assert_invariant() const noexcept { + assert(m_type != value_t::object or m_value.object != nullptr); + assert(m_type != value_t::array or m_value.array != nullptr); + assert(m_type != value_t::string or m_value.string != nullptr); + } - basic_json(initializer_list_t init, - bool type_deduction = true, - value_t manual_type = value_t::array) { - bool is_an_object = std::all_of(init.begin(), init.end(), - [](const detail::json_ref<basic_json> &element_ref) { - return (element_ref->is_array() and element_ref->size() == 2 and - (*element_ref)[0].is_string()); - }); + public: + using parse_event_t = typename parser::parse_event_t; - if(not type_deduction) { - if(manual_type == value_t::array) { - is_an_object = false; - } + using parser_callback_t = typename parser::parser_callback_t; - if(JSON_UNLIKELY(manual_type == value_t::object and not is_an_object)) { - JSON_THROW(type_error::create(301, "cannot create object from initializer list")); - } + basic_json(const value_t v) + : m_type(v), m_value(v) { + assert_invariant(); } - if(is_an_object) { - m_type = value_t::object; - m_value = value_t::object; - - std::for_each(init.begin(), init.end(), [this](const detail::json_ref<basic_json> &element_ref) { - auto element = element_ref.moved_or_copied(); - m_value.object->emplace( - std::move(*((*element.m_value.array)[0].m_value.string)), - std::move((*element.m_value.array)[1])); - }); - } else { - m_type = value_t::array; - m_value.array = create<array_t>(init.begin(), init.end()); + basic_json(std::nullptr_t = nullptr) noexcept + : basic_json(value_t::null) { + assert_invariant(); } - assert_invariant(); - } - - static basic_json array(initializer_list_t init = {}) { - return basic_json(init, false, value_t::array); - } - - static basic_json object(initializer_list_t init = {}) { - return basic_json(init, false, value_t::object); - } - - basic_json(size_type cnt, const basic_json &val) - : m_type(value_t::array) { - m_value.array = create<array_t>(cnt, val); - assert_invariant(); - } - - template<class InputIT, typename std::enable_if< - std::is_same<InputIT, typename basic_json_t::iterator>::value or - std::is_same<InputIT, typename basic_json_t::const_iterator>::value, int>::type = 0> - basic_json(InputIT first, InputIT last) { - assert(first.m_object != nullptr); - assert(last.m_object != nullptr); + template<typename CompatibleType, + typename U = detail::uncvref_t<CompatibleType>, + detail::enable_if_t< + detail::is_compatible_type<basic_json_t, U>::value, int> = 0> + basic_json(CompatibleType &&val) noexcept(noexcept( + JSONSerializer<U>::to_json(std::declval<basic_json_t &>(), + std::forward<CompatibleType>(val)))) { + JSONSerializer<U>::to_json(*this, std::forward<CompatibleType>(val)); + assert_invariant(); + } - if(JSON_UNLIKELY(first.m_object != last.m_object)) { - JSON_THROW(invalid_iterator::create(201, "iterators are not compatible")); + template<typename BasicJsonType, + detail::enable_if_t< + detail::is_basic_json<BasicJsonType>::value and + not std::is_same<basic_json, BasicJsonType>::value, int> = 0> + basic_json(const BasicJsonType &val) { + using other_boolean_t = typename BasicJsonType::boolean_t; + using other_number_float_t = typename BasicJsonType::number_float_t; + using other_number_integer_t = typename BasicJsonType::number_integer_t; + using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using other_string_t = typename BasicJsonType::string_t; + using other_object_t = typename BasicJsonType::object_t; + using other_array_t = typename BasicJsonType::array_t; + + switch(val.type()) { + case value_t::boolean: + JSONSerializer<other_boolean_t>::to_json(*this, val.template get<other_boolean_t>()); + break; + case value_t::number_float: + JSONSerializer<other_number_float_t>::to_json(*this, val.template get<other_number_float_t>()); + break; + case value_t::number_integer: + JSONSerializer<other_number_integer_t>::to_json(*this, val.template get<other_number_integer_t>()); + break; + case value_t::number_unsigned: + JSONSerializer<other_number_unsigned_t>::to_json(*this, val.template get<other_number_unsigned_t>()); + break; + case value_t::string: + JSONSerializer<other_string_t>::to_json(*this, val.template get_ref<const other_string_t &>()); + break; + case value_t::object: + JSONSerializer<other_object_t>::to_json(*this, val.template get_ref<const other_object_t &>()); + break; + case value_t::array: + JSONSerializer<other_array_t>::to_json(*this, val.template get_ref<const other_array_t &>()); + break; + case value_t::null: + *this = nullptr; + break; + case value_t::discarded: + m_type = value_t::discarded; + break; + } + assert_invariant(); } - m_type = first.m_object->m_type; + basic_json(initializer_list_t init, + bool type_deduction = true, + value_t manual_type = value_t::array) { + bool is_an_object = std::all_of(init.begin(), init.end(), + [](const detail::json_ref<basic_json> &element_ref) { + return (element_ref->is_array() and element_ref->size() == 2 and + (*element_ref)[0].is_string()); + }); - switch(m_type) { - case value_t::boolean: - case value_t::number_float: - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::string: { - if(JSON_UNLIKELY(not first.m_it.primitive_iterator.is_begin() - or not last.m_it.primitive_iterator.is_end())) { - JSON_THROW(invalid_iterator::create(204, "iterators out of range")); + if(not type_deduction) { + if(manual_type == value_t::array) { + is_an_object = false; + } + + if(JSON_UNLIKELY(manual_type == value_t::object and not is_an_object)) { + JSON_THROW(type_error::create(301, "cannot create object from initializer list")); } - break; } - default: - break; - } + if(is_an_object) { + m_type = value_t::object; + m_value = value_t::object; - switch(m_type) { - case value_t::number_integer: { - m_value.number_integer = first.m_object->m_value.number_integer; - break; + std::for_each(init.begin(), init.end(), [this](const detail::json_ref<basic_json> &element_ref) { + auto element = element_ref.moved_or_copied(); + m_value.object->emplace( + std::move(*((*element.m_value.array)[0].m_value.string)), + std::move((*element.m_value.array)[1])); + }); + } else { + m_type = value_t::array; + m_value.array = create<array_t>(init.begin(), init.end()); } - case value_t::number_unsigned: { - m_value.number_unsigned = first.m_object->m_value.number_unsigned; - break; - } + assert_invariant(); + } - case value_t::number_float: { - m_value.number_float = first.m_object->m_value.number_float; - break; - } + static basic_json array(initializer_list_t init = {}) { + return basic_json(init, false, value_t::array); + } - case value_t::boolean: { - m_value.boolean = first.m_object->m_value.boolean; - break; - } + static basic_json object(initializer_list_t init = {}) { + return basic_json(init, false, value_t::object); + } - case value_t::string: { - m_value = *first.m_object->m_value.string; - break; - } + basic_json(size_type cnt, const basic_json &val) + : m_type(value_t::array) { + m_value.array = create<array_t>(cnt, val); + assert_invariant(); + } - case value_t::object: { - m_value.object = create<object_t>(first.m_it.object_iterator, - last.m_it.object_iterator); - break; - } + template<class InputIT, typename std::enable_if< + std::is_same<InputIT, typename basic_json_t::iterator>::value or + std::is_same<InputIT, typename basic_json_t::const_iterator>::value, int>::type = 0> + basic_json(InputIT first, InputIT last) { + assert(first.m_object != nullptr); + assert(last.m_object != nullptr); - case value_t::array: { - m_value.array = create<array_t>(first.m_it.array_iterator, - last.m_it.array_iterator); - break; + if(JSON_UNLIKELY(first.m_object != last.m_object)) { + JSON_THROW(invalid_iterator::create(201, "iterators are not compatible")); } - default: - JSON_THROW(invalid_iterator::create(206, "cannot construct with iterators from "+ - std::string(first.m_object->type_name()))); - } + m_type = first.m_object->m_type; - assert_invariant(); - } + switch(m_type) { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: { + if(JSON_UNLIKELY(not first.m_it.primitive_iterator.is_begin() + or not last.m_it.primitive_iterator.is_end())) { + JSON_THROW(invalid_iterator::create(204, "iterators out of range")); + } + break; + } - basic_json(const detail::json_ref<basic_json> &ref) - : basic_json(ref.moved_or_copied()) {} + default: + break; + } - basic_json(const basic_json &other) - : m_type(other.m_type) { - other.assert_invariant(); + switch(m_type) { + case value_t::number_integer: { + m_value.number_integer = first.m_object->m_value.number_integer; + break; + } - switch(m_type) { - case value_t::object: { - m_value = *other.m_value.object; - break; - } + case value_t::number_unsigned: { + m_value.number_unsigned = first.m_object->m_value.number_unsigned; + break; + } - case value_t::array: { - m_value = *other.m_value.array; - break; - } + case value_t::number_float: { + m_value.number_float = first.m_object->m_value.number_float; + break; + } - case value_t::string: { - m_value = *other.m_value.string; - break; - } + case value_t::boolean: { + m_value.boolean = first.m_object->m_value.boolean; + break; + } - case value_t::boolean: { - m_value = other.m_value.boolean; - break; - } + case value_t::string: { + m_value = *first.m_object->m_value.string; + break; + } - case value_t::number_integer: { - m_value = other.m_value.number_integer; - break; - } + case value_t::object: { + m_value.object = create<object_t>(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } - case value_t::number_unsigned: { - m_value = other.m_value.number_unsigned; - break; - } + case value_t::array: { + m_value.array = create<array_t>(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } - case value_t::number_float: { - m_value = other.m_value.number_float; - break; + default: + JSON_THROW(invalid_iterator::create(206, "cannot construct with iterators from "+ + std::string(first.m_object->type_name()))); } - default: - break; + assert_invariant(); } - assert_invariant(); - } + basic_json(const detail::json_ref<basic_json> &ref) + : basic_json(ref.moved_or_copied()) {} - basic_json(basic_json &&other) noexcept - : m_type(std::move(other.m_type)), - m_value(std::move(other.m_value)) { - other.assert_invariant(); + basic_json(const basic_json &other) + : m_type(other.m_type) { + other.assert_invariant(); - other.m_type = value_t::null; - other.m_value = {}; + switch(m_type) { + case value_t::object: { + m_value = *other.m_value.object; + break; + } - assert_invariant(); - } + case value_t::array: { + m_value = *other.m_value.array; + break; + } - reference &operator=(basic_json other) noexcept( - std::is_nothrow_move_constructible<value_t>::value and - std::is_nothrow_move_assignable<value_t>::value and - std::is_nothrow_move_constructible<json_value>::value and - std::is_nothrow_move_assignable<json_value>::value - ) { - other.assert_invariant(); + case value_t::string: { + m_value = *other.m_value.string; + break; + } + + case value_t::boolean: { + m_value = other.m_value.boolean; + break; + } - using std::swap; - swap(m_type, other.m_type); - swap(m_value, other.m_value); + case value_t::number_integer: { + m_value = other.m_value.number_integer; + break; + } - assert_invariant(); - return *this; - } + case value_t::number_unsigned: { + m_value = other.m_value.number_unsigned; + break; + } - ~basic_json() noexcept { - assert_invariant(); - m_value.destroy(m_type); - } + case value_t::number_float: { + m_value = other.m_value.number_float; + break; + } -public: - string_t dump(const int indent = -1, const char indent_char = ' ', - const bool ensure_ascii = false) const { - string_t result; - serializer s(detail::output_adapter<char, string_t>(result), indent_char); + default: + break; + } - if(indent >= 0) { - s.dump(*this, true, ensure_ascii, static_cast<unsigned int>(indent)); - } else { - s.dump(*this, false, ensure_ascii, 0); + assert_invariant(); } - return result; - } + basic_json(basic_json &&other) noexcept + : m_type(std::move(other.m_type)), + m_value(std::move(other.m_value)) { + other.assert_invariant(); - constexpr value_t type() const noexcept { - return m_type; - } + other.m_type = value_t::null; + other.m_value = {}; - constexpr bool is_primitive() const noexcept { - return is_null() or is_string() or is_boolean() or is_number(); - } + assert_invariant(); + } - constexpr bool is_structured() const noexcept { - return is_array() or is_object(); - } + reference &operator=(basic_json other) noexcept( + std::is_nothrow_move_constructible<value_t>::value and + std::is_nothrow_move_assignable<value_t>::value and + std::is_nothrow_move_constructible<json_value>::value and + std::is_nothrow_move_assignable<json_value>::value + ) { + other.assert_invariant(); - constexpr bool is_null() const noexcept { - return (m_type == value_t::null); - } + using std::swap; + swap(m_type, other.m_type); + swap(m_value, other.m_value); - constexpr bool is_boolean() const noexcept { - return (m_type == value_t::boolean); - } + assert_invariant(); + return *this; + } - constexpr bool is_number() const noexcept { - return is_number_integer() or is_number_float(); - } + ~basic_json() noexcept { + assert_invariant(); + m_value.destroy(m_type); + } - constexpr bool is_number_integer() const noexcept { - return (m_type == value_t::number_integer or m_type == value_t::number_unsigned); - } + public: + string_t dump(const int indent = -1, const char indent_char = ' ', + const bool ensure_ascii = false) const { + string_t result; + serializer s(detail::output_adapter<char, string_t>(result), indent_char); - constexpr bool is_number_unsigned() const noexcept { - return (m_type == value_t::number_unsigned); - } + if(indent >= 0) { + s.dump(*this, true, ensure_ascii, static_cast<unsigned int>(indent)); + } else { + s.dump(*this, false, ensure_ascii, 0); + } - constexpr bool is_number_float() const noexcept { - return (m_type == value_t::number_float); - } + return result; + } - constexpr bool is_object() const noexcept { - return (m_type == value_t::object); - } + constexpr value_t type() const noexcept { + return m_type; + } - constexpr bool is_array() const noexcept { - return (m_type == value_t::array); - } + constexpr bool is_primitive() const noexcept { + return is_null() or is_string() or is_boolean() or is_number(); + } - constexpr bool is_string() const noexcept { - return (m_type == value_t::string); - } + constexpr bool is_structured() const noexcept { + return is_array() or is_object(); + } - constexpr bool is_discarded() const noexcept { - return (m_type == value_t::discarded); - } + constexpr bool is_null() const noexcept { + return (m_type == value_t::null); + } - constexpr operator value_t() const noexcept { - return m_type; - } + constexpr bool is_boolean() const noexcept { + return (m_type == value_t::boolean); + } -private: - boolean_t get_impl(boolean_t *) const { - if(JSON_LIKELY(is_boolean())) { - return m_value.boolean; + constexpr bool is_number() const noexcept { + return is_number_integer() or is_number_float(); } - JSON_THROW(type_error::create(302, "type must be boolean, but is "+std::string(type_name()))); - } + constexpr bool is_number_integer() const noexcept { + return (m_type == value_t::number_integer or m_type == value_t::number_unsigned); + } - object_t *get_impl_ptr(object_t *) noexcept { - return is_object() ? m_value.object : nullptr; - } + constexpr bool is_number_unsigned() const noexcept { + return (m_type == value_t::number_unsigned); + } - constexpr const object_t *get_impl_ptr(const object_t *) const noexcept { - return is_object() ? m_value.object : nullptr; - } + constexpr bool is_number_float() const noexcept { + return (m_type == value_t::number_float); + } - array_t *get_impl_ptr(array_t *) noexcept { - return is_array() ? m_value.array : nullptr; - } + constexpr bool is_object() const noexcept { + return (m_type == value_t::object); + } - constexpr const array_t *get_impl_ptr(const array_t *) const noexcept { - return is_array() ? m_value.array : nullptr; - } + constexpr bool is_array() const noexcept { + return (m_type == value_t::array); + } - string_t *get_impl_ptr(string_t *) noexcept { - return is_string() ? m_value.string : nullptr; - } + constexpr bool is_string() const noexcept { + return (m_type == value_t::string); + } - constexpr const string_t *get_impl_ptr(const string_t *) const noexcept { - return is_string() ? m_value.string : nullptr; - } + constexpr bool is_discarded() const noexcept { + return (m_type == value_t::discarded); + } - boolean_t *get_impl_ptr(boolean_t *) noexcept { - return is_boolean() ? &m_value.boolean : nullptr; - } + constexpr operator value_t() const noexcept { + return m_type; + } - constexpr const boolean_t *get_impl_ptr(const boolean_t *) const noexcept { - return is_boolean() ? &m_value.boolean : nullptr; - } + private: + boolean_t get_impl(boolean_t *) const { + if(JSON_LIKELY(is_boolean())) { + return m_value.boolean; + } - number_integer_t *get_impl_ptr(number_integer_t *) noexcept { - return is_number_integer() ? &m_value.number_integer : nullptr; - } + JSON_THROW(type_error::create(302, "type must be boolean, but is "+std::string(type_name()))); + } - constexpr const number_integer_t *get_impl_ptr(const number_integer_t *) const noexcept { - return is_number_integer() ? &m_value.number_integer : nullptr; - } + object_t *get_impl_ptr(object_t *) noexcept { + return is_object() ? m_value.object : nullptr; + } - number_unsigned_t *get_impl_ptr(number_unsigned_t *) noexcept { - return is_number_unsigned() ? &m_value.number_unsigned : nullptr; - } + constexpr const object_t *get_impl_ptr(const object_t *) const noexcept { + return is_object() ? m_value.object : nullptr; + } - constexpr const number_unsigned_t *get_impl_ptr(const number_unsigned_t *) const noexcept { - return is_number_unsigned() ? &m_value.number_unsigned : nullptr; - } + array_t *get_impl_ptr(array_t *) noexcept { + return is_array() ? m_value.array : nullptr; + } - number_float_t *get_impl_ptr(number_float_t *) noexcept { - return is_number_float() ? &m_value.number_float : nullptr; - } + constexpr const array_t *get_impl_ptr(const array_t *) const noexcept { + return is_array() ? m_value.array : nullptr; + } - constexpr const number_float_t *get_impl_ptr(const number_float_t *) const noexcept { - return is_number_float() ? &m_value.number_float : nullptr; - } + string_t *get_impl_ptr(string_t *) noexcept { + return is_string() ? m_value.string : nullptr; + } - template<typename ReferenceType, typename ThisType> - static ReferenceType get_ref_impl(ThisType &obj) { - auto ptr = obj.template get_ptr<typename std::add_pointer<ReferenceType>::type>(); + constexpr const string_t *get_impl_ptr(const string_t *) const noexcept { + return is_string() ? m_value.string : nullptr; + } - if(JSON_LIKELY(ptr != nullptr)) { - return *ptr; + boolean_t *get_impl_ptr(boolean_t *) noexcept { + return is_boolean() ? &m_value.boolean : nullptr; } - JSON_THROW(type_error::create(303, "incompatible ReferenceType for get_ref, actual type is "+ - std::string(obj.type_name()))); - } + constexpr const boolean_t *get_impl_ptr(const boolean_t *) const noexcept { + return is_boolean() ? &m_value.boolean : nullptr; + } -public: - template<typename BasicJsonType, detail::enable_if_t< - std::is_same<typename std::remove_const<BasicJsonType>::type, basic_json_t>::value, - int> = 0> - basic_json get() const { - return *this; - } + number_integer_t *get_impl_ptr(number_integer_t *) noexcept { + return is_number_integer() ? &m_value.number_integer : nullptr; + } - template<typename BasicJsonType, detail::enable_if_t< - not std::is_same<BasicJsonType, basic_json>::value and - detail::is_basic_json<BasicJsonType>::value, int> = 0> - BasicJsonType get() const { - return *this; - } + constexpr const number_integer_t *get_impl_ptr(const number_integer_t *) const noexcept { + return is_number_integer() ? &m_value.number_integer : nullptr; + } - template<typename ValueTypeCV, typename ValueType = detail::uncvref_t<ValueTypeCV>, - detail::enable_if_t< - not detail::is_basic_json<ValueType>::value and - detail::has_from_json<basic_json_t, ValueType>::value and - not detail::has_non_default_from_json<basic_json_t, ValueType>::value, - int> = 0> - ValueType get() const noexcept(noexcept( - JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t &>(), std::declval<ValueType &>()))) { - static_assert(not std::is_reference<ValueTypeCV>::value, - "get() cannot be used with reference types, you might want to use get_ref()"); - static_assert(std::is_default_constructible<ValueType>::value, - "types must be DefaultConstructible when used with get()"); - - ValueType ret; - JSONSerializer<ValueType>::from_json(*this, ret); - return ret; - } + number_unsigned_t *get_impl_ptr(number_unsigned_t *) noexcept { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } - template<typename ValueTypeCV, typename ValueType = detail::uncvref_t<ValueTypeCV>, - detail::enable_if_t<not std::is_same<basic_json_t, ValueType>::value and - detail::has_non_default_from_json<basic_json_t, ValueType>::value, - int> = 0> - ValueType get() const noexcept(noexcept( - JSONSerializer<ValueTypeCV>::from_json(std::declval<const basic_json_t &>()))) { - static_assert(not std::is_reference<ValueTypeCV>::value, - "get() cannot be used with reference types, you might want to use get_ref()"); - return JSONSerializer<ValueTypeCV>::from_json(*this); - } + constexpr const number_unsigned_t *get_impl_ptr(const number_unsigned_t *) const noexcept { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } - template<typename PointerType, typename std::enable_if< - std::is_pointer<PointerType>::value, int>::type = 0> - PointerType get() noexcept { - return get_ptr<PointerType>(); - } + number_float_t *get_impl_ptr(number_float_t *) noexcept { + return is_number_float() ? &m_value.number_float : nullptr; + } - template<typename PointerType, typename std::enable_if< - std::is_pointer<PointerType>::value, int>::type = 0> - constexpr const PointerType get() const noexcept { - return get_ptr<PointerType>(); - } + constexpr const number_float_t *get_impl_ptr(const number_float_t *) const noexcept { + return is_number_float() ? &m_value.number_float : nullptr; + } - template<typename PointerType, typename std::enable_if< - std::is_pointer<PointerType>::value, int>::type = 0> - PointerType get_ptr() noexcept { - using pointee_t = typename std::remove_const<typename - std::remove_pointer<typename - std::remove_const<PointerType>::type>::type>::type; - - static_assert( - std::is_same<object_t, pointee_t>::value - or std::is_same<array_t, pointee_t>::value - or std::is_same<string_t, pointee_t>::value - or std::is_same<boolean_t, pointee_t>::value - or std::is_same<number_integer_t, pointee_t>::value - or std::is_same<number_unsigned_t, pointee_t>::value - or std::is_same<number_float_t, pointee_t>::value, "incompatible pointer type"); - - return get_impl_ptr(static_cast<PointerType>(nullptr)); - } + template<typename ReferenceType, typename ThisType> + static ReferenceType get_ref_impl(ThisType &obj) { + auto ptr = obj.template get_ptr<typename std::add_pointer<ReferenceType>::type>(); - template<typename PointerType, typename std::enable_if< - std::is_pointer<PointerType>::value and - std::is_const<typename std::remove_pointer<PointerType>::type>::value, int>::type = 0> - constexpr const PointerType get_ptr() const noexcept { - using pointee_t = typename std::remove_const<typename - std::remove_pointer<typename - std::remove_const<PointerType>::type>::type>::type; - - static_assert( - std::is_same<object_t, pointee_t>::value - or std::is_same<array_t, pointee_t>::value - or std::is_same<string_t, pointee_t>::value - or std::is_same<boolean_t, pointee_t>::value - or std::is_same<number_integer_t, pointee_t>::value - or std::is_same<number_unsigned_t, pointee_t>::value - or std::is_same<number_float_t, pointee_t>::value, "incompatible pointer type"); - - return get_impl_ptr(static_cast<PointerType>(nullptr)); - } + if(JSON_LIKELY(ptr != nullptr)) { + return *ptr; + } - template<typename ReferenceType, typename std::enable_if< - std::is_reference<ReferenceType>::value, int>::type = 0> - ReferenceType get_ref() { - return get_ref_impl<ReferenceType>(*this); - } + JSON_THROW(type_error::create(303, "incompatible ReferenceType for get_ref, actual type is "+ + std::string(obj.type_name()))); + } - template<typename ReferenceType, typename std::enable_if< - std::is_reference<ReferenceType>::value and - std::is_const<typename std::remove_reference<ReferenceType>::type>::value, int>::type = 0> - ReferenceType get_ref() const { - return get_ref_impl<ReferenceType>(*this); - } + public: + template<typename BasicJsonType, detail::enable_if_t< + std::is_same<typename std::remove_const<BasicJsonType>::type, basic_json_t>::value, + int> = 0> + basic_json get() const { + return *this; + } + + template<typename BasicJsonType, detail::enable_if_t< + not std::is_same<BasicJsonType, basic_json>::value and + detail::is_basic_json<BasicJsonType>::value, int> = 0> + BasicJsonType get() const { + return *this; + } - template<typename ValueType, typename std::enable_if< - not std::is_pointer<ValueType>::value and - not std::is_same<ValueType, detail::json_ref<basic_json>>::value and - not std::is_same<ValueType, typename string_t::value_type>::value and - not detail::is_basic_json<ValueType>::value - #ifndef _MSC_VER - and not std::is_same<ValueType, std::initializer_list<typename string_t::value_type>>::value + template<typename ValueTypeCV, typename ValueType = detail::uncvref_t<ValueTypeCV>, + detail::enable_if_t< + not detail::is_basic_json<ValueType>::value and + detail::has_from_json<basic_json_t, ValueType>::value and + not detail::has_non_default_from_json<basic_json_t, ValueType>::value, + int> = 0> + ValueType get() const noexcept(noexcept( + JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t &>(), std::declval<ValueType &>()))) { + static_assert(not std::is_reference<ValueTypeCV>::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + static_assert(std::is_default_constructible<ValueType>::value, + "types must be DefaultConstructible when used with get()"); + + ValueType ret; + JSONSerializer<ValueType>::from_json(*this, ret); + return ret; + } + + template<typename ValueTypeCV, typename ValueType = detail::uncvref_t<ValueTypeCV>, + detail::enable_if_t<not std::is_same<basic_json_t, ValueType>::value and + detail::has_non_default_from_json<basic_json_t, ValueType>::value, + int> = 0> + ValueType get() const noexcept(noexcept( + JSONSerializer<ValueTypeCV>::from_json(std::declval<const basic_json_t &>()))) { + static_assert(not std::is_reference<ValueTypeCV>::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + return JSONSerializer<ValueTypeCV>::from_json(*this); + } + + template<typename PointerType, typename std::enable_if< + std::is_pointer<PointerType>::value, int>::type = 0> + PointerType get() noexcept { + return get_ptr<PointerType>(); + } + + template<typename PointerType, typename std::enable_if< + std::is_pointer<PointerType>::value, int>::type = 0> + constexpr const PointerType get() const noexcept { + return get_ptr<PointerType>(); + } + + template<typename PointerType, typename std::enable_if< + std::is_pointer<PointerType>::value, int>::type = 0> + PointerType get_ptr() noexcept { + using pointee_t = typename std::remove_const<typename + std::remove_pointer<typename + std::remove_const<PointerType>::type>::type>::type; + + static_assert( + std::is_same<object_t, pointee_t>::value + or std::is_same<array_t, pointee_t>::value + or std::is_same<string_t, pointee_t>::value + or std::is_same<boolean_t, pointee_t>::value + or std::is_same<number_integer_t, pointee_t>::value + or std::is_same<number_unsigned_t, pointee_t>::value + or std::is_same<number_float_t, pointee_t>::value, "incompatible pointer type"); + + return get_impl_ptr(static_cast<PointerType>(nullptr)); + } + + template<typename PointerType, typename std::enable_if< + std::is_pointer<PointerType>::value and + std::is_const<typename std::remove_pointer<PointerType>::type>::value, int>::type = 0> + constexpr const PointerType get_ptr() const noexcept { + using pointee_t = typename std::remove_const<typename + std::remove_pointer<typename + std::remove_const<PointerType>::type>::type>::type; + + static_assert( + std::is_same<object_t, pointee_t>::value + or std::is_same<array_t, pointee_t>::value + or std::is_same<string_t, pointee_t>::value + or std::is_same<boolean_t, pointee_t>::value + or std::is_same<number_integer_t, pointee_t>::value + or std::is_same<number_unsigned_t, pointee_t>::value + or std::is_same<number_float_t, pointee_t>::value, "incompatible pointer type"); + + return get_impl_ptr(static_cast<PointerType>(nullptr)); + } + + template<typename ReferenceType, typename std::enable_if< + std::is_reference<ReferenceType>::value, int>::type = 0> + ReferenceType get_ref() { + return get_ref_impl<ReferenceType>(*this); + } + + template<typename ReferenceType, typename std::enable_if< + std::is_reference<ReferenceType>::value and + std::is_const<typename std::remove_reference<ReferenceType>::type>::value, int>::type = 0> + ReferenceType get_ref() const { + return get_ref_impl<ReferenceType>(*this); + } + + template<typename ValueType, typename std::enable_if< + not std::is_pointer<ValueType>::value and + not std::is_same<ValueType, detail::json_ref<basic_json>>::value and + not std::is_same<ValueType, typename string_t::value_type>::value and + not detail::is_basic_json<ValueType>::value + #ifndef _MSC_VER + and not std::is_same<ValueType, std::initializer_list<typename string_t::value_type>>::value #endif #if defined(JSON_HAS_CPP_17) - and not std::is_same<ValueType, typename std::string_view>::value + and not std::is_same<ValueType, typename std::string_view>::value #endif - , int>::type = 0> - operator ValueType() const { - return get<ValueType>(); - } + , int>::type = 0> + operator ValueType() const { + return get<ValueType>(); + } - reference at(size_type idx) { - if(JSON_LIKELY(is_array())) { - JSON_TRY { - return m_value.array->at(idx); - } - JSON_CATCH (std::out_of_range &) { - JSON_THROW(out_of_range::create(401, "array index "+std::to_string(idx)+" is out of range")); + reference at(size_type idx) { + if(JSON_LIKELY(is_array())) { + JSON_TRY { + return m_value.array->at(idx); + } + JSON_CATCH (std::out_of_range &) { + JSON_THROW(out_of_range::create(401, "array index "+std::to_string(idx)+" is out of range")); + } + } else { + JSON_THROW(type_error::create(304, "cannot use at() with "+std::string(type_name()))); } - } else { - JSON_THROW(type_error::create(304, "cannot use at() with "+std::string(type_name()))); } - } - const_reference at(size_type idx) const { - if(JSON_LIKELY(is_array())) { - JSON_TRY { - return m_value.array->at(idx); - } - JSON_CATCH (std::out_of_range &) { - JSON_THROW(out_of_range::create(401, "array index "+std::to_string(idx)+" is out of range")); + const_reference at(size_type idx) const { + if(JSON_LIKELY(is_array())) { + JSON_TRY { + return m_value.array->at(idx); + } + JSON_CATCH (std::out_of_range &) { + JSON_THROW(out_of_range::create(401, "array index "+std::to_string(idx)+" is out of range")); + } + } else { + JSON_THROW(type_error::create(304, "cannot use at() with "+std::string(type_name()))); } - } else { - JSON_THROW(type_error::create(304, "cannot use at() with "+std::string(type_name()))); } - } - reference at(const typename object_t::key_type &key) { - if(JSON_LIKELY(is_object())) { - JSON_TRY { - return m_value.object->at(key); + reference at(const typename object_t::key_type &key) { + if(JSON_LIKELY(is_object())) { + JSON_TRY { + return m_value.object->at(key); + } + JSON_CATCH (std::out_of_range &) { + JSON_THROW(out_of_range::create(403, "key '"+key+"' not found")); + } + } else { + JSON_THROW(type_error::create(304, "cannot use at() with "+std::string(type_name()))); } - JSON_CATCH (std::out_of_range &) { - JSON_THROW(out_of_range::create(403, "key '"+key+"' not found")); + } + + const_reference at(const typename object_t::key_type &key) const { + if(JSON_LIKELY(is_object())) { + JSON_TRY { + return m_value.object->at(key); + } + JSON_CATCH (std::out_of_range &) { + JSON_THROW(out_of_range::create(403, "key '"+key+"' not found")); + } + } else { + JSON_THROW(type_error::create(304, "cannot use at() with "+std::string(type_name()))); } - } else { - JSON_THROW(type_error::create(304, "cannot use at() with "+std::string(type_name()))); } - } - const_reference at(const typename object_t::key_type &key) const { - if(JSON_LIKELY(is_object())) { - JSON_TRY { - return m_value.object->at(key); + reference operator[](size_type idx) { + if(is_null()) { + m_type = value_t::array; + m_value.array = create<array_t>(); + assert_invariant(); } - JSON_CATCH (std::out_of_range &) { - JSON_THROW(out_of_range::create(403, "key '"+key+"' not found")); + + if(JSON_LIKELY(is_array())) { + if(idx >= m_value.array->size()) { + m_value.array->insert(m_value.array->end(), + idx-m_value.array->size()+1, + basic_json()); + } + + return m_value.array->operator[](idx); } - } else { - JSON_THROW(type_error::create(304, "cannot use at() with "+std::string(type_name()))); - } - } - reference operator[](size_type idx) { - if(is_null()) { - m_type = value_t::array; - m_value.array = create<array_t>(); - assert_invariant(); + JSON_THROW(type_error::create(305, "cannot use operator[] with "+std::string(type_name()))); } - if(JSON_LIKELY(is_array())) { - if(idx >= m_value.array->size()) { - m_value.array->insert(m_value.array->end(), - idx-m_value.array->size()+1, - basic_json()); + const_reference operator[](size_type idx) const { + if(JSON_LIKELY(is_array())) { + return m_value.array->operator[](idx); } - return m_value.array->operator[](idx); + JSON_THROW(type_error::create(305, "cannot use operator[] with "+std::string(type_name()))); } - JSON_THROW(type_error::create(305, "cannot use operator[] with "+std::string(type_name()))); - } + reference operator[](const typename object_t::key_type &key) { + if(is_null()) { + m_type = value_t::object; + m_value.object = create<object_t>(); + assert_invariant(); + } + + if(JSON_LIKELY(is_object())) { + return m_value.object->operator[](key); + } - const_reference operator[](size_type idx) const { - if(JSON_LIKELY(is_array())) { - return m_value.array->operator[](idx); + JSON_THROW(type_error::create(305, "cannot use operator[] with "+std::string(type_name()))); } - JSON_THROW(type_error::create(305, "cannot use operator[] with "+std::string(type_name()))); - } + const_reference operator[](const typename object_t::key_type &key) const { + if(JSON_LIKELY(is_object())) { + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } - reference operator[](const typename object_t::key_type &key) { - if(is_null()) { - m_type = value_t::object; - m_value.object = create<object_t>(); - assert_invariant(); + JSON_THROW(type_error::create(305, "cannot use operator[] with "+std::string(type_name()))); } - if(JSON_LIKELY(is_object())) { - return m_value.object->operator[](key); - } + template<typename T> + reference operator[](T *key) { + if(is_null()) { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } - JSON_THROW(type_error::create(305, "cannot use operator[] with "+std::string(type_name()))); - } + if(JSON_LIKELY(is_object())) { + return m_value.object->operator[](key); + } - const_reference operator[](const typename object_t::key_type &key) const { - if(JSON_LIKELY(is_object())) { - assert(m_value.object->find(key) != m_value.object->end()); - return m_value.object->find(key)->second; + JSON_THROW(type_error::create(305, "cannot use operator[] with "+std::string(type_name()))); } - JSON_THROW(type_error::create(305, "cannot use operator[] with "+std::string(type_name()))); - } + template<typename T> + const_reference operator[](T *key) const { + if(JSON_LIKELY(is_object())) { + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } - template<typename T> - reference operator[](T *key) { - if(is_null()) { - m_type = value_t::object; - m_value = value_t::object; - assert_invariant(); + JSON_THROW(type_error::create(305, "cannot use operator[] with "+std::string(type_name()))); } - if(JSON_LIKELY(is_object())) { - return m_value.object->operator[](key); - } + template<class ValueType, typename std::enable_if< + std::is_convertible<basic_json_t, ValueType>::value, int>::type = 0> + ValueType value(const typename object_t::key_type &key, const ValueType &default_value) const { + if(JSON_LIKELY(is_object())) { + const auto it = find(key); + if(it != end()) { + return *it; + } - JSON_THROW(type_error::create(305, "cannot use operator[] with "+std::string(type_name()))); - } + return default_value; + } - template<typename T> - const_reference operator[](T *key) const { - if(JSON_LIKELY(is_object())) { - assert(m_value.object->find(key) != m_value.object->end()); - return m_value.object->find(key)->second; + JSON_THROW(type_error::create(306, "cannot use value() with "+std::string(type_name()))); } - JSON_THROW(type_error::create(305, "cannot use operator[] with "+std::string(type_name()))); - } + string_t value(const typename object_t::key_type &key, const char *default_value) const { + return value(key, string_t(default_value)); + } - template<class ValueType, typename std::enable_if< - std::is_convertible<basic_json_t, ValueType>::value, int>::type = 0> - ValueType value(const typename object_t::key_type &key, const ValueType &default_value) const { - if(JSON_LIKELY(is_object())) { - const auto it = find(key); - if(it != end()) { - return *it; + template<class ValueType, typename std::enable_if< + std::is_convertible<basic_json_t, ValueType>::value, int>::type = 0> + ValueType value(const json_pointer &ptr, const ValueType &default_value) const { + if(JSON_LIKELY(is_object())) { + JSON_TRY { + return ptr.get_checked(this); + } + JSON_CATCH (out_of_range &) { + return default_value; + } } - return default_value; + JSON_THROW(type_error::create(306, "cannot use value() with "+std::string(type_name()))); } - JSON_THROW(type_error::create(306, "cannot use value() with "+std::string(type_name()))); - } - - string_t value(const typename object_t::key_type &key, const char *default_value) const { - return value(key, string_t(default_value)); - } + string_t value(const json_pointer &ptr, const char *default_value) const { + return value(ptr, string_t(default_value)); + } - template<class ValueType, typename std::enable_if< - std::is_convertible<basic_json_t, ValueType>::value, int>::type = 0> - ValueType value(const json_pointer &ptr, const ValueType &default_value) const { - if(JSON_LIKELY(is_object())) { - JSON_TRY { - return ptr.get_checked(this); - } - JSON_CATCH (out_of_range &) { - return default_value; - } + reference front() { + return *begin(); } - JSON_THROW(type_error::create(306, "cannot use value() with "+std::string(type_name()))); - } + const_reference front() const { + return *cbegin(); + } - string_t value(const json_pointer &ptr, const char *default_value) const { - return value(ptr, string_t(default_value)); - } + reference back() { + auto tmp = end(); + --tmp; + return *tmp; + } - reference front() { - return *begin(); - } + const_reference back() const { + auto tmp = cend(); + --tmp; + return *tmp; + } - const_reference front() const { - return *cbegin(); - } + template<class IteratorType, typename std::enable_if< + std::is_same<IteratorType, typename basic_json_t::iterator>::value or + std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int>::type + = 0> + IteratorType erase(IteratorType pos) { + if(JSON_UNLIKELY(this != pos.m_object)) { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } - reference back() { - auto tmp = end(); - --tmp; - return *tmp; - } + IteratorType result = end(); - const_reference back() const { - auto tmp = cend(); - --tmp; - return *tmp; - } + switch(m_type) { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: { + if(JSON_UNLIKELY(not pos.m_it.primitive_iterator.is_begin())) { + JSON_THROW(invalid_iterator::create(205, "iterator out of range")); + } - template<class IteratorType, typename std::enable_if< - std::is_same<IteratorType, typename basic_json_t::iterator>::value or - std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int>::type - = 0> - IteratorType erase(IteratorType pos) { - if(JSON_UNLIKELY(this != pos.m_object)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); - } + if(is_string()) { + AllocatorType<string_t> alloc; + std::allocator_traits<decltype(alloc)>::destroy(alloc, m_value.string); + std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.string, 1); + m_value.string = nullptr; + } - IteratorType result = end(); + m_type = value_t::null; + assert_invariant(); + break; + } - switch(m_type) { - case value_t::boolean: - case value_t::number_float: - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::string: { - if(JSON_UNLIKELY(not pos.m_it.primitive_iterator.is_begin())) { - JSON_THROW(invalid_iterator::create(205, "iterator out of range")); + case value_t::object: { + result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator); + break; } - if(is_string()) { - AllocatorType<string_t> alloc; - std::allocator_traits<decltype(alloc)>::destroy(alloc, m_value.string); - std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.string, 1); - m_value.string = nullptr; + case value_t::array: { + result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); + break; } - m_type = value_t::null; - assert_invariant(); - break; + default: + JSON_THROW(type_error::create(307, "cannot use erase() with "+std::string(type_name()))); } - case value_t::object: { - result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator); - break; - } + return result; + } - case value_t::array: { - result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); - break; + template<class IteratorType, typename std::enable_if< + std::is_same<IteratorType, typename basic_json_t::iterator>::value or + std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int>::type + = 0> + IteratorType erase(IteratorType first, IteratorType last) { + if(JSON_UNLIKELY(this != first.m_object or this != last.m_object)) { + JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value")); } - default: - JSON_THROW(type_error::create(307, "cannot use erase() with "+std::string(type_name()))); - } + IteratorType result = end(); - return result; - } + switch(m_type) { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: { + if(JSON_LIKELY(not first.m_it.primitive_iterator.is_begin() + or not last.m_it.primitive_iterator.is_end())) { + JSON_THROW(invalid_iterator::create(204, "iterators out of range")); + } - template<class IteratorType, typename std::enable_if< - std::is_same<IteratorType, typename basic_json_t::iterator>::value or - std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int>::type - = 0> - IteratorType erase(IteratorType first, IteratorType last) { - if(JSON_UNLIKELY(this != first.m_object or this != last.m_object)) { - JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value")); - } + if(is_string()) { + AllocatorType<string_t> alloc; + std::allocator_traits<decltype(alloc)>::destroy(alloc, m_value.string); + std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.string, 1); + m_value.string = nullptr; + } - IteratorType result = end(); + m_type = value_t::null; + assert_invariant(); + break; + } - switch(m_type) { - case value_t::boolean: - case value_t::number_float: - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::string: { - if(JSON_LIKELY(not first.m_it.primitive_iterator.is_begin() - or not last.m_it.primitive_iterator.is_end())) { - JSON_THROW(invalid_iterator::create(204, "iterators out of range")); + case value_t::object: { + result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator, + last.m_it.object_iterator); + break; } - if(is_string()) { - AllocatorType<string_t> alloc; - std::allocator_traits<decltype(alloc)>::destroy(alloc, m_value.string); - std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.string, 1); - m_value.string = nullptr; + case value_t::array: { + result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, + last.m_it.array_iterator); + break; } - m_type = value_t::null; - assert_invariant(); - break; + default: + JSON_THROW(type_error::create(307, "cannot use erase() with "+std::string(type_name()))); } - case value_t::object: { - result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator, - last.m_it.object_iterator); - break; - } + return result; + } - case value_t::array: { - result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, - last.m_it.array_iterator); - break; + size_type erase(const typename object_t::key_type &key) { + if(JSON_LIKELY(is_object())) { + return m_value.object->erase(key); } - default: + JSON_THROW(type_error::create(307, "cannot use erase() with "+std::string(type_name()))); + } + + void erase(const size_type idx) { + if(JSON_LIKELY(is_array())) { + if(JSON_UNLIKELY(idx >= size())) { + JSON_THROW(out_of_range::create(401, "array index "+std::to_string(idx)+" is out of range")); + } + + m_value.array->erase(m_value.array->begin()+static_cast<difference_type>(idx)); + } else { JSON_THROW(type_error::create(307, "cannot use erase() with "+std::string(type_name()))); + } } - return result; - } + template<typename KeyT> + iterator find(KeyT &&key) { + auto result = end(); + + if(is_object()) { + result.m_it.object_iterator = m_value.object->find(std::forward<KeyT>(key)); + } - size_type erase(const typename object_t::key_type &key) { - if(JSON_LIKELY(is_object())) { - return m_value.object->erase(key); + return result; } - JSON_THROW(type_error::create(307, "cannot use erase() with "+std::string(type_name()))); - } + template<typename KeyT> + const_iterator find(KeyT &&key) const { + auto result = cend(); - void erase(const size_type idx) { - if(JSON_LIKELY(is_array())) { - if(JSON_UNLIKELY(idx >= size())) { - JSON_THROW(out_of_range::create(401, "array index "+std::to_string(idx)+" is out of range")); + if(is_object()) { + result.m_it.object_iterator = m_value.object->find(std::forward<KeyT>(key)); } - m_value.array->erase(m_value.array->begin()+static_cast<difference_type>(idx)); - } else { - JSON_THROW(type_error::create(307, "cannot use erase() with "+std::string(type_name()))); + return result; } - } - template<typename KeyT> - iterator find(KeyT &&key) { - auto result = end(); + template<typename KeyT> + size_type count(KeyT &&key) const { + return is_object() ? m_value.object->count(std::forward<KeyT>(key)) : 0; + } - if(is_object()) { - result.m_it.object_iterator = m_value.object->find(std::forward<KeyT>(key)); + iterator begin() noexcept { + iterator result(this); + result.set_begin(); + return result; } - return result; - } + const_iterator begin() const noexcept { + return cbegin(); + } + + const_iterator cbegin() const noexcept { + const_iterator result(this); + result.set_begin(); + return result; + } - template<typename KeyT> - const_iterator find(KeyT &&key) const { - auto result = cend(); + iterator end() noexcept { + iterator result(this); + result.set_end(); + return result; + } - if(is_object()) { - result.m_it.object_iterator = m_value.object->find(std::forward<KeyT>(key)); + const_iterator end() const noexcept { + return cend(); } - return result; - } + const_iterator cend() const noexcept { + const_iterator result(this); + result.set_end(); + return result; + } - template<typename KeyT> - size_type count(KeyT &&key) const { - return is_object() ? m_value.object->count(std::forward<KeyT>(key)) : 0; - } + reverse_iterator rbegin() noexcept { + return reverse_iterator(end()); + } - iterator begin() noexcept { - iterator result(this); - result.set_begin(); - return result; - } + const_reverse_iterator rbegin() const noexcept { + return crbegin(); + } - const_iterator begin() const noexcept { - return cbegin(); - } + reverse_iterator rend() noexcept { + return reverse_iterator(begin()); + } - const_iterator cbegin() const noexcept { - const_iterator result(this); - result.set_begin(); - return result; - } + const_reverse_iterator rend() const noexcept { + return crend(); + } - iterator end() noexcept { - iterator result(this); - result.set_end(); - return result; - } + const_reverse_iterator crbegin() const noexcept { + return const_reverse_iterator(cend()); + } - const_iterator end() const noexcept { - return cend(); - } + const_reverse_iterator crend() const noexcept { + return const_reverse_iterator(cbegin()); + } - const_iterator cend() const noexcept { - const_iterator result(this); - result.set_end(); - return result; - } + public: + iteration_proxy<iterator> items() noexcept { + return iteration_proxy<iterator>(*this); + } - reverse_iterator rbegin() noexcept { - return reverse_iterator(end()); - } + iteration_proxy<const_iterator> items() const noexcept { + return iteration_proxy<const_iterator>(*this); + } - const_reverse_iterator rbegin() const noexcept { - return crbegin(); - } + bool empty() const noexcept { + switch(m_type) { + case value_t::null: { + return true; + } - reverse_iterator rend() noexcept { - return reverse_iterator(begin()); - } + case value_t::array: { + return m_value.array->empty(); + } - const_reverse_iterator rend() const noexcept { - return crend(); - } + case value_t::object: { + return m_value.object->empty(); + } - const_reverse_iterator crbegin() const noexcept { - return const_reverse_iterator(cend()); - } + default: { + return false; + } + } + } - const_reverse_iterator crend() const noexcept { - return const_reverse_iterator(cbegin()); - } + size_type size() const noexcept { + switch(m_type) { + case value_t::null: { + return 0; + } -public: - iteration_proxy<iterator> items() noexcept { - return iteration_proxy<iterator>(*this); - } + case value_t::array: { + return m_value.array->size(); + } - iteration_proxy<const_iterator> items() const noexcept { - return iteration_proxy<const_iterator>(*this); - } + case value_t::object: { + return m_value.object->size(); + } - bool empty() const noexcept { - switch(m_type) { - case value_t::null: { - return true; + default: { + return 1; + } } + } - case value_t::array: { - return m_value.array->empty(); - } + size_type max_size() const noexcept { + switch(m_type) { + case value_t::array: { + return m_value.array->max_size(); + } - case value_t::object: { - return m_value.object->empty(); - } + case value_t::object: { + return m_value.object->max_size(); + } - default: { - return false; + default: { + return size(); + } } } - } - size_type size() const noexcept { - switch(m_type) { - case value_t::null: { - return 0; - } + void clear() noexcept { + switch(m_type) { + case value_t::number_integer: { + m_value.number_integer = 0; + break; + } - case value_t::array: { - return m_value.array->size(); - } + case value_t::number_unsigned: { + m_value.number_unsigned = 0; + break; + } - case value_t::object: { - return m_value.object->size(); - } + case value_t::number_float: { + m_value.number_float = 0.0; + break; + } - default: { - return 1; - } - } - } + case value_t::boolean: { + m_value.boolean = false; + break; + } - size_type max_size() const noexcept { - switch(m_type) { - case value_t::array: { - return m_value.array->max_size(); - } + case value_t::string: { + m_value.string->clear(); + break; + } - case value_t::object: { - return m_value.object->max_size(); - } + case value_t::array: { + m_value.array->clear(); + break; + } + + case value_t::object: { + m_value.object->clear(); + break; + } - default: { - return size(); + default: + break; } } - } - void clear() noexcept { - switch(m_type) { - case value_t::number_integer: { - m_value.number_integer = 0; - break; + void push_back(basic_json &&val) { + if(JSON_UNLIKELY(not(is_null() or is_array()))) { + JSON_THROW(type_error::create(308, "cannot use push_back() with "+std::string(type_name()))); } - case value_t::number_unsigned: { - m_value.number_unsigned = 0; - break; + if(is_null()) { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); } - case value_t::number_float: { - m_value.number_float = 0.0; - break; - } + m_value.array->push_back(std::move(val)); - case value_t::boolean: { - m_value.boolean = false; - break; - } + val.m_type = value_t::null; + } - case value_t::string: { - m_value.string->clear(); - break; - } + reference operator+=(basic_json &&val) { + push_back(std::move(val)); + return *this; + } - case value_t::array: { - m_value.array->clear(); - break; + void push_back(const basic_json &val) { + if(JSON_UNLIKELY(not(is_null() or is_array()))) { + JSON_THROW(type_error::create(308, "cannot use push_back() with "+std::string(type_name()))); } - case value_t::object: { - m_value.object->clear(); - break; + if(is_null()) { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); } - default: - break; - } - } - - void push_back(basic_json &&val) { - if(JSON_UNLIKELY(not(is_null() or is_array()))) { - JSON_THROW(type_error::create(308, "cannot use push_back() with "+std::string(type_name()))); + m_value.array->push_back(val); } - if(is_null()) { - m_type = value_t::array; - m_value = value_t::array; - assert_invariant(); + reference operator+=(const basic_json &val) { + push_back(val); + return *this; } - m_value.array->push_back(std::move(val)); + void push_back(const typename object_t::value_type &val) { + if(JSON_UNLIKELY(not(is_null() or is_object()))) { + JSON_THROW(type_error::create(308, "cannot use push_back() with "+std::string(type_name()))); + } - val.m_type = value_t::null; - } + if(is_null()) { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } - reference operator+=(basic_json &&val) { - push_back(std::move(val)); - return *this; - } + m_value.object->insert(val); + } - void push_back(const basic_json &val) { - if(JSON_UNLIKELY(not(is_null() or is_array()))) { - JSON_THROW(type_error::create(308, "cannot use push_back() with "+std::string(type_name()))); + reference operator+=(const typename object_t::value_type &val) { + push_back(val); + return *this; } - if(is_null()) { - m_type = value_t::array; - m_value = value_t::array; - assert_invariant(); + void push_back(initializer_list_t init) { + if(is_object() and init.size() == 2 and (*init.begin())->is_string()) { + basic_json &&key = init.begin()->moved_or_copied(); + push_back(typename object_t::value_type( + std::move(key.get_ref<string_t &>()), (init.begin()+1)->moved_or_copied())); + } else { + push_back(basic_json(init)); + } } - m_value.array->push_back(val); - } + reference operator+=(initializer_list_t init) { + push_back(init); + return *this; + } - reference operator+=(const basic_json &val) { - push_back(val); - return *this; - } + template<class... Args> + void emplace_back(Args &&... args) { + if(JSON_UNLIKELY(not(is_null() or is_array()))) { + JSON_THROW(type_error::create(311, "cannot use emplace_back() with "+std::string(type_name()))); + } - void push_back(const typename object_t::value_type &val) { - if(JSON_UNLIKELY(not(is_null() or is_object()))) { - JSON_THROW(type_error::create(308, "cannot use push_back() with "+std::string(type_name()))); - } + if(is_null()) { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } - if(is_null()) { - m_type = value_t::object; - m_value = value_t::object; - assert_invariant(); + m_value.array->emplace_back(std::forward<Args>(args)...); } - m_value.object->insert(val); - } + template<class... Args> + std::pair<iterator, bool> emplace(Args &&... args) { + if(JSON_UNLIKELY(not(is_null() or is_object()))) { + JSON_THROW(type_error::create(311, "cannot use emplace() with "+std::string(type_name()))); + } - reference operator+=(const typename object_t::value_type &val) { - push_back(val); - return *this; - } + if(is_null()) { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } - void push_back(initializer_list_t init) { - if(is_object() and init.size() == 2 and (*init.begin())->is_string()) { - basic_json &&key = init.begin()->moved_or_copied(); - push_back(typename object_t::value_type( - std::move(key.get_ref<string_t &>()), (init.begin()+1)->moved_or_copied())); - } else { - push_back(basic_json(init)); - } - } + auto res = m_value.object->emplace(std::forward<Args>(args)...); - reference operator+=(initializer_list_t init) { - push_back(init); - return *this; - } + auto it = begin(); + it.m_it.object_iterator = res.first; - template<class... Args> - void emplace_back(Args &&... args) { - if(JSON_UNLIKELY(not(is_null() or is_array()))) { - JSON_THROW(type_error::create(311, "cannot use emplace_back() with "+std::string(type_name()))); + return {it, res.second}; } - if(is_null()) { - m_type = value_t::array; - m_value = value_t::array; - assert_invariant(); - } + iterator insert(const_iterator pos, const basic_json &val) { + if(JSON_LIKELY(is_array())) { + if(JSON_UNLIKELY(pos.m_object != this)) { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } - m_value.array->emplace_back(std::forward<Args>(args)...); - } + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, val); + return result; + } - template<class... Args> - std::pair<iterator, bool> emplace(Args &&... args) { - if(JSON_UNLIKELY(not(is_null() or is_object()))) { - JSON_THROW(type_error::create(311, "cannot use emplace() with "+std::string(type_name()))); + JSON_THROW(type_error::create(309, "cannot use insert() with "+std::string(type_name()))); } - if(is_null()) { - m_type = value_t::object; - m_value = value_t::object; - assert_invariant(); + iterator insert(const_iterator pos, basic_json &&val) { + return insert(pos, val); } - auto res = m_value.object->emplace(std::forward<Args>(args)...); + iterator insert(const_iterator pos, size_type cnt, const basic_json &val) { + if(JSON_LIKELY(is_array())) { + if(JSON_UNLIKELY(pos.m_object != this)) { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } - auto it = begin(); - it.m_it.object_iterator = res.first; + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); + return result; + } - return {it, res.second}; - } + JSON_THROW(type_error::create(309, "cannot use insert() with "+std::string(type_name()))); + } + + iterator insert(const_iterator pos, const_iterator first, const_iterator last) { + if(JSON_UNLIKELY(not is_array())) { + JSON_THROW(type_error::create(309, "cannot use insert() with "+std::string(type_name()))); + } - iterator insert(const_iterator pos, const basic_json &val) { - if(JSON_LIKELY(is_array())) { if(JSON_UNLIKELY(pos.m_object != this)) { JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); } + if(JSON_UNLIKELY(first.m_object != last.m_object)) { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + if(JSON_UNLIKELY(first.m_object == this)) { + JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container")); + } + iterator result(this); - result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, val); + result.m_it.array_iterator = m_value.array->insert( + pos.m_it.array_iterator, + first.m_it.array_iterator, + last.m_it.array_iterator); return result; } - JSON_THROW(type_error::create(309, "cannot use insert() with "+std::string(type_name()))); - } - - iterator insert(const_iterator pos, basic_json &&val) { - return insert(pos, val); - } + iterator insert(const_iterator pos, initializer_list_t ilist) { + if(JSON_UNLIKELY(not is_array())) { + JSON_THROW(type_error::create(309, "cannot use insert() with "+std::string(type_name()))); + } - iterator insert(const_iterator pos, size_type cnt, const basic_json &val) { - if(JSON_LIKELY(is_array())) { if(JSON_UNLIKELY(pos.m_object != this)) { JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); } iterator result(this); - result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist.begin(), ilist.end()); return result; } - JSON_THROW(type_error::create(309, "cannot use insert() with "+std::string(type_name()))); - } - - iterator insert(const_iterator pos, const_iterator first, const_iterator last) { - if(JSON_UNLIKELY(not is_array())) { - JSON_THROW(type_error::create(309, "cannot use insert() with "+std::string(type_name()))); - } - - if(JSON_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); - } - - if(JSON_UNLIKELY(first.m_object != last.m_object)) { - JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); - } - - if(JSON_UNLIKELY(first.m_object == this)) { - JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container")); - } + void insert(const_iterator first, const_iterator last) { + if(JSON_UNLIKELY(not is_object())) { + JSON_THROW(type_error::create(309, "cannot use insert() with "+std::string(type_name()))); + } - iterator result(this); - result.m_it.array_iterator = m_value.array->insert( - pos.m_it.array_iterator, - first.m_it.array_iterator, - last.m_it.array_iterator); - return result; - } + if(JSON_UNLIKELY(first.m_object != last.m_object)) { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } - iterator insert(const_iterator pos, initializer_list_t ilist) { - if(JSON_UNLIKELY(not is_array())) { - JSON_THROW(type_error::create(309, "cannot use insert() with "+std::string(type_name()))); - } + if(JSON_UNLIKELY(not first.m_object->is_object())) { + JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); + } - if(JSON_UNLIKELY(pos.m_object != this)) { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); } - iterator result(this); - result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist.begin(), ilist.end()); - return result; - } + void update(const_reference j) { + if(is_null()) { + m_type = value_t::object; + m_value.object = create<object_t>(); + assert_invariant(); + } - void insert(const_iterator first, const_iterator last) { - if(JSON_UNLIKELY(not is_object())) { - JSON_THROW(type_error::create(309, "cannot use insert() with "+std::string(type_name()))); - } + if(JSON_UNLIKELY(not is_object())) { + JSON_THROW(type_error::create(312, "cannot use update() with "+std::string(type_name()))); + } + if(JSON_UNLIKELY(not j.is_object())) { + JSON_THROW(type_error::create(312, "cannot use update() with "+std::string(j.type_name()))); + } - if(JSON_UNLIKELY(first.m_object != last.m_object)) { - JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + for(auto it = j.cbegin(); it != j.cend(); ++it) { + m_value.object->operator[](it.key()) = it.value(); + } } - if(JSON_UNLIKELY(not first.m_object->is_object())) { - JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); - } + void update(const_iterator first, const_iterator last) { + if(is_null()) { + m_type = value_t::object; + m_value.object = create<object_t>(); + assert_invariant(); + } - m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); - } + if(JSON_UNLIKELY(not is_object())) { + JSON_THROW(type_error::create(312, "cannot use update() with "+std::string(type_name()))); + } - void update(const_reference j) { - if(is_null()) { - m_type = value_t::object; - m_value.object = create<object_t>(); - assert_invariant(); - } + if(JSON_UNLIKELY(first.m_object != last.m_object)) { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } - if(JSON_UNLIKELY(not is_object())) { - JSON_THROW(type_error::create(312, "cannot use update() with "+std::string(type_name()))); - } - if(JSON_UNLIKELY(not j.is_object())) { - JSON_THROW(type_error::create(312, "cannot use update() with "+std::string(j.type_name()))); - } + if(JSON_UNLIKELY(not first.m_object->is_object() + or not last.m_object->is_object())) { + JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); + } - for(auto it = j.cbegin(); it != j.cend(); ++it) { - m_value.object->operator[](it.key()) = it.value(); + for(auto it = first; it != last; ++it) { + m_value.object->operator[](it.key()) = it.value(); + } } - } - void update(const_iterator first, const_iterator last) { - if(is_null()) { - m_type = value_t::object; - m_value.object = create<object_t>(); + void swap(reference other) noexcept( + std::is_nothrow_move_constructible<value_t>::value and + std::is_nothrow_move_assignable<value_t>::value and + std::is_nothrow_move_constructible<json_value>::value and + std::is_nothrow_move_assignable<json_value>::value + ) { + std::swap(m_type, other.m_type); + std::swap(m_value, other.m_value); assert_invariant(); } - if(JSON_UNLIKELY(not is_object())) { - JSON_THROW(type_error::create(312, "cannot use update() with "+std::string(type_name()))); - } - - if(JSON_UNLIKELY(first.m_object != last.m_object)) { - JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); - } - - if(JSON_UNLIKELY(not first.m_object->is_object() - or not last.m_object->is_object())) { - JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); - } - - for(auto it = first; it != last; ++it) { - m_value.object->operator[](it.key()) = it.value(); - } - } - - void swap(reference other) noexcept( - std::is_nothrow_move_constructible<value_t>::value and - std::is_nothrow_move_assignable<value_t>::value and - std::is_nothrow_move_constructible<json_value>::value and - std::is_nothrow_move_assignable<json_value>::value - ) { - std::swap(m_type, other.m_type); - std::swap(m_value, other.m_value); - assert_invariant(); - } - - void swap(array_t &other) { - if(JSON_LIKELY(is_array())) { - std::swap(*(m_value.array), other); - } else { - JSON_THROW(type_error::create(310, "cannot use swap() with "+std::string(type_name()))); - } - } - - void swap(object_t &other) { - if(JSON_LIKELY(is_object())) { - std::swap(*(m_value.object), other); - } else { - JSON_THROW(type_error::create(310, "cannot use swap() with "+std::string(type_name()))); + void swap(array_t &other) { + if(JSON_LIKELY(is_array())) { + std::swap(*(m_value.array), other); + } else { + JSON_THROW(type_error::create(310, "cannot use swap() with "+std::string(type_name()))); + } } - } - void swap(string_t &other) { - if(JSON_LIKELY(is_string())) { - std::swap(*(m_value.string), other); - } else { - JSON_THROW(type_error::create(310, "cannot use swap() with "+std::string(type_name()))); + void swap(object_t &other) { + if(JSON_LIKELY(is_object())) { + std::swap(*(m_value.object), other); + } else { + JSON_THROW(type_error::create(310, "cannot use swap() with "+std::string(type_name()))); + } } - } - -public: - friend bool operator==(const_reference lhs, const_reference rhs) noexcept { - const auto lhs_type = lhs.type(); - const auto rhs_type = rhs.type(); - - if(lhs_type == rhs_type) { - switch(lhs_type) { - case value_t::array: - return (*lhs.m_value.array == *rhs.m_value.array); - - case value_t::object: - return (*lhs.m_value.object == *rhs.m_value.object); - - case value_t::null: - return true; - - case value_t::string: - return (*lhs.m_value.string == *rhs.m_value.string); - - case value_t::boolean: - return (lhs.m_value.boolean == rhs.m_value.boolean); - case value_t::number_integer: - return (lhs.m_value.number_integer == rhs.m_value.number_integer); - - case value_t::number_unsigned: - return (lhs.m_value.number_unsigned == rhs.m_value.number_unsigned); - - case value_t::number_float: - return (lhs.m_value.number_float == rhs.m_value.number_float); - - default: - return false; + void swap(string_t &other) { + if(JSON_LIKELY(is_string())) { + std::swap(*(m_value.string), other); + } else { + JSON_THROW(type_error::create(310, "cannot use swap() with "+std::string(type_name()))); } - } else if(lhs_type == value_t::number_integer and rhs_type == value_t::number_float) { - return (static_cast<number_float_t>(lhs.m_value.number_integer) == rhs.m_value.number_float); - } else if(lhs_type == value_t::number_float and rhs_type == value_t::number_integer) { - return (lhs.m_value.number_float == static_cast<number_float_t>(rhs.m_value.number_integer)); - } else if(lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) { - return (static_cast<number_float_t>(lhs.m_value.number_unsigned) == rhs.m_value.number_float); - } else if(lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) { - return (lhs.m_value.number_float == static_cast<number_float_t>(rhs.m_value.number_unsigned)); - } else if(lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) { - return (static_cast<number_integer_t>(lhs.m_value.number_unsigned) == rhs.m_value.number_integer); - } else if(lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) { - return (lhs.m_value.number_integer == static_cast<number_integer_t>(rhs.m_value.number_unsigned)); - } - - return false; - } - - template<typename ScalarType, typename std::enable_if< - std::is_scalar<ScalarType>::value, int>::type = 0> - friend bool operator==(const_reference lhs, const ScalarType rhs) noexcept { - return (lhs == basic_json(rhs)); - } - - template<typename ScalarType, typename std::enable_if< - std::is_scalar<ScalarType>::value, int>::type = 0> - friend bool operator==(const ScalarType lhs, const_reference rhs) noexcept { - return (basic_json(lhs) == rhs); - } - - friend bool operator!=(const_reference lhs, const_reference rhs) noexcept { - return not(lhs == rhs); - } - - template<typename ScalarType, typename std::enable_if< - std::is_scalar<ScalarType>::value, int>::type = 0> - friend bool operator!=(const_reference lhs, const ScalarType rhs) noexcept { - return (lhs != basic_json(rhs)); - } - - template<typename ScalarType, typename std::enable_if< - std::is_scalar<ScalarType>::value, int>::type = 0> - friend bool operator!=(const ScalarType lhs, const_reference rhs) noexcept { - return (basic_json(lhs) != rhs); - } + } - friend bool operator<(const_reference lhs, const_reference rhs) noexcept { - const auto lhs_type = lhs.type(); - const auto rhs_type = rhs.type(); + public: + friend bool operator==(const_reference lhs, const_reference rhs) noexcept { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); - if(lhs_type == rhs_type) { - switch(lhs_type) { - case value_t::array: - return (*lhs.m_value.array) < (*rhs.m_value.array); + if(lhs_type == rhs_type) { + switch(lhs_type) { + case value_t::array: + return (*lhs.m_value.array == *rhs.m_value.array); - case value_t::object: - return *lhs.m_value.object < *rhs.m_value.object; + case value_t::object: + return (*lhs.m_value.object == *rhs.m_value.object); - case value_t::null: - return false; + case value_t::null: + return true; - case value_t::string: - return *lhs.m_value.string < *rhs.m_value.string; + case value_t::string: + return (*lhs.m_value.string == *rhs.m_value.string); - case value_t::boolean: - return lhs.m_value.boolean < rhs.m_value.boolean; + case value_t::boolean: + return (lhs.m_value.boolean == rhs.m_value.boolean); - case value_t::number_integer: - return lhs.m_value.number_integer < rhs.m_value.number_integer; + case value_t::number_integer: + return (lhs.m_value.number_integer == rhs.m_value.number_integer); - case value_t::number_unsigned: - return lhs.m_value.number_unsigned < rhs.m_value.number_unsigned; + case value_t::number_unsigned: + return (lhs.m_value.number_unsigned == rhs.m_value.number_unsigned); - case value_t::number_float: - return lhs.m_value.number_float < rhs.m_value.number_float; + case value_t::number_float: + return (lhs.m_value.number_float == rhs.m_value.number_float); - default: - return false; + default: + return false; + } + } else if(lhs_type == value_t::number_integer and rhs_type == value_t::number_float) { + return (static_cast<number_float_t>(lhs.m_value.number_integer) == rhs.m_value.number_float); + } else if(lhs_type == value_t::number_float and rhs_type == value_t::number_integer) { + return (lhs.m_value.number_float == static_cast<number_float_t>(rhs.m_value.number_integer)); + } else if(lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) { + return (static_cast<number_float_t>(lhs.m_value.number_unsigned) == rhs.m_value.number_float); + } else if(lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) { + return (lhs.m_value.number_float == static_cast<number_float_t>(rhs.m_value.number_unsigned)); + } else if(lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) { + return (static_cast<number_integer_t>(lhs.m_value.number_unsigned) == rhs.m_value.number_integer); + } else if(lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) { + return (lhs.m_value.number_integer == static_cast<number_integer_t>(rhs.m_value.number_unsigned)); } - } else if(lhs_type == value_t::number_integer and rhs_type == value_t::number_float) { - return static_cast<number_float_t>(lhs.m_value.number_integer) < rhs.m_value.number_float; - } else if(lhs_type == value_t::number_float and rhs_type == value_t::number_integer) { - return lhs.m_value.number_float < static_cast<number_float_t>(rhs.m_value.number_integer); - } else if(lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) { - return static_cast<number_float_t>(lhs.m_value.number_unsigned) < rhs.m_value.number_float; - } else if(lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) { - return lhs.m_value.number_float < static_cast<number_float_t>(rhs.m_value.number_unsigned); - } else if(lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) { - return lhs.m_value.number_integer < static_cast<number_integer_t>(rhs.m_value.number_unsigned); - } else if(lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) { - return static_cast<number_integer_t>(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; - } - - return operator<(lhs_type, rhs_type); - } - template<typename ScalarType, typename std::enable_if< - std::is_scalar<ScalarType>::value, int>::type = 0> - friend bool operator<(const_reference lhs, const ScalarType rhs) noexcept { - return (lhs < basic_json(rhs)); - } + return false; + } - template<typename ScalarType, typename std::enable_if< - std::is_scalar<ScalarType>::value, int>::type = 0> - friend bool operator<(const ScalarType lhs, const_reference rhs) noexcept { - return (basic_json(lhs) < rhs); - } + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator==(const_reference lhs, const ScalarType rhs) noexcept { + return (lhs == basic_json(rhs)); + } - friend bool operator<=(const_reference lhs, const_reference rhs) noexcept { - return not(rhs < lhs); - } + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator==(const ScalarType lhs, const_reference rhs) noexcept { + return (basic_json(lhs) == rhs); + } - template<typename ScalarType, typename std::enable_if< - std::is_scalar<ScalarType>::value, int>::type = 0> - friend bool operator<=(const_reference lhs, const ScalarType rhs) noexcept { - return (lhs <= basic_json(rhs)); - } + friend bool operator!=(const_reference lhs, const_reference rhs) noexcept { + return not(lhs == rhs); + } - template<typename ScalarType, typename std::enable_if< - std::is_scalar<ScalarType>::value, int>::type = 0> - friend bool operator<=(const ScalarType lhs, const_reference rhs) noexcept { - return (basic_json(lhs) <= rhs); - } + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator!=(const_reference lhs, const ScalarType rhs) noexcept { + return (lhs != basic_json(rhs)); + } - friend bool operator>(const_reference lhs, const_reference rhs) noexcept { - return not(lhs <= rhs); - } + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator!=(const ScalarType lhs, const_reference rhs) noexcept { + return (basic_json(lhs) != rhs); + } - template<typename ScalarType, typename std::enable_if< - std::is_scalar<ScalarType>::value, int>::type = 0> - friend bool operator>(const_reference lhs, const ScalarType rhs) noexcept { - return (lhs > basic_json(rhs)); - } + friend bool operator<(const_reference lhs, const_reference rhs) noexcept { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); - template<typename ScalarType, typename std::enable_if< - std::is_scalar<ScalarType>::value, int>::type = 0> - friend bool operator>(const ScalarType lhs, const_reference rhs) noexcept { - return (basic_json(lhs) > rhs); - } + if(lhs_type == rhs_type) { + switch(lhs_type) { + case value_t::array: + return (*lhs.m_value.array) < (*rhs.m_value.array); - friend bool operator>=(const_reference lhs, const_reference rhs) noexcept { - return not(lhs < rhs); - } + case value_t::object: + return *lhs.m_value.object < *rhs.m_value.object; - template<typename ScalarType, typename std::enable_if< - std::is_scalar<ScalarType>::value, int>::type = 0> - friend bool operator>=(const_reference lhs, const ScalarType rhs) noexcept { - return (lhs >= basic_json(rhs)); - } + case value_t::null: + return false; - template<typename ScalarType, typename std::enable_if< - std::is_scalar<ScalarType>::value, int>::type = 0> - friend bool operator>=(const ScalarType lhs, const_reference rhs) noexcept { - return (basic_json(lhs) >= rhs); - } + case value_t::string: + return *lhs.m_value.string < *rhs.m_value.string; - friend std::ostream &operator<<(std::ostream &o, const basic_json &j) { - const bool pretty_print = (o.width() > 0); - const auto indentation = (pretty_print ? o.width() : 0); + case value_t::boolean: + return lhs.m_value.boolean < rhs.m_value.boolean; - o.width(0); + case value_t::number_integer: + return lhs.m_value.number_integer < rhs.m_value.number_integer; - serializer s(detail::output_adapter<char>(o), o.fill()); - s.dump(j, pretty_print, false, static_cast<unsigned int>(indentation)); - return o; - } + case value_t::number_unsigned: + return lhs.m_value.number_unsigned < rhs.m_value.number_unsigned; - static basic_json parse(detail::input_adapter i, - const parser_callback_t cb = nullptr, - const bool allow_exceptions = true) { - basic_json result; - parser(i, cb, allow_exceptions).parse(true, result); - return result; - } + case value_t::number_float: + return lhs.m_value.number_float < rhs.m_value.number_float; - static basic_json parse(detail::input_adapter &i, - const parser_callback_t cb = nullptr, - const bool allow_exceptions = true) { - basic_json result; - parser(i, cb, allow_exceptions).parse(true, result); - return result; - } + default: + return false; + } + } else if(lhs_type == value_t::number_integer and rhs_type == value_t::number_float) { + return static_cast<number_float_t>(lhs.m_value.number_integer) < rhs.m_value.number_float; + } else if(lhs_type == value_t::number_float and rhs_type == value_t::number_integer) { + return lhs.m_value.number_float < static_cast<number_float_t>(rhs.m_value.number_integer); + } else if(lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) { + return static_cast<number_float_t>(lhs.m_value.number_unsigned) < rhs.m_value.number_float; + } else if(lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) { + return lhs.m_value.number_float < static_cast<number_float_t>(rhs.m_value.number_unsigned); + } else if(lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) { + return lhs.m_value.number_integer < static_cast<number_integer_t>(rhs.m_value.number_unsigned); + } else if(lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) { + return static_cast<number_integer_t>(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; + } - static bool accept(detail::input_adapter i) { - return parser(i).accept(true); - } + return operator<(lhs_type, rhs_type); + } - static bool accept(detail::input_adapter &i) { - return parser(i).accept(true); - } + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator<(const_reference lhs, const ScalarType rhs) noexcept { + return (lhs < basic_json(rhs)); + } - template<class IteratorType, typename std::enable_if< - std::is_base_of< - std::random_access_iterator_tag, - typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0> - static basic_json parse(IteratorType first, IteratorType last, - const parser_callback_t cb = nullptr, - const bool allow_exceptions = true) { - basic_json result; - parser(detail::input_adapter(first, last), cb, allow_exceptions).parse(true, result); - return result; - } + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator<(const ScalarType lhs, const_reference rhs) noexcept { + return (basic_json(lhs) < rhs); + } - template<class IteratorType, typename std::enable_if< - std::is_base_of< - std::random_access_iterator_tag, - typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0> - static bool accept(IteratorType first, IteratorType last) { - return parser(detail::input_adapter(first, last)).accept(true); - } + friend bool operator<=(const_reference lhs, const_reference rhs) noexcept { + return not(rhs < lhs); + } - friend std::istream &operator>>(std::istream &i, basic_json &j) { - parser(detail::input_adapter(i)).parse(false, j); - return i; - } + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator<=(const_reference lhs, const ScalarType rhs) noexcept { + return (lhs <= basic_json(rhs)); + } - const char *type_name() const noexcept { - { - switch(m_type) { - case value_t::null: - return "null"; - case value_t::object: - return "object"; - case value_t::array: - return "array"; - case value_t::string: - return "string"; - case value_t::boolean: - return "boolean"; - case value_t::discarded: - return "discarded"; - default: - return "number"; - } + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator<=(const ScalarType lhs, const_reference rhs) noexcept { + return (basic_json(lhs) <= rhs); } - } -private: - value_t m_type = value_t::null; + friend bool operator>(const_reference lhs, const_reference rhs) noexcept { + return not(lhs <= rhs); + } - json_value m_value = {}; + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator>(const_reference lhs, const ScalarType rhs) noexcept { + return (lhs > basic_json(rhs)); + } -public: - static std::vector<uint8_t> to_cbor(const basic_json &j) { - std::vector<uint8_t> result; - to_cbor(j, result); - return result; - } + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator>(const ScalarType lhs, const_reference rhs) noexcept { + return (basic_json(lhs) > rhs); + } - static void to_cbor(const basic_json &j, detail::output_adapter<uint8_t> o) { - binary_writer<uint8_t>(o).write_cbor(j); - } + friend bool operator>=(const_reference lhs, const_reference rhs) noexcept { + return not(lhs < rhs); + } - static void to_cbor(const basic_json &j, detail::output_adapter<char> o) { - binary_writer<char>(o).write_cbor(j); - } + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator>=(const_reference lhs, const ScalarType rhs) noexcept { + return (lhs >= basic_json(rhs)); + } - static std::vector<uint8_t> to_msgpack(const basic_json &j) { - std::vector<uint8_t> result; - to_msgpack(j, result); - return result; - } + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator>=(const ScalarType lhs, const_reference rhs) noexcept { + return (basic_json(lhs) >= rhs); + } - static void to_msgpack(const basic_json &j, detail::output_adapter<uint8_t> o) { - binary_writer<uint8_t>(o).write_msgpack(j); - } + friend std::ostream &operator<<(std::ostream &o, const basic_json &j) { + const bool pretty_print = (o.width() > 0); + const auto indentation = (pretty_print ? o.width() : 0); - static void to_msgpack(const basic_json &j, detail::output_adapter<char> o) { - binary_writer<char>(o).write_msgpack(j); - } + o.width(0); - static std::vector<uint8_t> to_ubjson(const basic_json &j, - const bool use_size = false, - const bool use_type = false) { - std::vector<uint8_t> result; - to_ubjson(j, result, use_size, use_type); - return result; - } + serializer s(detail::output_adapter<char>(o), o.fill()); + s.dump(j, pretty_print, false, static_cast<unsigned int>(indentation)); + return o; + } - static void to_ubjson(const basic_json &j, detail::output_adapter<uint8_t> o, - const bool use_size = false, const bool use_type = false) { - binary_writer<uint8_t>(o).write_ubjson(j, use_size, use_type); - } + static basic_json parse(detail::input_adapter i, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true) { + basic_json result; + parser(i, cb, allow_exceptions).parse(true, result); + return result; + } - static void to_ubjson(const basic_json &j, detail::output_adapter<char> o, - const bool use_size = false, const bool use_type = false) { - binary_writer<char>(o).write_ubjson(j, use_size, use_type); - } + static basic_json parse(detail::input_adapter &i, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true) { + basic_json result; + parser(i, cb, allow_exceptions).parse(true, result); + return result; + } - static basic_json from_cbor(detail::input_adapter i, - const bool strict = true) { - return binary_reader(i).parse_cbor(strict); - } + static bool accept(detail::input_adapter i) { + return parser(i).accept(true); + } - template<typename A1, typename A2, - detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0> - static basic_json from_cbor(A1 &&a1, A2 &&a2, const bool strict = true) { - return binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).parse_cbor(strict); - } + static bool accept(detail::input_adapter &i) { + return parser(i).accept(true); + } - static basic_json from_msgpack(detail::input_adapter i, - const bool strict = true) { - return binary_reader(i).parse_msgpack(strict); - } + template<class IteratorType, typename std::enable_if< + std::is_base_of< + std::random_access_iterator_tag, + typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0> + static basic_json parse(IteratorType first, IteratorType last, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true) { + basic_json result; + parser(detail::input_adapter(first, last), cb, allow_exceptions).parse(true, result); + return result; + } - template<typename A1, typename A2, - detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0> - static basic_json from_msgpack(A1 &&a1, A2 &&a2, const bool strict = true) { - return binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).parse_msgpack(strict); - } + template<class IteratorType, typename std::enable_if< + std::is_base_of< + std::random_access_iterator_tag, + typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0> + static bool accept(IteratorType first, IteratorType last) { + return parser(detail::input_adapter(first, last)).accept(true); + } - static basic_json from_ubjson(detail::input_adapter i, - const bool strict = true) { - return binary_reader(i).parse_ubjson(strict); - } + friend std::istream &operator>>(std::istream &i, basic_json &j) { + parser(detail::input_adapter(i)).parse(false, j); + return i; + } - template<typename A1, typename A2, - detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0> - static basic_json from_ubjson(A1 &&a1, A2 &&a2, const bool strict = true) { - return binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).parse_ubjson(strict); - } + const char *type_name() const noexcept { + { + switch(m_type) { + case value_t::null: + return "null"; + case value_t::object: + return "object"; + case value_t::array: + return "array"; + case value_t::string: + return "string"; + case value_t::boolean: + return "boolean"; + case value_t::discarded: + return "discarded"; + default: + return "number"; + } + } + } - reference operator[](const json_pointer &ptr) { - return ptr.get_unchecked(this); - } + private: + value_t m_type = value_t::null; - const_reference operator[](const json_pointer &ptr) const { - return ptr.get_unchecked(this); - } + json_value m_value = {}; - reference at(const json_pointer &ptr) { - return ptr.get_checked(this); - } + public: + reference operator[](const json_pointer &ptr) { + return ptr.get_unchecked(this); + } - const_reference at(const json_pointer &ptr) const { - return ptr.get_checked(this); - } + const_reference operator[](const json_pointer &ptr) const { + return ptr.get_unchecked(this); + } - basic_json flatten() const { - basic_json result(value_t::object); - json_pointer::flatten("", *this, result); - return result; - } + reference at(const json_pointer &ptr) { + return ptr.get_checked(this); + } - basic_json unflatten() const { - return json_pointer::unflatten(*this); - } + const_reference at(const json_pointer &ptr) const { + return ptr.get_checked(this); + } - basic_json patch(const basic_json &json_patch) const { - basic_json result = *this; + basic_json flatten() const { + basic_json result(value_t::object); + json_pointer::flatten("", *this, result); + return result; + } - enum class patch_operations { - add, remove, replace, move, copy, test, invalid - }; + basic_json unflatten() const { + return json_pointer::unflatten(*this); + } - const auto get_op = [](const std::string &op) { - if(op == "add") { - return patch_operations::add; - } - if(op == "remove") { - return patch_operations::remove; - } - if(op == "replace") { - return patch_operations::replace; - } - if(op == "move") { - return patch_operations::move; - } - if(op == "copy") { - return patch_operations::copy; - } - if(op == "test") { - return patch_operations::test; - } + basic_json patch(const basic_json &json_patch) const { + basic_json result = *this; - return patch_operations::invalid; - }; + enum class patch_operations { + add, remove, replace, move, copy, test, invalid + }; - const auto operation_add = [&result](json_pointer &ptr, basic_json val) { - if(ptr.is_root()) { - result = val; - } else { - json_pointer top_pointer = ptr.top(); - if(top_pointer != ptr) { - result.at(top_pointer); + const auto get_op = [](const std::string &op) { + if(op == "add") { + return patch_operations::add; + } + if(op == "remove") { + return patch_operations::remove; + } + if(op == "replace") { + return patch_operations::replace; + } + if(op == "move") { + return patch_operations::move; + } + if(op == "copy") { + return patch_operations::copy; + } + if(op == "test") { + return patch_operations::test; } - const auto last_path = ptr.pop_back(); - basic_json &parent = result[ptr]; + return patch_operations::invalid; + }; - switch(parent.m_type) { - case value_t::null: - case value_t::object: { - parent[last_path] = val; - break; + const auto operation_add = [&result](json_pointer &ptr, basic_json val) { + if(ptr.is_root()) { + result = val; + } else { + json_pointer top_pointer = ptr.top(); + if(top_pointer != ptr) { + result.at(top_pointer); } - case value_t::array: { - if(last_path == "-") { - parent.push_back(val); - } else { - const auto idx = json_pointer::array_index(last_path); - if(JSON_UNLIKELY(static_cast<size_type>(idx) > parent.size())) { - JSON_THROW(out_of_range::create(401, "array index "+std::to_string(idx)+" is out of range")); + const auto last_path = ptr.pop_back(); + basic_json &parent = result[ptr]; + + switch(parent.m_type) { + case value_t::null: + case value_t::object: { + parent[last_path] = val; + break; + } + + case value_t::array: { + if(last_path == "-") { + parent.push_back(val); } else { - parent.insert(parent.begin()+static_cast<difference_type>(idx), val); + const auto idx = json_pointer::array_index(last_path); + if(JSON_UNLIKELY(static_cast<size_type>(idx) > parent.size())) { + JSON_THROW(out_of_range::create(401, "array index "+std::to_string(idx)+" is out of range")); + } else { + parent.insert(parent.begin()+static_cast<difference_type>(idx), val); + } } + break; } - break; - } - default: { - assert(false); + default: { + assert(false); + } } } - } - }; - - const auto operation_remove = [&result](json_pointer &ptr) { - const auto last_path = ptr.pop_back(); - basic_json &parent = result.at(ptr); - - if(parent.is_object()) { - auto it = parent.find(last_path); - if(JSON_LIKELY(it != parent.end())) { - parent.erase(it); - } else { - JSON_THROW(out_of_range::create(403, "key '"+last_path+"' not found")); - } - } else if(parent.is_array()) { - parent.erase(static_cast<size_type>(json_pointer::array_index(last_path))); - } - }; - - if(JSON_UNLIKELY(not json_patch.is_array())) { - JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); - } - - for(const auto &val : json_patch) { - const auto get_value = [&val](const std::string &op, - const std::string &member, - bool string_type) -> basic_json & { - auto it = val.m_value.object->find(member); - - const auto error_msg = (op == "op") ? "operation" : "operation '"+op+"'"; + }; - if(JSON_UNLIKELY(it == val.m_value.object->end())) { - JSON_THROW(parse_error::create(105, 0, error_msg+" must have member '"+member+"'")); - } + const auto operation_remove = [&result](json_pointer &ptr) { + const auto last_path = ptr.pop_back(); + basic_json &parent = result.at(ptr); - if(JSON_UNLIKELY(string_type and not it->second.is_string())) { - JSON_THROW(parse_error::create(105, 0, error_msg+" must have string member '"+member+"'")); + if(parent.is_object()) { + auto it = parent.find(last_path); + if(JSON_LIKELY(it != parent.end())) { + parent.erase(it); + } else { + JSON_THROW(out_of_range::create(403, "key '"+last_path+"' not found")); + } + } else if(parent.is_array()) { + parent.erase(static_cast<size_type>(json_pointer::array_index(last_path))); } - - return it->second; }; - if(JSON_UNLIKELY(not val.is_object())) { + if(JSON_UNLIKELY(not json_patch.is_array())) { JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); } - const std::string op = get_value("op", "op", true); - const std::string path = get_value(op, "path", true); - json_pointer ptr(path); - - switch(get_op(op)) { - case patch_operations::add: { - operation_add(ptr, get_value("add", "value", false)); - break; - } + for(const auto &val : json_patch) { + const auto get_value = [&val](const std::string &op, + const std::string &member, + bool string_type) -> basic_json & { + auto it = val.m_value.object->find(member); - case patch_operations::remove: { - operation_remove(ptr); - break; - } + const auto error_msg = (op == "op") ? "operation" : "operation '"+op+"'"; - case patch_operations::replace: { - result.at(ptr) = get_value("replace", "value", false); - break; - } + if(JSON_UNLIKELY(it == val.m_value.object->end())) { + JSON_THROW(parse_error::create(105, 0, error_msg+" must have member '"+member+"'")); + } - case patch_operations::move: { - const std::string from_path = get_value("move", "from", true); - json_pointer from_ptr(from_path); + if(JSON_UNLIKELY(string_type and not it->second.is_string())) { + JSON_THROW(parse_error::create(105, 0, error_msg+" must have string member '"+member+"'")); + } - basic_json v = result.at(from_ptr); + return it->second; + }; - operation_remove(from_ptr); - operation_add(ptr, v); - break; + if(JSON_UNLIKELY(not val.is_object())) { + JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); } - case patch_operations::copy: { - const std::string from_path = get_value("copy", "from", true); - const json_pointer from_ptr(from_path); + const std::string op = get_value("op", "op", true); + const std::string path = get_value(op, "path", true); + json_pointer ptr(path); - basic_json v = result.at(from_ptr); - - operation_add(ptr, v); - break; - } - - case patch_operations::test: { - bool success = false; - JSON_TRY { - success = (result.at(ptr) == get_value("test", "value", false)); + switch(get_op(op)) { + case patch_operations::add: { + operation_add(ptr, get_value("add", "value", false)); + break; } - JSON_CATCH (out_of_range &) { + + case patch_operations::remove: { + operation_remove(ptr); + break; } - if(JSON_UNLIKELY(not success)) { - JSON_THROW(other_error::create(501, "unsuccessful: "+val.dump())); + case patch_operations::replace: { + result.at(ptr) = get_value("replace", "value", false); + break; } - break; - } + case patch_operations::move: { + const std::string from_path = get_value("move", "from", true); + json_pointer from_ptr(from_path); - case patch_operations::invalid: { - JSON_THROW(parse_error::create(105, 0, "operation value '"+op+"' is invalid")); - } - } - } + basic_json v = result.at(from_ptr); - return result; - } + operation_remove(from_ptr); + operation_add(ptr, v); + break; + } - static basic_json diff(const basic_json &source, const basic_json &target, - const std::string &path = "") { - basic_json result(value_t::array); + case patch_operations::copy: { + const std::string from_path = get_value("copy", "from", true); + const json_pointer from_ptr(from_path); - if(source == target) { - return result; - } + basic_json v = result.at(from_ptr); - if(source.type() != target.type()) { - result.push_back( - { - {"op", "replace"}, - {"path", path}, - {"value", target} - }); - } else { - switch(source.type()) { - case value_t::array: { - std::size_t i = 0; - while(i < source.size() and i < target.size()) { - auto temp_diff = diff(source[i], target[i], path+"/"+std::to_string(i)); - result.insert(result.end(), temp_diff.begin(), temp_diff.end()); - ++i; + operation_add(ptr, v); + break; } - const auto end_index = static_cast<difference_type>(result.size()); - while(i < source.size()) { - result.insert(result.begin()+end_index, object( - { - {"op", "remove"}, - {"path", path+"/"+std::to_string(i)} - })); - ++i; - } + case patch_operations::test: { + bool success = false; + JSON_TRY { + success = (result.at(ptr) == get_value("test", "value", false)); + } + JSON_CATCH (out_of_range &) { + } - while(i < target.size()) { - result.push_back( - { - {"op", "add"}, - {"path", path+"/"+std::to_string(i)}, - {"value", target[i]} - }); - ++i; + if(JSON_UNLIKELY(not success)) { + JSON_THROW(other_error::create(501, "unsuccessful: "+val.dump())); + } + + break; } - break; + case patch_operations::invalid: { + JSON_THROW(parse_error::create(105, 0, "operation value '"+op+"' is invalid")); + } } + } - case value_t::object: { - for(auto it = source.cbegin(); it != source.cend(); ++it) { - const auto key = json_pointer::escape(it.key()); + return result; + } + + static basic_json diff(const basic_json &source, const basic_json &target, + const std::string &path = "") { + basic_json result(value_t::array); + + if(source == target) { + return result; + } - if(target.find(it.key()) != target.end()) { - auto temp_diff = diff(it.value(), target[it.key()], path+"/"+key); + if(source.type() != target.type()) { + result.push_back( + { + {"op", "replace"}, + {"path", path}, + {"value", target} + }); + } else { + switch(source.type()) { + case value_t::array: { + std::size_t i = 0; + while(i < source.size() and i < target.size()) { + auto temp_diff = diff(source[i], target[i], path+"/"+std::to_string(i)); result.insert(result.end(), temp_diff.begin(), temp_diff.end()); - } else { - result.push_back(object( + ++i; + } + + const auto end_index = static_cast<difference_type>(result.size()); + while(i < source.size()) { + result.insert(result.begin()+end_index, object( { {"op", "remove"}, - {"path", path+"/"+key} + {"path", path+"/"+std::to_string(i)} })); + ++i; } - } - for(auto it = target.cbegin(); it != target.cend(); ++it) { - if(source.find(it.key()) == source.end()) { - const auto key = json_pointer::escape(it.key()); + while(i < target.size()) { result.push_back( { {"op", "add"}, - {"path", path+"/"+key}, - {"value", it.value()} + {"path", path+"/"+std::to_string(i)}, + {"value", target[i]} }); + ++i; } + + break; } - break; - } + case value_t::object: { + for(auto it = source.cbegin(); it != source.cend(); ++it) { + const auto key = json_pointer::escape(it.key()); - default: { - result.push_back( - { - {"op", "replace"}, - {"path", path}, - {"value", target} - }); - break; + if(target.find(it.key()) != target.end()) { + auto temp_diff = diff(it.value(), target[it.key()], path+"/"+key); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + } else { + result.push_back(object( + { + {"op", "remove"}, + {"path", path+"/"+key} + })); + } + } + + for(auto it = target.cbegin(); it != target.cend(); ++it) { + if(source.find(it.key()) == source.end()) { + const auto key = json_pointer::escape(it.key()); + result.push_back( + { + {"op", "add"}, + {"path", path+"/"+key}, + {"value", it.value()} + }); + } + } + + break; + } + + default: { + result.push_back( + { + {"op", "replace"}, + {"path", path}, + {"value", target} + }); + break; + } } } - } - return result; - } + return result; + } - void merge_patch(const basic_json &patch) { - if(patch.is_object()) { - if(not is_object()) { - *this = object(); - } - for(auto it = patch.begin(); it != patch.end(); ++it) { - if(it.value().is_null()) { - erase(it.key()); - } else { - operator[](it.key()).merge_patch(it.value()); + void merge_patch(const basic_json &patch) { + if(patch.is_object()) { + if(not is_object()) { + *this = object(); + } + for(auto it = patch.begin(); it != patch.end(); ++it) { + if(it.value().is_null()) { + erase(it.key()); + } else { + operator[](it.key()).merge_patch(it.value()); + } } + } else { + *this = patch; } - } else { - *this = patch; } - } -}; + }; } namespace std { -template<> -inline void swap(nlohmann::json &j1, - nlohmann::json &j2) noexcept( -is_nothrow_move_constructible<nlohmann::json>::value and -is_nothrow_move_assignable<nlohmann::json>::value -) { - j1.swap(j2); -} - -template<> -struct hash<nlohmann::json> { - std::size_t operator()(const nlohmann::json &j) const { - const auto &h = hash<nlohmann::json::string_t>(); - return h(j.dump()); + template<> + inline void swap(nlohmann::json &j1, + nlohmann::json &j2) noexcept( + is_nothrow_move_constructible<nlohmann::json>::value and + is_nothrow_move_assignable<nlohmann::json>::value + ) { + j1.swap(j2); } -}; -template<> -struct less<::nlohmann::detail::value_t> { - bool operator()(nlohmann::detail::value_t lhs, - nlohmann::detail::value_t rhs) const noexcept { - return nlohmann::detail::operator<(lhs, rhs); - } -}; + template<> + struct hash<nlohmann::json> { + std::size_t operator()(const nlohmann::json &j) const { + const auto &h = hash<nlohmann::json::string_t>(); + return h(j.dump()); + } + }; + + template<> + struct less<::nlohmann::detail::value_t> { + bool operator()(nlohmann::detail::value_t lhs, + nlohmann::detail::value_t rhs) const noexcept { + return nlohmann::detail::operator<(lhs, rhs); + } + }; } diff --git a/src/cmake.cc b/src/cmake.cc index 252f2586..734cf0a1 100644 --- a/src/cmake.cc +++ b/src/cmake.cc @@ -1,3 +1,4 @@ +#include <map> #include "project_build.h" #include "makefile_base.h" #include "filesystem.h" diff --git a/src/config.cc b/src/config.cc index bc8d2e8d..83c35b3d 100644 --- a/src/config.cc +++ b/src/config.cc @@ -1,10 +1,166 @@ #include "config.h" -#include <exception> #include "files.h" #include <iostream> #include "filesystem.h" #include "terminal.h" -#include <algorithm> +#include <boost/property_tree/json_parser.hpp> + +namespace { + /// Used to dispatch Terminal outputs after juCi++ GUI setup and configuration + Dispatcher dispatcher; + + void make_version_dependent_corrections(boost::property_tree::ptree &cfg, + const boost::property_tree::ptree &default_cfg, + const std::string &version) { + auto &keybindings_cfg = cfg.get_child("keybindings"); + try { + if(version <= "1.2.4") { + auto it_file_print = keybindings_cfg.find("print"); + if(it_file_print != keybindings_cfg.not_found() && it_file_print->second.data() == "<primary>p") { + dispatcher.post([] { + ::Terminal::get().print("Preference change: keybindings.print set to \"\"\n"); + }); + it_file_print->second.data() = ""; + } + } + } + catch(const std::exception &e) { + std::cerr << "Error correcting preferences: " << e.what() << std::endl; + } + } + + bool add_missing_nodes(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, + std::string parent_path = "") { + if(!parent_path.empty()) + parent_path += "."; + bool unchanged = true; + for(auto &node: default_cfg) { + auto path = parent_path+node.first; + try { + cfg.get<std::string>(path); + } + catch(const std::exception &e) { + cfg.add(path, node.second.data()); + unchanged = false; + } + unchanged &= add_missing_nodes(cfg, node.second, path); + } + return unchanged; + } + + bool remove_deprecated_nodes(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, + std::string parent_path = "") { + if(!parent_path.empty()) + parent_path += "."; + bool unchanged = true; + for(auto it = cfg.begin(); it != cfg.end();) { + auto path = parent_path+it->first; + try { + default_cfg.get<std::string>(path); + unchanged &= remove_deprecated_nodes(it->second, default_cfg, path); + ++it; + } + catch(const std::exception &e) { + it = cfg.erase(it); + unchanged = false; + } + } + return unchanged; + } + + void update(boost::property_tree::ptree &cfg) { + auto &cfgs = Config::get(); + boost::property_tree::ptree default_cfg; + bool cfg_ok = true; + if(cfg.get<std::string>("version") != JUCI_VERSION) { + std::stringstream ss; + ss << default_config_file; + boost::property_tree::read_json(ss, default_cfg); + cfg_ok = false; + auto it_version = cfg.find("version"); + if(it_version != cfg.not_found()) { + make_version_dependent_corrections(cfg, default_cfg, it_version->second.data()); + it_version->second.data() = JUCI_VERSION; + } + + auto style_path = cfgs.home_juci_path / "styles"; + filesystem::write(style_path / "juci-light.xml", juci_light_style); + filesystem::write(style_path / "juci-dark.xml", juci_dark_style); + filesystem::write(style_path / "juci-dark-blue.xml", juci_dark_blue_style); + } else + return; + cfg_ok &= add_missing_nodes(cfg, default_cfg); + cfg_ok &= remove_deprecated_nodes(cfg, default_cfg); + if(!cfg_ok) + boost::property_tree::write_json((cfgs.home_juci_path / "config" / "config.json").string(), cfg); + } + + void read(const boost::property_tree::ptree &cfg) { + auto &cfgs = Config::get(); + auto keybindings_pt = cfg.get_child("keybindings"); + for(auto &i : keybindings_pt) { + cfgs.menu.keys[i.first] = i.second.get_value<std::string>(); + } + + auto source_json = cfg.get_child("source"); + cfgs.source.style = source_json.get<std::string>("style"); + cfgs.source.font = source_json.get<std::string>("font"); + cfgs.source.cleanup_whitespace_characters = source_json.get<bool>("cleanup_whitespace_characters"); + cfgs.source.show_whitespace_characters = source_json.get<std::string>("show_whitespace_characters"); + cfgs.source.format_style_on_save = source_json.get<bool>("format_style_on_save"); + cfgs.source.format_style_on_save_if_style_file_found = source_json.get<bool>( + "format_style_on_save_if_style_file_found"); + cfgs.source.smart_brackets = source_json.get<bool>("smart_brackets"); + cfgs.source.smart_inserts = source_json.get<bool>("smart_inserts"); + if(cfgs.source.smart_inserts) + cfgs.source.smart_brackets = true; + cfgs.source.show_map = source_json.get<bool>("show_map"); + cfgs.source.map_font_size = source_json.get<std::string>("map_font_size"); + cfgs.source.show_git_diff = source_json.get<bool>("show_git_diff"); + cfgs.source.show_background_pattern = source_json.get<bool>("show_background_pattern"); + cfgs.source.show_right_margin = source_json.get<bool>("show_right_margin"); + cfgs.source.right_margin_position = source_json.get<unsigned>("right_margin_position"); + cfgs.source.spellcheck_language = source_json.get<std::string>("spellcheck_language"); + cfgs.source.default_tab_char = source_json.get<char>("default_tab_char"); + cfgs.source.default_tab_size = source_json.get<unsigned>("default_tab_size"); + cfgs.source.auto_tab_char_and_size = source_json.get<bool>("auto_tab_char_and_size"); + cfgs.source.tab_indents_line = source_json.get<bool>("tab_indents_line"); + cfgs.source.wrap_lines = source_json.get<bool>("wrap_lines"); + cfgs.source.highlight_current_line = source_json.get<bool>("highlight_current_line"); + cfgs.source.show_line_numbers = source_json.get<bool>("show_line_numbers"); + cfgs.source.enable_multiple_cursors = source_json.get<bool>("enable_multiple_cursors"); + cfgs.source.auto_reload_changed_files = source_json.get<bool>("auto_reload_changed_files"); + cfgs.source.clang_format_style = source_json.get<std::string>("clang_format_style"); + cfgs.source.clang_usages_threads = static_cast<unsigned>(source_json.get<int>("clang_usages_threads")); + auto pt_doc_search = cfg.get_child("documentation_searches"); + for(auto &pt_doc_search_lang: pt_doc_search) { + cfgs.source.documentation_searches[pt_doc_search_lang.first].separator = pt_doc_search_lang.second.get<std::string>( + "separator"); + auto &queries = cfgs.source.documentation_searches.find(pt_doc_search_lang.first)->second.queries; + for(auto &i: pt_doc_search_lang.second.get_child("queries")) { + queries[i.first] = i.second.get_value<std::string>(); + } + } + + cfgs.window.theme_name = cfg.get<std::string>("gtk_theme.name"); + cfgs.window.theme_variant = cfg.get<std::string>("gtk_theme.variant"); + cfgs.window.version = cfg.get<std::string>("version"); + + cfgs.project.default_build_path = cfg.get<std::string>("project.default_build_path"); + cfgs.project.debug_build_path = cfg.get<std::string>("project.debug_build_path"); + cfgs.project.cmake.command = cfg.get<std::string>("project.cmake.command"); + cfgs.project.cmake.compile_command = cfg.get<std::string>("project.cmake.compile_command"); + cfgs.project.meson.command = cfg.get<std::string>("project.meson.command"); + cfgs.project.meson.compile_command = cfg.get<std::string>("project.meson.compile_command"); + cfgs.project.save_on_compile_or_run = cfg.get<bool>("project.save_on_compile_or_run"); + cfgs.project.clear_terminal_on_compile = cfg.get<bool>("project.clear_terminal_on_compile"); + cfgs.project.ctags_command = cfg.get<std::string>("project.ctags_command"); + cfgs.project.python_command = cfg.get<std::string>("project.python_command"); + + cfgs.terminal.history_size = cfg.get<int>("terminal.history_size"); + cfgs.terminal.font = cfg.get<std::string>("terminal.font"); + } +} Config::Config() { home_path = filesystem::get_home_path(); @@ -58,152 +214,3 @@ void Config::find_or_create_config_files() { if (!boost::filesystem::exists(juci_style_path)) filesystem::write(juci_style_path, juci_dark_blue_style); } - -void Config::update(boost::property_tree::ptree &cfg) { - boost::property_tree::ptree default_cfg; - bool cfg_ok = true; - if (cfg.get<std::string>("version") != JUCI_VERSION) { - std::stringstream ss; - ss << default_config_file; - boost::property_tree::read_json(ss, default_cfg); - cfg_ok = false; - auto it_version = cfg.find("version"); - if (it_version != cfg.not_found()) { - make_version_dependent_corrections(cfg, default_cfg, it_version->second.data()); - it_version->second.data() = JUCI_VERSION; - } - - auto style_path = home_juci_path / "styles"; - filesystem::write(style_path / "juci-light.xml", juci_light_style); - filesystem::write(style_path / "juci-dark.xml", juci_dark_style); - filesystem::write(style_path / "juci-dark-blue.xml", juci_dark_blue_style); - } else - return; - cfg_ok &= add_missing_nodes(cfg, default_cfg); - cfg_ok &= remove_deprecated_nodes(cfg, default_cfg); - if (!cfg_ok) - boost::property_tree::write_json((home_juci_path / "config" / "config.json").string(), cfg); -} - -void Config::make_version_dependent_corrections(boost::property_tree::ptree &cfg, - const boost::property_tree::ptree &default_cfg, - const std::string &version) { - auto &keybindings_cfg = cfg.get_child("keybindings"); - try { - if (version <= "1.2.4") { - auto it_file_print = keybindings_cfg.find("print"); - if (it_file_print != keybindings_cfg.not_found() && it_file_print->second.data() == "<primary>p") { - dispatcher.post([] { - ::Terminal::get().print("Preference change: keybindings.print set to \"\"\n"); - }); - it_file_print->second.data() = ""; - } - } - } - catch (const std::exception &e) { - std::cerr << "Error correcting preferences: " << e.what() << std::endl; - } -} - -bool Config::add_missing_nodes(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, - std::string parent_path) { - if (parent_path.size() > 0) - parent_path += "."; - bool unchanged = true; - for (auto &node: default_cfg) { - auto path = parent_path + node.first; - try { - cfg.get<std::string>(path); - } - catch (const std::exception &e) { - cfg.add(path, node.second.data()); - unchanged = false; - } - unchanged &= add_missing_nodes(cfg, node.second, path); - } - return unchanged; -} - -bool Config::remove_deprecated_nodes(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, - std::string parent_path) { - if (parent_path.size() > 0) - parent_path += "."; - bool unchanged = true; - for (auto it = cfg.begin(); it != cfg.end();) { - auto path = parent_path + it->first; - try { - default_cfg.get<std::string>(path); - unchanged &= remove_deprecated_nodes(it->second, default_cfg, path); - ++it; - } - catch (const std::exception &e) { - it = cfg.erase(it); - unchanged = false; - } - } - return unchanged; -} - -void Config::read(const boost::property_tree::ptree &cfg) { - auto keybindings_pt = cfg.get_child("keybindings"); - for (auto &i : keybindings_pt) { - menu.keys[i.first] = i.second.get_value<std::string>(); - } - - auto source_json = cfg.get_child("source"); - source.style = source_json.get<std::string>("style"); - source.font = source_json.get<std::string>("font"); - source.cleanup_whitespace_characters = source_json.get<bool>("cleanup_whitespace_characters"); - source.show_whitespace_characters = source_json.get<std::string>("show_whitespace_characters"); - source.format_style_on_save = source_json.get<bool>("format_style_on_save"); - source.format_style_on_save_if_style_file_found = source_json.get<bool>("format_style_on_save_if_style_file_found"); - source.smart_brackets = source_json.get<bool>("smart_brackets"); - source.smart_inserts = source_json.get<bool>("smart_inserts"); - if (source.smart_inserts) - source.smart_brackets = true; - source.show_map = source_json.get<bool>("show_map"); - source.map_font_size = source_json.get<std::string>("map_font_size"); - source.show_git_diff = source_json.get<bool>("show_git_diff"); - source.show_background_pattern = source_json.get<bool>("show_background_pattern"); - source.show_right_margin = source_json.get<bool>("show_right_margin"); - source.right_margin_position = source_json.get<unsigned>("right_margin_position"); - source.spellcheck_language = source_json.get<std::string>("spellcheck_language"); - source.default_tab_char = source_json.get<char>("default_tab_char"); - source.default_tab_size = source_json.get<unsigned>("default_tab_size"); - source.auto_tab_char_and_size = source_json.get<bool>("auto_tab_char_and_size"); - source.tab_indents_line = source_json.get<bool>("tab_indents_line"); - source.wrap_lines = source_json.get<bool>("wrap_lines"); - source.highlight_current_line = source_json.get<bool>("highlight_current_line"); - source.show_line_numbers = source_json.get<bool>("show_line_numbers"); - source.enable_multiple_cursors = source_json.get<bool>("enable_multiple_cursors"); - source.auto_reload_changed_files = source_json.get<bool>("auto_reload_changed_files"); - source.clang_format_style = source_json.get<std::string>("clang_format_style"); - source.clang_usages_threads = static_cast<unsigned>(source_json.get<int>("clang_usages_threads")); - auto pt_doc_search = cfg.get_child("documentation_searches"); - for (auto &pt_doc_search_lang: pt_doc_search) { - source.documentation_searches[pt_doc_search_lang.first].separator = pt_doc_search_lang.second.get<std::string>( - "separator"); - auto &queries = source.documentation_searches.find(pt_doc_search_lang.first)->second.queries; - for (auto &i: pt_doc_search_lang.second.get_child("queries")) { - queries[i.first] = i.second.get_value<std::string>(); - } - } - - window.theme_name = cfg.get<std::string>("gtk_theme.name"); - window.theme_variant = cfg.get<std::string>("gtk_theme.variant"); - window.version = cfg.get<std::string>("version"); - - 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.cmake.command = cfg.get<std::string>("project.cmake.command"); - project.cmake.compile_command = cfg.get<std::string>("project.cmake.compile_command"); - project.meson.command = cfg.get<std::string>("project.meson.command"); - project.meson.compile_command = cfg.get<std::string>("project.meson.compile_command"); - project.save_on_compile_or_run = cfg.get<bool>("project.save_on_compile_or_run"); - project.clear_terminal_on_compile = cfg.get<bool>("project.clear_terminal_on_compile"); - project.ctags_command = cfg.get<std::string>("project.ctags_command"); - project.python_command = cfg.get<std::string>("project.python_command"); - - terminal.history_size = cfg.get<int>("terminal.history_size"); - terminal.font = cfg.get<std::string>("terminal.font"); -} diff --git a/src/config.h b/src/config.h index 096374b8..ba56be69 100644 --- a/src/config.h +++ b/src/config.h @@ -1,12 +1,8 @@ #pragma once -#include <boost/property_tree/json_parser.hpp> #include <boost/filesystem.hpp> #include <unordered_map> #include <string> -#include <utility> -#include <vector> -#include "dispatcher.h" class Config { public: @@ -117,22 +113,5 @@ class Config { boost::filesystem::path home_juci_path; private: - /// Used to dispatch Terminal outputs after juCi++ GUI setup and configuration - Dispatcher dispatcher; - void find_or_create_config_files(); - - void update(boost::property_tree::ptree &cfg); - - void - make_version_dependent_corrections(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, - const std::string &version); - - bool add_missing_nodes(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, - std::string parent_path = ""); - - bool remove_deprecated_nodes(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, - std::string parent_path = ""); - - void read(const boost::property_tree::ptree &cfg); }; diff --git a/src/window.cc b/src/window.cc index 9e0fb579..6a4fa057 100644 --- a/src/window.cc +++ b/src/window.cc @@ -1,3 +1,4 @@ +#include <boost/property_tree/json_parser.hpp> #include "window.h" #include "config.h" #include "menu.h"