From e67799eec39665b0937ca7dff93d40fa90e28d3c Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 8 Sep 2020 19:19:49 +0200 Subject: [PATCH 001/375] Added and made use of gray color in terminal. Also made all error messages red. --- src/debug_lldb.cpp | 16 ++++++++-------- src/directories.cpp | 2 +- src/project.cpp | 32 ++++++++++++++++---------------- src/source_diff.cpp | 2 +- src/source_language_protocol.cpp | 4 ++-- src/terminal.cpp | 12 +++++++++++- src/terminal.hpp | 2 +- src/window.cpp | 4 ++-- 8 files changed, 42 insertions(+), 32 deletions(-) diff --git a/src/debug_lldb.cpp b/src/debug_lldb.cpp index e9dd9d8..fcad197 100644 --- a/src/debug_lldb.cpp +++ b/src/debug_lldb.cpp @@ -131,7 +131,7 @@ void Debug::LLDB::start(const std::string &command, const boost::filesystem::pat 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); + Terminal::get().async_print("\e[31mError (debug)\e[m: Could not create debug target to: " + executable + '\n', true); for(auto &handler : on_exit) handler(-1); return; @@ -140,7 +140,7 @@ void Debug::LLDB::start(const std::string &command, const boost::filesystem::pat //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); + Terminal::get().async_print("\e[31mError (debug)\e[m: Could not create breakpoint at: " + breakpoint.first.string() + ":" + std::to_string(breakpoint.second) + '\n', true); for(auto &handler : on_exit) handler(-1); return; @@ -152,7 +152,7 @@ void Debug::LLDB::start(const std::string &command, const boost::filesystem::pat auto connect_string = "connect://" + remote_host; process = std::make_unique(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); + Terminal::get().async_print(std::string("\e[31mError (debug)\e[m: ") + error.GetCString() + '\n', true); for(auto &handler : on_exit) handler(-1); return; @@ -197,7 +197,7 @@ void Debug::LLDB::start(const std::string &command, const boost::filesystem::pat } if(error.Fail()) { - Terminal::get().async_print(std::string("Error (debug): ") + error.GetCString() + '\n', true); + Terminal::get().async_print(std::string("\e[31mError (debug)\e[m: ") + error.GetCString() + '\n', true); for(auto &handler : on_exit) handler(-1); return; @@ -279,7 +279,7 @@ void Debug::LLDB::stop() { if(state == lldb::StateType::eStateRunning) { auto error = process->Stop(); if(error.Fail()) - Terminal::get().async_print(std::string("Error (debug): ") + error.GetCString() + '\n', true); + Terminal::get().async_print(std::string("\e[31mError (debug)\e[m: ") + error.GetCString() + '\n', true); } } @@ -288,7 +288,7 @@ void Debug::LLDB::kill() { if(process) { auto error = process->Kill(); if(error.Fail()) - Terminal::get().async_print(std::string("Error (debug): ") + error.GetCString() + '\n', true); + Terminal::get().async_print(std::string("\e[31mError (debug)\e[m: ") + error.GetCString() + '\n', true); } } @@ -534,7 +534,7 @@ void Debug::LLDB::add_breakpoint(const boost::filesystem::path &file_path, int l LockGuard 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); + Terminal::get().async_print("\e[31mError (debug)\e[m: Could not create breakpoint at: " + file_path.string() + ":" + std::to_string(line_nr) + '\n', true); } } @@ -553,7 +553,7 @@ void Debug::LLDB::remove_breakpoint(const boost::filesystem::path &file_path, in 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); + Terminal::get().async_print("\e[31mError (debug)\e[m: Could not delete breakpoint at: " + file_path.string() + ":" + std::to_string(line_nr) + '\n', true); return; } } diff --git a/src/directories.cpp b/src/directories.cpp index d8353c0..e5396fb 100644 --- a/src/directories.cpp +++ b/src/directories.cpp @@ -801,7 +801,7 @@ void Directories::colorize_path(boost::filesystem::path dir_path_, bool include_ status = repository->get_status(); } catch(const std::exception &e) { - Terminal::get().async_print(std::string("Error (git): ") + e.what() + '\n', true); + Terminal::get().async_print(std::string("\e[31mError (git)\e[m: ") + e.what() + '\n', true); } dispatcher.post([this, dir_path, include_parent_paths, status = std::move(status)] { diff --git a/src/project.cpp b/src/project.cpp index a857541..3fe4c86 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -381,7 +381,7 @@ void Project::LLDB::debug_start() { if(Config::get().terminal.clear_on_compile) Terminal::get().clear(); - Terminal::get().print("Compiling and debugging " + *run_arguments + "\n"); + Terminal::get().print("\e[2mCompiling and debugging " + *run_arguments + "\e[m\n"); Terminal::get().async_process(build->get_compile_command(), debug_build_path, [self = this->shared_from_this(), run_arguments, project_path](int exit_status) { if(exit_status != EXIT_SUCCESS) debugging = false; @@ -409,7 +409,7 @@ void Project::LLDB::debug_start() { Debug::LLDB::get().on_exit.erase(on_exit_it); Debug::LLDB::get().on_exit.emplace_back([self, run_arguments](int exit_status) { debugging = false; - Terminal::get().async_print(*run_arguments + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); + Terminal::get().async_print("\e[2m" + *run_arguments + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); self->dispatcher.post([] { debug_update_status(""); }); @@ -852,7 +852,7 @@ void Project::Clang::compile() { if(Config::get().terminal.clear_on_compile) Terminal::get().clear(); - Terminal::get().print("Compiling project " + filesystem::get_short_path(build->project_path).string() + "\n"); + Terminal::get().print("\e[2mCompiling project " + filesystem::get_short_path(build->project_path).string() + "\e[m\n"); Terminal::get().async_process(build->get_compile_command(), default_build_path, [](int exit_status) { compiling = false; }); @@ -890,12 +890,12 @@ void Project::Clang::compile_and_run() { if(Config::get().terminal.clear_on_compile) Terminal::get().clear(); - Terminal::get().print("Compiling and running " + arguments + "\n"); + Terminal::get().print("\e[2mCompiling and running " + arguments + "\e[m\n"); Terminal::get().async_process(build->get_compile_command(), default_build_path, [arguments, project_path](int exit_status) { compiling = false; - if(exit_status == EXIT_SUCCESS) { + if(exit_status == 0) { Terminal::get().async_process(arguments, project_path, [arguments](int exit_status) { - Terminal::get().async_print(arguments + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); + Terminal::get().async_print("\e[2m" + arguments + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); }); } }); @@ -999,9 +999,9 @@ void Project::Python::compile_and_run() { if(Config::get().terminal.clear_on_compile) Terminal::get().clear(); - Terminal::get().print("Running " + command + "\n"); + Terminal::get().print("\e[2mRunning " + command + "\e[m\n"); Terminal::get().async_process(command, path, [command](int exit_status) { - Terminal::get().async_print(command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); + Terminal::get().async_print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); }); } @@ -1025,9 +1025,9 @@ void Project::JavaScript::compile_and_run() { if(Config::get().terminal.clear_on_compile) Terminal::get().clear(); - Terminal::get().print("Running " + command + "\n"); + Terminal::get().print("\e[2mRunning " + command + "\e[m\n"); Terminal::get().async_process(command, path, [command](int exit_status) { - Terminal::get().async_print(command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); + Terminal::get().async_print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); }); } @@ -1038,9 +1038,9 @@ void Project::HTML::compile_and_run() { if(Config::get().terminal.clear_on_compile) Terminal::get().clear(); - Terminal::get().print("Running " + command + "\n"); + Terminal::get().print("\e[2mRunning " + command + "\e[m\n"); Terminal::get().async_process(command, build->project_path, [command](int exit_status) { - Terminal::get().async_print(command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); + Terminal::get().async_print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); }); } else if(auto view = Notebook::get().get_current_view()) @@ -1066,7 +1066,7 @@ void Project::Rust::compile() { if(Config::get().terminal.clear_on_compile) Terminal::get().clear(); - Terminal::get().print("Compiling project " + filesystem::get_short_path(build->project_path).string() + "\n"); + Terminal::get().print("\e[2mCompiling project " + filesystem::get_short_path(build->project_path).string() + "\e[m\n"); auto command = build->get_compile_command(); Terminal::get().async_process(command, build->project_path, [](int exit_status) { @@ -1081,14 +1081,14 @@ void Project::Rust::compile_and_run() { Terminal::get().clear(); auto arguments = get_run_arguments().second; - Terminal::get().print("Compiling and running " + arguments + "\n"); + Terminal::get().print("\e[2mCompiling and running " + arguments + "\e[m\n"); auto self = this->shared_from_this(); Terminal::get().async_process(build->get_compile_command(), build->project_path, [self, arguments = std::move(arguments)](int exit_status) { compiling = false; - if(exit_status == EXIT_SUCCESS) { + if(exit_status == 0) { Terminal::get().async_process(arguments, self->build->project_path, [arguments](int exit_status) { - Terminal::get().async_print(arguments + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); + Terminal::get().async_print("\e[2m" + arguments + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); }); } }); diff --git a/src/source_diff.cpp b/src/source_diff.cpp index 3a55fb3..22f683a 100644 --- a/src/source_diff.cpp +++ b/src/source_diff.cpp @@ -263,7 +263,7 @@ void Source::DiffView::configure() { 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); + Terminal::get().print(std::string("\e[31mError (git)\e[m: ") + e_what + '\n', true); }); } }); diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 8180274..cf49a3d 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -352,7 +352,7 @@ void LanguageProtocol::Client::write_request(Source::LanguageProtocolView *view, LockGuard lock(read_write_mutex); auto id_it = handlers.find(message_id); if(id_it != handlers.end()) { - Terminal::get().async_print("Request to language server timed out. If you suspect the server has crashed, please close and reopen all project source files.\n", true); + Terminal::get().async_print("\e[33mWarning\e[m: request to language server timed out. If you suspect the server has crashed, please close and reopen all project source files.\n", true); auto function = std::move(id_it->second.second); handlers.erase(id_it); lock.unlock(); @@ -366,7 +366,7 @@ void LanguageProtocol::Client::write_request(Source::LanguageProtocolView *view, if(Config::get().log.language_server) 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); + Terminal::get().async_print("\e[31mError\e[m: could not write to language server. Please close and reopen all project files.\n", true); auto id_it = handlers.find(message_id - 1); if(id_it != handlers.end()) { auto function = std::move(id_it->second.second); diff --git a/src/terminal.cpp b/src/terminal.cpp index 332b602..c73c545 100644 --- a/src/terminal.cpp +++ b/src/terminal.cpp @@ -30,6 +30,7 @@ Terminal::Terminal() : Source::SearchView() { blue_tag = get_buffer()->create_tag(); magenta_tag = get_buffer()->create_tag(); cyan_tag = get_buffer()->create_tag(); + gray_tag = get_buffer()->create_tag(); link_mouse_cursor = Gdk::Cursor::create(Gdk::CursorType::HAND1); default_mouse_cursor = Gdk::Cursor::create(Gdk::CursorType::XTERM); @@ -158,7 +159,7 @@ Terminal::Terminal() : Source::SearchView() { } else if(code == 48 || code == 58) break; // Do not read next arguments - else if(code == 0 || (code >= 30 && code <= 37)) + else if(code == 0 || code == 2 || code == 22 || (code >= 30 && code <= 37)) color = code; } catch(...) { @@ -182,6 +183,8 @@ Terminal::Terminal() : Source::SearchView() { get_buffer()->apply_tag(magenta_tag, (*last_color_sequence_mark)->get_iter(), start); else if(last_color == 36) get_buffer()->apply_tag(cyan_tag, (*last_color_sequence_mark)->get_iter(), start); + else if(last_color == 37 || last_color == 2) + get_buffer()->apply_tag(gray_tag, (*last_color_sequence_mark)->get_iter(), start); } if(color >= 0) { @@ -490,6 +493,13 @@ void Terminal::configure() { rgba.set_blue(normal_color.get_blue() + factor * (rgba.get_blue() - normal_color.get_blue())); cyan_tag->property_foreground_rgba() = rgba; + rgba.set_rgba(0.5, 0.5, 0.5); + factor = light_theme ? 0.6 : 0.4; + rgba.set_red(normal_color.get_red() + factor * (rgba.get_red() - normal_color.get_red())); + rgba.set_green(normal_color.get_green() + factor * (rgba.get_green() - normal_color.get_green())); + rgba.set_blue(normal_color.get_blue() + factor * (rgba.get_blue() - normal_color.get_blue())); + gray_tag->property_foreground_rgba() = rgba; + // Set search match style: get_buffer()->get_tag_table()->foreach([](const Glib::RefPtr &tag) { if(tag->property_background_set()) { diff --git a/src/terminal.hpp b/src/terminal.hpp index 3781e72..41d0386 100644 --- a/src/terminal.hpp +++ b/src/terminal.hpp @@ -46,7 +46,7 @@ private: Glib::RefPtr bold_tag; Glib::RefPtr link_tag; Glib::RefPtr invisible_tag; - Glib::RefPtr red_tag, green_tag, yellow_tag, blue_tag, magenta_tag, cyan_tag; + Glib::RefPtr red_tag, green_tag, yellow_tag, blue_tag, magenta_tag, cyan_tag, gray_tag; Glib::RefPtr link_mouse_cursor; Glib::RefPtr default_mouse_cursor; diff --git a/src/window.cpp b/src/window.cpp index 3d69dcc..ed73397 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -1383,10 +1383,10 @@ void Window::set_menu_actions() { auto directory_folder = Project::get_preferably_directory_folder(); if(Config::get().terminal.clear_on_run_command) Terminal::get().clear(); - Terminal::get().async_print("Running: " + content + '\n'); + Terminal::get().async_print("\e[2mRunning: " + content + "\e[m\n"); Terminal::get().async_process(content, directory_folder, [content](int exit_status) { - Terminal::get().async_print(content + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); + Terminal::get().async_print("\e[2m" + content + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); }); } EntryBox::get().hide(); From 3124fa7fa3e2bb92331c168cca89603929c125e7 Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 9 Sep 2020 09:28:45 +0200 Subject: [PATCH 002/375] Added suggestion to restart juCi++ on libclang completion failure --- src/source_clang.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/source_clang.cpp b/src/source_clang.cpp index 4614239..1d72a35 100644 --- a/src/source_clang.cpp +++ b/src/source_clang.cpp @@ -196,7 +196,7 @@ void Source::ClangViewParse::parse_initialize() { parse_state = ParseState::stop; parse_mutex.unlock(); dispatcher.post([this] { - Terminal::get().print("\e[31mError\e[m: failed to reparse " + this->file_path.string() + ".\n", true); + Terminal::get().print("\e[31mError\e[m: failed to reparse " + filesystem::get_short_path(this->file_path).string() + ".\n", true); status_state = ""; if(update_status_state) update_status_state(this); @@ -911,7 +911,7 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa }; autocomplete.on_add_rows_error = [this] { - Terminal::get().print("\e[31mError\e[m: autocomplete failed, reparsing " + this->file_path.string() + '\n', true); + Terminal::get().print("\e[31mError\e[m: completion failed, reparsing " + filesystem::get_short_path(this->file_path.string()).string() + ". You should restart juCi++ to recover potentially lost resources.\n", true); selected_completion_string = nullptr; code_complete_results = nullptr; full_reparse(); @@ -1253,7 +1253,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file usages_renamed.emplace_back(&usage); } else - Terminal::get().print("\e[31mError\e[m: could not write to file " + usage.path.string() + '\n', true); + Terminal::get().print("\e[31mError\e[m: could not write to file " + filesystem::get_short_path(usage.path).string() + '\n', true); } } From 85b19da51b54db95c00d83f5e7d90cf82e356a56 Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 9 Sep 2020 10:41:36 +0200 Subject: [PATCH 003/375] Added include fixits for C warnings beginning with: implicitly declaring library function --- src/source_clang.cpp | 53 ++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/src/source_clang.cpp b/src/source_clang.cpp index 1d72a35..ec643d9 100644 --- a/src/source_clang.cpp +++ b/src/source_clang.cpp @@ -390,12 +390,13 @@ void Source::ClangViewParse::update_diagnostics() { diagnostic.fix_its.emplace_back(clangmm::Diagnostic::FixIt{"#include <" + *cpp_header + ">\n", file_path.string(), get_new_include_offsets()}); } }; - if(diagnostic.fix_its.empty() && diagnostic.severity >= clangmm::Diagnostic::Severity::Error) { + if(diagnostic.fix_its.empty() && diagnostic.severity >= clangmm::Diagnostic::Severity::Warning) { for(size_t c = 0; c < clang_tokens->size(); c++) { auto &token = (*clang_tokens)[c]; auto &token_offsets = clang_tokens_offsets[c]; if(static_cast(line) == token_offsets.first.line - 1 && static_cast(index) >= token_offsets.first.index - 1 && static_cast(index) <= token_offsets.second.index - 1) { - if(starts_with(diagnostic.spelling, "implicit instantiation of undefined template")) { + if(diagnostic.severity >= clangmm::Diagnostic::Severity::Error && + starts_with(diagnostic.spelling, "implicit instantiation of undefined template")) { size_t start = 44 + 2; if(start < diagnostic.spelling.size()) { auto end = diagnostic.spelling.find('<', start); @@ -417,30 +418,34 @@ void Source::ClangViewParse::update_diagnostics() { } } } - if(is_token_char(*start) && token.get_kind() == clangmm::Token::Kind::Identifier && - (starts_with(diagnostic.spelling, "unknown type name") || - starts_with(diagnostic.spelling, "no type named") || - starts_with(diagnostic.spelling, "no member named") || - starts_with(diagnostic.spelling, "no template named") || - starts_with(diagnostic.spelling, "use of undeclared identifier") || - starts_with(diagnostic.spelling, "implicit instantiation of undefined template") || - starts_with(diagnostic.spelling, "no viable constructor or deduction guide for deduction of template arguments of"))) { - auto token_string = get_token(start); - bool has_std = false; - if(is_cpp) { - if(token_string == "std" && c + 2 < clang_tokens->size() && (*clang_tokens)[c + 2].get_kind() == clangmm::Token::Kind::Identifier) { - token_string = (*clang_tokens)[c + 2].get_spelling(); - has_std = true; + if(is_token_char(*start) && token.get_kind() == clangmm::Token::Kind::Identifier) { + if(diagnostic.severity >= clangmm::Diagnostic::Severity::Error && + (starts_with(diagnostic.spelling, "unknown type name") || + starts_with(diagnostic.spelling, "no type named") || + starts_with(diagnostic.spelling, "no member named") || + starts_with(diagnostic.spelling, "no template named") || + starts_with(diagnostic.spelling, "use of undeclared identifier") || + starts_with(diagnostic.spelling, "implicit instantiation of undefined template") || + starts_with(diagnostic.spelling, "no viable constructor or deduction guide for deduction of template arguments of"))) { + auto token_string = get_token(start); + bool has_std = false; + if(is_cpp) { + if(token_string == "std" && c + 2 < clang_tokens->size() && (*clang_tokens)[c + 2].get_kind() == clangmm::Token::Kind::Identifier) { + token_string = (*clang_tokens)[c + 2].get_spelling(); + has_std = true; + } + else if(c >= 2 && + (*clang_tokens)[c - 1].get_kind() == clangmm::Token::Punctuation && + (*clang_tokens)[c - 2].get_kind() == clangmm::Token::Identifier && + (*clang_tokens)[c - 1].get_spelling() == "::" && + (*clang_tokens)[c - 2].get_spelling() == "std") + has_std = true; } - else if(c >= 2 && - (*clang_tokens)[c - 1].get_kind() == clangmm::Token::Punctuation && - (*clang_tokens)[c - 2].get_kind() == clangmm::Token::Identifier && - (*clang_tokens)[c - 1].get_spelling() == "::" && - (*clang_tokens)[c - 2].get_spelling() == "std") - has_std = true; + add_include_fixit(has_std, is_cpp && has_using_namespace_std(c), token_string); } - - add_include_fixit(has_std, is_cpp && has_using_namespace_std(c), token_string); + else if(diagnostic.severity >= clangmm::Diagnostic::Severity::Warning && is_c && + starts_with(diagnostic.spelling, "implicitly declaring library function")) + add_include_fixit(false, false, get_token(start)); } break; } From 161b26aefafc3f7f07611f7632a621ebace973f9 Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 9 Sep 2020 09:55:47 +0200 Subject: [PATCH 004/375] Cleanup of language client json parsing code --- src/source_language_protocol.cpp | 97 ++++++++++++++------------------ 1 file changed, 41 insertions(+), 56 deletions(-) diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index cf49a3d..8c0a5dc 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -171,31 +171,30 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang }, "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()) { + if(auto capabilities_pt = result.get_child_optional("capabilities")) { try { - capabilities.text_document_sync = static_cast(capabilities_pt->second.get("textDocumentSync")); + capabilities.text_document_sync = static_cast(capabilities_pt->get("textDocumentSync")); } catch(...) { - capabilities.text_document_sync = static_cast(capabilities_pt->second.get("textDocumentSync.change", 0)); + capabilities.text_document_sync = static_cast(capabilities_pt->get("textDocumentSync.change", 0)); } - capabilities.hover = capabilities_pt->second.get("hoverProvider", false); - capabilities.completion = capabilities_pt->second.find("completionProvider") != capabilities_pt->second.not_found() ? true : false; - capabilities.signature_help = capabilities_pt->second.find("signatureHelpProvider") != capabilities_pt->second.not_found() ? true : false; - capabilities.definition = capabilities_pt->second.get("definitionProvider", false); - capabilities.references = capabilities_pt->second.get("referencesProvider", false); - capabilities.document_highlight = capabilities_pt->second.get("documentHighlightProvider", false); - capabilities.workspace_symbol = capabilities_pt->second.get("workspaceSymbolProvider", false); - capabilities.document_symbol = capabilities_pt->second.get("documentSymbolProvider", false); - capabilities.document_formatting = capabilities_pt->second.get("documentFormattingProvider", false); - capabilities.document_range_formatting = capabilities_pt->second.get("documentRangeFormattingProvider", false); - capabilities.rename = capabilities_pt->second.get("renameProvider", false); + capabilities.hover = capabilities_pt->get("hoverProvider", false); + capabilities.completion = static_cast(capabilities_pt->get_child_optional("completionProvider")); + capabilities.signature_help = static_cast(capabilities_pt->get_child_optional("signatureHelpProvider")); + capabilities.definition = capabilities_pt->get("definitionProvider", false); + capabilities.references = capabilities_pt->get("referencesProvider", false); + capabilities.document_highlight = capabilities_pt->get("documentHighlightProvider", false); + capabilities.workspace_symbol = capabilities_pt->get("workspaceSymbolProvider", false); + capabilities.document_symbol = capabilities_pt->get("documentSymbolProvider", false); + capabilities.document_formatting = capabilities_pt->get("documentFormattingProvider", false); + capabilities.document_range_formatting = capabilities_pt->get("documentRangeFormattingProvider", false); + capabilities.rename = capabilities_pt->get("renameProvider", false); if(!capabilities.rename) - capabilities.rename = capabilities_pt->second.get("renameProvider.prepareProvider", false); - capabilities.code_action = capabilities_pt->second.get("codeActionProvider", false); + capabilities.rename = capabilities_pt->get("renameProvider.prepareProvider", false); + capabilities.code_action = capabilities_pt->get("codeActionProvider", false); if(!capabilities.code_action) - capabilities.code_action = static_cast(capabilities_pt->second.get_child_optional("codeActionProvider.codeActionKinds")); - capabilities.type_coverage = capabilities_pt->second.get("typeCoverageProvider", false); + capabilities.code_action = static_cast(capabilities_pt->get_child_optional("codeActionProvider.codeActionKinds")); + capabilities.type_coverage = capabilities_pt->get("typeCoverageProvider", false); } write_notification("initialized", ""); @@ -274,23 +273,21 @@ void LanguageProtocol::Client::parse_server_message() { } auto message_id = pt.get_optional("id"); - auto result_it = pt.find("result"); - auto error_it = pt.find("error"); { LockGuard lock(read_write_mutex); - if(result_it != pt.not_found()) { + if(auto result = pt.get_child_optional("result")) { 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); lock.unlock(); - function(result_it->second, false); + function(*result, false); lock.lock(); } } } - else if(error_it != pt.not_found()) { + else if(auto error = pt.get_child_optional("error")) { if(!Config::get().log.language_server) boost::property_tree::write_json(std::cerr, pt); if(message_id) { @@ -299,23 +296,19 @@ void LanguageProtocol::Client::parse_server_message() { auto function = std::move(id_it->second.second); handlers.erase(id_it); lock.unlock(); - function(error_it->second, true); + function(*error, 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(); - if(message_id) - handle_server_request(*message_id, method_it->second.get_value(""), params_it->second); - else - handle_server_notification(method_it->second.get_value(""), params_it->second); - lock.lock(); - } + else if(auto method = pt.get_optional("method")) { + if(auto params = pt.get_child_optional("params")) { + lock.unlock(); + if(message_id) + handle_server_request(*message_id, *method, *params); + else + handle_server_notification(*method, *params); + lock.lock(); } } } @@ -754,9 +747,8 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { 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) { + if(auto changes_pt = result.get_child_optional("changes")) { + for(auto file_it = changes_pt->begin(); file_it != changes_pt->end(); ++file_it) { auto file = file_it->first; file.erase(0, 7); if(filesystem::file_in_path(file, project_path)) { @@ -767,12 +759,10 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { } } } - 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 file = filesystem::get_path_from_uri(document_it->second.get("uri", "")); + else if(auto changes_pt = result.get_child_optional("documentChanges")) { + for(auto change_it = changes_pt->begin(); change_it != changes_pt->end(); ++change_it) { + if(auto document = change_it->second.get_child_optional("textDocument")) { + auto file = filesystem::get_path_from_uri(document->get("uri", "")); if(filesystem::file_in_path(file, project_path)) { std::vector edits; auto edits_pt = change_it->second.get_child("edits", boost::property_tree::ptree()); @@ -916,8 +906,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { if(kind == 6 || kind == 9 || kind == 12) { std::unique_ptr range; std::string prefix; - auto location_pt = it->second.get_child_optional("location"); - if(location_pt) { + if(auto location_pt = it->second.get_child_optional("location")) { LanguageProtocol::Location location(*location_pt); range = std::make_unique(location.range); std::string container = it->second.get("containerName", ""); @@ -933,8 +922,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { } methods.emplace_back(Offset(range->start.line, range->start.character), (!prefix.empty() ? Glib::Markup::escape_text(prefix) + ':' : "") + std::to_string(range->start.line + 1) + ": " + "" + Glib::Markup::escape_text(it->second.get("name")) + ""); } - auto children = it->second.get_child_optional("children"); - if(children) + if(auto children = it->second.get_child_optional("children")) parse_result(*children, (!container.empty() ? container + "::" : "") + it->second.get("name")); } catch(...) { @@ -1024,8 +1012,7 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vectorsecond.get("kind") == "quickfix") { auto title = it->second.get("title"); std::vector quickfix_diagnostics; - auto diagnostics_pt = it->second.get_child_optional("diagnostics"); - if(diagnostics_pt) { + if(auto diagnostics_pt = it->second.get_child_optional("diagnostics")) { for(auto it = diagnostics_pt->begin(); it != diagnostics_pt->end(); ++it) quickfix_diagnostics.emplace_back(it->second); } @@ -1681,8 +1668,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { client->write_request(this, "textDocument/completion", R"("textDocument":{"uri":")" + uri + R"("}, "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) { boost::property_tree::ptree::const_iterator begin, end; - auto items = result.get_child_optional("items"); - if(items) { + if(auto items = result.get_child_optional("items")) { begin = items->begin(); end = items->end(); } @@ -1702,8 +1688,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { auto documentation = it->second.get("documentation", ""); std::string documentation_kind; if(documentation.empty()) { - auto documentation_pt = it->second.get_child_optional("documentation"); - if(documentation_pt) { + if(auto documentation_pt = it->second.get_child_optional("documentation")) { documentation = documentation_pt->get("value", ""); documentation_kind = documentation_pt->get("kind", ""); } From 7e44c266159efc16d360841b5fcc8c98acfbc559 Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 9 Sep 2020 17:25:21 +0200 Subject: [PATCH 005/375] Fix CI failure --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d0882b7..041ec55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,7 @@ if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") add_compile_options(-Wno-cpp) elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options("-Wno-#warnings") - add_compile_options(-Wthread-safety) + add_compile_options(-Wthread-safety -Wno-deprecated) endif() if(APPLE) From 64b67eeddeaab2fa0d6f7597320bbdeaf6f3038a Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 9 Sep 2020 14:17:10 +0200 Subject: [PATCH 006/375] Can now paste in terminal --- src/source_base.cpp | 6 ++--- src/terminal.cpp | 57 +++++++++++++++++++++++++++++++++++++++++---- src/terminal.hpp | 2 ++ src/window.cpp | 6 +++-- 4 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/source_base.cpp b/src/source_base.cpp index 371c0bc..62e41b0 100644 --- a/src/source_base.cpp +++ b/src/source_base.cpp @@ -797,7 +797,7 @@ void Source::BaseView::paste() { std::string text = Gtk::Clipboard::get()->wait_for_text(); - //Replace carriage returns (which leads to crash) with newlines + // 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') @@ -807,7 +807,7 @@ void Source::BaseView::paste() { } } - //Exception for when pasted text is only whitespaces + // Exception for when pasted text is only whitespaces bool only_whitespaces = true; for(auto &chr : text) { if(chr != '\n' && chr != '\r' && chr != ' ' && chr != '\t') { @@ -926,7 +926,7 @@ void Source::BaseView::paste() { paste_line = false; } } - // add final newline if present in text + // Add final newline if present in text if(text.size() > 0 && text.back() == '\n') get_buffer()->insert_at_cursor('\n' + prefix_tabs); get_buffer()->end_user_action(); diff --git a/src/terminal.cpp b/src/terminal.cpp index c73c545..21a2ed7 100644 --- a/src/terminal.cpp +++ b/src/terminal.cpp @@ -584,11 +584,15 @@ bool Terminal::on_key_press_event(GdkEventKey *event) { if(processes.size() > 0 || debug_is_running) { auto unicode = gdk_keyval_to_unicode(event->keyval); if(unicode >= 32 && unicode != 126 && unicode != 0) { + if(scroll_to_bottom) + scroll_to_bottom(); get_buffer()->place_cursor(get_buffer()->end()); stdin_buffer += unicode; - get_buffer()->insert_at_cursor(stdin_buffer.substr(stdin_buffer.size() - 1)); + get_buffer()->insert_at_cursor(Glib::ustring() + unicode); } else if(event->keyval == GDK_KEY_BackSpace) { + if(scroll_to_bottom) + scroll_to_bottom(); get_buffer()->place_cursor(get_buffer()->end()); if(stdin_buffer.size() > 0 && get_buffer()->get_char_count() > 0) { auto iter = get_buffer()->end(); @@ -598,18 +602,63 @@ bool Terminal::on_key_press_event(GdkEventKey *event) { } } else if(event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) { + if(scroll_to_bottom) + scroll_to_bottom(); get_buffer()->place_cursor(get_buffer()->end()); stdin_buffer += '\n'; + get_buffer()->insert_at_cursor("\n"); if(debug_is_running) { #ifdef JUCI_ENABLE_DEBUG - Project::current->debug_write(stdin_buffer); + Project::current->debug_write(stdin_buffer.raw()); #endif } else - processes.back()->write(stdin_buffer); - get_buffer()->insert_at_cursor(stdin_buffer.substr(stdin_buffer.size() - 1)); + processes.back()->write(stdin_buffer.raw()); stdin_buffer.clear(); } } return true; } + +void Terminal::paste() { + std::string text = Gtk::Clipboard::get()->wait_for_text(); + if(text.empty()) + 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"); + } + } + + std::string after_last_newline_str; + auto last_newline = text.rfind('\n'); + + LockGuard lock(processes_mutex); + bool debug_is_running = false; +#ifdef JUCI_ENABLE_DEBUG + debug_is_running = Project::current ? Project::current->debug_is_running() : false; +#endif + if(processes.size() > 0 || debug_is_running) { + if(scroll_to_bottom) + scroll_to_bottom(); + get_buffer()->place_cursor(get_buffer()->end()); + get_buffer()->insert_at_cursor(text); + if(last_newline != std::string::npos) { + if(debug_is_running) { +#ifdef JUCI_ENABLE_DEBUG + Project::current->debug_write(stdin_buffer.raw() + text.substr(0, last_newline + 1)); +#endif + } + else + processes.back()->write(stdin_buffer.raw() + text.substr(0, last_newline + 1)); + stdin_buffer = text.substr(last_newline + 1); + } + else + stdin_buffer += text; + } +} diff --git a/src/terminal.hpp b/src/terminal.hpp index 41d0386..0269fb9 100644 --- a/src/terminal.hpp +++ b/src/terminal.hpp @@ -36,6 +36,8 @@ public: std::function scroll_to_bottom; + void paste(); + protected: bool on_motion_notify_event(GdkEventMotion *motion_event) override; bool on_button_press_event(GdkEventButton *button_event) override; diff --git a/src/window.cpp b/src/window.cpp index ed73397..3fac2d9 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -683,13 +683,15 @@ void Window::set_menu_actions() { if(auto entry = dynamic_cast(widget)) entry->paste_clipboard(); else if(auto view = dynamic_cast(widget)) { - auto source_view = dynamic_cast(view); - if(source_view) { + if(auto source_view = dynamic_cast(view)) { source_view->disable_spellcheck = true; source_view->paste(); source_view->disable_spellcheck = false; source_view->hide_tooltips(); } + else if(auto terminal = dynamic_cast(view)) { + terminal->paste(); + } else if(view->get_editable()) view->get_buffer()->paste_clipboard(Gtk::Clipboard::get()); } From 19887dff276bcfe42200063e9975b1a43ac1ac6a Mon Sep 17 00:00:00 2001 From: eidheim Date: Thu, 10 Sep 2020 09:19:22 +0200 Subject: [PATCH 007/375] Fixes #438: improved detection and handling of closing curly bracket in special circumstances --- src/source.cpp | 10 ++++++++++ tests/source_key_test.cpp | 14 ++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/source.cpp b/src/source.cpp index f7bc78e..b7f0cc6 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -2542,6 +2542,16 @@ bool Source::View::on_key_press_event_bracket_language(GdkEventKey *event) { } } } + /** + * Handle pressing enter after '{' in special expressions like: + * enum class A { a, + * b } + */ + else if(close_iter.get_line() > condition_iter.get_line() && close_iter.get_line_offset() > condition_iter.get_line_offset()) { + get_buffer()->insert_at_cursor('\n' + get_line_before(get_tabs_end_iter(condition_iter.get_line() + 1))); + scroll_to(get_buffer()->get_insert()); + return true; + } } // Check if one should add semicolon after '}' diff --git a/tests/source_key_test.cpp b/tests/source_key_test.cpp index 6e8bedf..51f63ae 100644 --- a/tests/source_key_test.cpp +++ b/tests/source_key_test.cpp @@ -588,6 +588,20 @@ int main() { g_assert(buffer->get_insert()->get_iter().get_line_offset() == 4); } + { + buffer->set_text(" enum class A { a,\n" + " b };"); + auto iter = buffer->begin(); + iter.forward_chars(16); + buffer->place_cursor(iter); + view.on_key_press_event(&event); + g_assert(buffer->get_text() == " enum class A {\n" + " a,\n" + " b };"); + g_assert(buffer->get_insert()->get_iter().get_line() == 1); + g_assert(buffer->get_insert()->get_iter().get_line_offset() == 17); + } + { buffer->set_text(" else"); view.on_key_press_event(&event); From f3d76e08ae93e9cac0c1f6de182a8fc11a671195 Mon Sep 17 00:00:00 2001 From: eidheim Date: Thu, 10 Sep 2020 10:07:50 +0200 Subject: [PATCH 008/375] Improved handling of enter after { at beginning of lambda expressions that are arguments --- src/source.cpp | 12 ++++++++++++ tests/source_key_test.cpp | 16 ++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/source.cpp b/src/source.cpp index b7f0cc6..1015bc9 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -2552,6 +2552,18 @@ bool Source::View::on_key_press_event_bracket_language(GdkEventKey *event) { scroll_to(get_buffer()->get_insert()); return true; } + /** + * Handle pressing enter after '{' in special expressions like: + * test(2, + * []() { + * func(); + * }); + */ + else if(close_iter.get_line() > condition_iter.get_line() && close_iter.get_line_offset() == get_tabs_end_iter(condition_iter).get_line_offset()) { + get_buffer()->insert_at_cursor('\n' + get_line_before(get_tabs_end_iter(condition_iter)) + tab); + scroll_to(get_buffer()->get_insert()); + return true; + } } // Check if one should add semicolon after '}' diff --git a/tests/source_key_test.cpp b/tests/source_key_test.cpp index 51f63ae..f1e8b01 100644 --- a/tests/source_key_test.cpp +++ b/tests/source_key_test.cpp @@ -601,6 +601,22 @@ int main() { g_assert(buffer->get_insert()->get_iter().get_line() == 1); g_assert(buffer->get_insert()->get_iter().get_line_offset() == 17); } + { + buffer->set_text(" test(2,\n" + " []() {\n" + " func();\n" + " })\n"); + auto iter = buffer->get_iter_at_line_offset(1, 13); + buffer->place_cursor(iter); + view.on_key_press_event(&event); + g_assert(buffer->get_text() == " test(2,\n" + " []() {\n" + " \n" + " func();\n" + " })\n"); + g_assert(buffer->get_insert()->get_iter().get_line() == 2); + g_assert(buffer->get_insert()->get_iter().get_line_offset() == 9); + } { buffer->set_text(" else"); From 4a76c6114fb3cdfe58e17a47ad92163b2c02ece7 Mon Sep 17 00:00:00 2001 From: eidheim Date: Thu, 10 Sep 2020 10:59:43 +0200 Subject: [PATCH 009/375] No longer adds semicolon after lambda expressions that are passed as arguments --- src/source.cpp | 8 ++++++-- tests/source_key_test.cpp | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/source.cpp b/src/source.cpp index 1015bc9..bc58f49 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -2573,7 +2573,7 @@ bool Source::View::on_key_press_event_bracket_language(GdkEventKey *event) { auto token = get_token(tabs_end_iter); if(token == "class" || token == "struct") add_semicolon = true; - // Add semicolon after lambda unless it's a parameter + // Add semicolon after lambda unless it's an argument else if(*start_iter != '(' && *start_iter != '{' && *start_iter != '[') { auto it = condition_iter; long para_count = 0; @@ -2593,7 +2593,11 @@ bool Source::View::on_key_press_event_bracket_language(GdkEventKey *event) { ++para_count; if(square_outside_para_found && square_count == 0 && para_count == 0) { - add_semicolon = true; + // Look for equal sign + while(it.backward_char() && (*it == ' ' || *it == '\t' || it.ends_line())) { + } + if(*it == '=') + add_semicolon = true; break; } if(it == start_iter) diff --git a/tests/source_key_test.cpp b/tests/source_key_test.cpp index f1e8b01..8bb16ca 100644 --- a/tests/source_key_test.cpp +++ b/tests/source_key_test.cpp @@ -1123,6 +1123,20 @@ int main() { g_assert(buffer->get_insert()->get_iter().get_line() == 1); g_assert(buffer->get_insert()->get_iter().get_line_offset() == 4); } + { + buffer->set_text(" test(\n" + " [] {});"); + auto iter = buffer->end(); + iter.backward_chars(3); + buffer->place_cursor(iter); + view.on_key_press_event(&event); + g_assert(buffer->get_text() == " test(\n" + " [] {\n" + " \n" + " });"); + g_assert(buffer->get_insert()->get_iter().get_line() == 2); + g_assert(buffer->get_insert()->get_iter().get_line_offset() == 8); + } { buffer->set_text(" void Class::Class()\n" From bca93a62dc511b0c925590d294b0ddf1c8fc5739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Sverre=20Lien=20Sell=C3=A6g?= Date: Wed, 9 Sep 2020 14:39:14 +0200 Subject: [PATCH 010/375] use correct format on certain parts --- src/autocomplete.hpp | 7 +- src/directories.hpp | 5 +- src/git.cpp | 107 ++++++++++++++++--------------- src/notebook.hpp | 8 ++- src/source.cpp | 7 +- src/source.hpp | 6 +- src/source_clang.cpp | 107 ++++++++++++++++--------------- src/source_clang.hpp | 14 +++- src/source_diff.hpp | 8 ++- src/source_language_protocol.cpp | 77 +++++++++++----------- src/terminal.cpp | 37 +++++------ src/usages_clang.cpp | 78 ++++++++++++---------- src/window.cpp | 77 ++++++++++++---------- tests/filesystem_test.cpp | 2 +- tests/process_test.cpp | 23 +++---- tests/terminal_test.cpp | 16 +++-- 16 files changed, 322 insertions(+), 257 deletions(-) diff --git a/src/autocomplete.hpp b/src/autocomplete.hpp index 55a14bb..353d876 100644 --- a/src/autocomplete.hpp +++ b/src/autocomplete.hpp @@ -16,7 +16,12 @@ class Autocomplete { Dispatcher dispatcher; public: - enum class State { idle, starting, restarting, canceled }; + enum class State { + idle, + starting, + restarting, + canceled + }; Mutex prefix_mutex; Glib::ustring prefix GUARDED_BY(prefix_mutex); diff --git a/src/directories.hpp b/src/directories.hpp index a297f6f..a06d7db 100644 --- a/src/directories.hpp +++ b/src/directories.hpp @@ -18,7 +18,10 @@ class Directories : public Gtk::ListViewText { std::shared_ptr connection; }; - enum class PathType { known, unknown }; + enum class PathType { + known, + unknown + }; class TreeStore : public Gtk::TreeStore { protected: diff --git a/src/git.cpp b/src/git.cpp index c1cd09d..26a6da3 100644 --- a/src/git.cpp +++ b/src/git.cpp @@ -36,20 +36,21 @@ Git::Repository::Diff::Diff(const boost::filesystem::path &path, git_repository Git::Repository::Diff::Lines Git::Repository::Diff::get_lines(const std::string &buffer) { Lines lines; LockGuard lock(mutex); - error.code = git_diff_blob_to_buffer(blob.get(), nullptr, buffer.c_str(), buffer.size(), nullptr, &options, nullptr, nullptr, [](const git_diff_delta *delta, const git_diff_hunk *hunk, void *payload) { - //Based on https://github.com/atom/git-diff/blob/master/lib/git-diff-view.coffee - auto lines = static_cast(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; - }, nullptr, &lines); + error.code = git_diff_blob_to_buffer( + blob.get(), nullptr, buffer.c_str(), buffer.size(), nullptr, &options, nullptr, nullptr, [](const git_diff_delta *delta, const git_diff_hunk *hunk, void *payload) { + //Based on https://github.com/atom/git-diff/blob/master/lib/git-diff-view.coffee + auto lines = static_cast(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; + }, + nullptr, &lines); if(error) throw std::runtime_error(error.message()); return lines; @@ -62,11 +63,13 @@ std::vector Git::Repository::Diff::get_hunks(const 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 *>(payload); - hunks->emplace_back(hunk->old_start, hunk->old_lines, hunk->new_start, hunk->new_lines); - return 0; - }, nullptr, &hunks); + 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 *>(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; @@ -76,18 +79,20 @@ std::string Git::Repository::Diff::get_details(const std::string &buffer, int li std::pair details; details.second = line_nr; LockGuard lock(mutex); - error.code = git_diff_blob_to_buffer(blob.get(), nullptr, buffer.c_str(), buffer.size(), nullptr, &options, nullptr, nullptr, nullptr, [](const git_diff_delta *delta, const git_diff_hunk *hunk, const git_diff_line *line, void *payload) { - auto details = static_cast *>(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; - }, &details); + error.code = git_diff_blob_to_buffer( + blob.get(), nullptr, buffer.c_str(), buffer.size(), nullptr, &options, nullptr, nullptr, nullptr, [](const git_diff_delta *delta, const git_diff_hunk *hunk, const git_diff_line *line, void *payload) { + auto details = static_cast *>(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; + }, + &details); if(error) throw std::runtime_error(error.message()); return details.first; @@ -138,27 +143,27 @@ Git::Repository::Status Git::Repository::get_status() { Data data{work_path}; { LockGuard lock(mutex); - error.code = git_status_foreach(repository.get(), [](const char *path, unsigned int status_flags, void *payload) { - auto data = static_cast(payload); - - bool new_ = false; - bool modified = false; - if((status_flags & (GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_NEW)) > 0) - new_ = true; - else if((status_flags & (GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED)) > 0) - modified = true; - - boost::filesystem::path rel_path(path); - do { - if(new_) - data->status.added.emplace((data->work_path / rel_path).generic_string()); - else if(modified) - data->status.modified.emplace((data->work_path / rel_path).generic_string()); - rel_path = rel_path.parent_path(); - } while(!rel_path.empty()); + error.code = git_status_foreach( + repository.get(), [](const char *path, unsigned int status_flags, void *payload) { + auto data = static_cast(payload); + bool new_ = false; + bool modified = false; + if((status_flags & (GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_NEW)) > 0) + new_ = true; + else if((status_flags & (GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED)) > 0) + modified = true; + boost::filesystem::path rel_path(path); + do { + if(new_) + data->status.added.emplace((data->work_path / rel_path).generic_string()); + else if(modified) + data->status.modified.emplace((data->work_path / rel_path).generic_string()); + rel_path = rel_path.parent_path(); + } while(!rel_path.empty()); - return 0; - }, &data); + return 0; + }, + &data); if(error) throw std::runtime_error(error.message()); diff --git a/src/notebook.hpp b/src/notebook.hpp index 9b6394c..0e4cf20 100644 --- a/src/notebook.hpp +++ b/src/notebook.hpp @@ -37,8 +37,12 @@ public: Source::View *get_view(size_t index); Source::View *get_current_view(); std::vector &get_views(); - - enum class Position { left, right, infer, split }; + enum class Position { + left, + right, + infer, + split + }; bool open(Source::View *view); bool open(const boost::filesystem::path &file_path, Position position = Position::infer); void open_uri(const std::string &uri); diff --git a/src/source.cpp b/src/source.cpp index bc58f49..6ee6d23 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -203,6 +203,7 @@ Source::View::View(const boost::filesystem::path &file_path, const Glib::RefPtr< setup_signals(); setup_format_style(is_generic_view); + std::string comment_characters; if(is_bracket_language) comment_characters = "//"; @@ -375,8 +376,7 @@ Gsv::DrawSpacesFlags Source::View::parse_show_whitespace_characters(const std::s namespace qi = boost::spirit::qi; qi::symbols 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); + 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 out; @@ -2282,7 +2282,8 @@ bool Source::View::on_key_press_event_basic(GdkEventKey *event) { bool Source::View::on_key_press_event_bracket_language(GdkEventKey *event) { const static std::regex no_bracket_statement_regex("^[ \t]*(if( +constexpr)?|for|while) *\\(.*[^;}{] *$|" "^[ \t]*[}]? *else if( +constexpr)? *\\(.*[^;}{] *$|" - "^[ \t]*[}]? *else *$", std::regex::extended | std::regex::optimize); + "^[ \t]*[}]? *else *$", + std::regex::extended | std::regex::optimize); auto iter = get_buffer()->get_insert()->get_iter(); diff --git a/src/source.hpp b/src/source.hpp index c58a0b9..a6229e7 100644 --- a/src/source.hpp +++ b/src/source.hpp @@ -41,7 +41,11 @@ namespace Source { class FixIt { public: - enum class Type { insert, replace, erase }; + enum class Type { + insert, + replace, + erase + }; FixIt(std::string source_, std::string path_, std::pair offsets_); diff --git a/src/source_clang.cpp b/src/source_clang.cpp index ec643d9..f98c456 100644 --- a/src/source_clang.cpp +++ b/src/source_clang.cpp @@ -249,8 +249,7 @@ const std::map &Source::ClangViewParse::clang_types() { {46, "def:identifier"}, {109, "def:string"}, {702, "def:statement"}, - {705, "def:comment"} - }; + {705, "def:comment"}}; return types; } @@ -579,9 +578,7 @@ void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle) else if(type_description[pos] == '>') --angle_count; ++pos; - } - while(pos < type_description.size()) - ; + } while(pos < type_description.size()); } } tooltip.insert_code(type_description, language); @@ -647,29 +644,34 @@ void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle) VisitorData visitor_data{cursor.get_source_range().get_offsets(), cursor.get_spelling(), {}}; auto start_cursor = cursor; for(auto parent = cursor.get_semantic_parent(); parent.get_kind() != clangmm::Cursor::Kind::TranslationUnit && - parent.get_kind() != clangmm::Cursor::Kind::ClassDecl; parent = parent.get_semantic_parent()) + parent.get_kind() != clangmm::Cursor::Kind::ClassDecl; + parent = parent.get_semantic_parent()) start_cursor = parent; - clang_visitChildren(start_cursor.cx_cursor, [](CXCursor cx_cursor, CXCursor cx_parent, CXClientData data_) { - auto data = static_cast(data_); - if(clangmm::Cursor(cx_cursor).get_source_range().get_offsets() == data->offsets) { - auto parent = clangmm::Cursor(cx_parent); - if(parent.get_spelling() == data->spelling) { - data->parent = parent; - return CXChildVisit_Break; - } - } - return CXChildVisit_Recurse; - }, &visitor_data); + clang_visitChildren( + start_cursor.cx_cursor, [](CXCursor cx_cursor, CXCursor cx_parent, CXClientData data_) { + auto data = static_cast(data_); + if(clangmm::Cursor(cx_cursor).get_source_range().get_offsets() == data->offsets) { + auto parent = clangmm::Cursor(cx_parent); + if(parent.get_spelling() == data->spelling) { + data->parent = parent; + return CXChildVisit_Break; + } + } + return CXChildVisit_Recurse; + }, + &visitor_data); if(visitor_data.parent) cursor = visitor_data.parent; } // Check children std::vector children; - clang_visitChildren(cursor.cx_cursor, [](CXCursor cx_cursor, CXCursor /*parent*/, CXClientData data) { - static_cast *>(data)->emplace_back(cx_cursor); - return CXChildVisit_Continue; - }, &children); + clang_visitChildren( + cursor.cx_cursor, [](CXCursor cx_cursor, CXCursor /*parent*/, CXClientData data) { + static_cast *>(data)->emplace_back(cx_cursor); + return CXChildVisit_Continue; + }, + &children); // Check if expression can be called without altering state bool call_expression = true; @@ -1151,8 +1153,7 @@ const std::unordered_map &Source::ClangViewAutocomplet {"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"} - }; + {"dec(std::ios_base &__base)", "dec"}}; return map; } @@ -1317,37 +1318,41 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file 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(client_data_); - if(client_data->found_include.empty() && include_len > 0) { - auto source_location = clangmm::SourceLocation(inclusion_stack[0]); - if(static_cast(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); + clang_getInclusions( + clang_tu->cx_tu, [](CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData client_data_) { + auto client_data = static_cast(client_data_); + if(client_data->found_include.empty() && include_len > 0) { + auto source_location = clangmm::SourceLocation(inclusion_stack[0]); + if(static_cast(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(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(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(ends_with(included_file_str, client_data->sm_str) && - boost::filesystem::path(included_file_str).filename() == boost::filesystem::path(client_data->sm_str).filename()) { - client_data->found_include = included_file_str; - break; + clang_getInclusions( + clang_tu->cx_tu, [](CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData client_data_) { + auto client_data = static_cast(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(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(ends_with(included_file_str, client_data->sm_str) && + boost::filesystem::path(included_file_str).filename() == boost::filesystem::path(client_data->sm_str).filename()) { + client_data->found_include = included_file_str; + break; + } + } } } - } - } - }, &client_data); + }, + &client_data); if(!client_data.found_include.empty()) return Offset(0, 0, client_data.found_include); @@ -1957,9 +1962,11 @@ bool Source::ClangViewRefactor::wait_parsing() { } if(!not_parsed_clang_views.empty()) { bool canceled = false; - auto message = std::make_unique("Please wait while all buffers finish parsing", [&canceled] { - canceled = true; - }, true); + auto message = std::make_unique( + "Please wait while all buffers finish parsing", [&canceled] { + canceled = true; + }, + true); ScopeGuard guard{[&message] { message->hide(); }}; diff --git a/src/source_clang.hpp b/src/source_clang.hpp index 31420db..95b03be 100644 --- a/src/source_clang.hpp +++ b/src/source_clang.hpp @@ -13,8 +13,18 @@ namespace Source { class ClangViewParse : public View { protected: - enum class ParseState { processing, restarting, stop }; - enum class ParseProcessState { idle, starting, preprocessing, processing, postprocessing }; + 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 &language); diff --git a/src/source_diff.hpp b/src/source_diff.hpp index 4d6027a..80a39b3 100644 --- a/src/source_diff.hpp +++ b/src/source_diff.hpp @@ -11,7 +11,13 @@ namespace Source { class DiffView : virtual public Source::BaseView { - enum class ParseState { idle, starting, preprocessing, processing, postprocessing }; + enum class ParseState { + idle, + starting, + preprocessing, + processing, + postprocessing + }; class Renderer : public Gsv::GutterRenderer { public: diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 8c0a5dc..085098d 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -50,12 +50,10 @@ LanguageProtocol::Diagnostic::Diagnostic(const boost::property_tree::ptree &pt) LanguageProtocol::TextEdit::TextEdit(const boost::property_tree::ptree &pt, std::string new_text_) : range(pt.get_child("range")), new_text(new_text_.empty() ? pt.get("newText") : std::move(new_text_)) {} LanguageProtocol::Client::Client(boost::filesystem::path root_path_, std::string language_id_) : root_path(std::move(root_path_)), language_id(std::move(language_id_)) { - process = std::make_unique(filesystem::escape_argument(language_id + "-language-server"), root_path.string(), [this](const char *bytes, size_t n) { + process = std::make_unique( + filesystem::escape_argument(language_id + "-language-server"), root_path.string(), [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, TinyProcessLib::Config{1048576}); + parse_server_message(); }, [](const char *bytes, size_t n) { std::cerr.write(bytes, n); }, true, TinyProcessLib::Config{1048576}); } std::shared_ptr LanguageProtocol::Client::get(const boost::filesystem::path &file_path, const std::string &language_id) { @@ -169,38 +167,39 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang "initializationOptions": { "checkOnSave": { "enable": true } }, -"trace": "off")", [this, &result_processed](const boost::property_tree::ptree &result, bool error) { - if(!error) { - if(auto capabilities_pt = result.get_child_optional("capabilities")) { - try { - capabilities.text_document_sync = static_cast(capabilities_pt->get("textDocumentSync")); - } - catch(...) { - capabilities.text_document_sync = static_cast(capabilities_pt->get("textDocumentSync.change", 0)); - } - capabilities.hover = capabilities_pt->get("hoverProvider", false); - capabilities.completion = static_cast(capabilities_pt->get_child_optional("completionProvider")); - capabilities.signature_help = static_cast(capabilities_pt->get_child_optional("signatureHelpProvider")); - capabilities.definition = capabilities_pt->get("definitionProvider", false); - capabilities.references = capabilities_pt->get("referencesProvider", false); - capabilities.document_highlight = capabilities_pt->get("documentHighlightProvider", false); - capabilities.workspace_symbol = capabilities_pt->get("workspaceSymbolProvider", false); - capabilities.document_symbol = capabilities_pt->get("documentSymbolProvider", false); - capabilities.document_formatting = capabilities_pt->get("documentFormattingProvider", false); - capabilities.document_range_formatting = capabilities_pt->get("documentRangeFormattingProvider", false); - capabilities.rename = capabilities_pt->get("renameProvider", false); - if(!capabilities.rename) - capabilities.rename = capabilities_pt->get("renameProvider.prepareProvider", false); - capabilities.code_action = capabilities_pt->get("codeActionProvider", false); - if(!capabilities.code_action) - capabilities.code_action = static_cast(capabilities_pt->get_child_optional("codeActionProvider.codeActionKinds")); - capabilities.type_coverage = capabilities_pt->get("typeCoverageProvider", false); - } +"trace": "off")", + [this, &result_processed](const boost::property_tree::ptree &result, bool error) { + if(!error) { + if(auto capabilities_pt = result.get_child_optional("capabilities")) { + try { + capabilities.text_document_sync = static_cast(capabilities_pt->get("textDocumentSync")); + } + catch(...) { + capabilities.text_document_sync = static_cast(capabilities_pt->get("textDocumentSync.change", 0)); + } + capabilities.hover = capabilities_pt->get("hoverProvider", false); + capabilities.completion = static_cast(capabilities_pt->get_child_optional("completionProvider")); + capabilities.signature_help = static_cast(capabilities_pt->get_child_optional("signatureHelpProvider")); + capabilities.definition = capabilities_pt->get("definitionProvider", false); + capabilities.references = capabilities_pt->get("referencesProvider", false); + capabilities.document_highlight = capabilities_pt->get("documentHighlightProvider", false); + capabilities.workspace_symbol = capabilities_pt->get("workspaceSymbolProvider", false); + capabilities.document_symbol = capabilities_pt->get("documentSymbolProvider", false); + capabilities.document_formatting = capabilities_pt->get("documentFormattingProvider", false); + capabilities.document_range_formatting = capabilities_pt->get("documentRangeFormattingProvider", false); + capabilities.rename = capabilities_pt->get("renameProvider", false); + if(!capabilities.rename) + capabilities.rename = capabilities_pt->get("renameProvider.prepareProvider", false); + capabilities.code_action = capabilities_pt->get("codeActionProvider", false); + if(!capabilities.code_action) + capabilities.code_action = static_cast(capabilities_pt->get_child_optional("codeActionProvider.codeActionKinds")); + capabilities.type_coverage = capabilities_pt->get("typeCoverageProvider", false); + } - write_notification("initialized", ""); - } - result_processed.set_value(); - }); + write_notification("initialized", ""); + } + result_processed.set_value(); + }); result_processed.get_future().get(); initialized = true; @@ -1025,8 +1024,7 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vector{}); - pair.first->second.emplace_back(edit.new_text, filesystem::get_path_from_uri(file_it->first).string(), std::make_pair(Offset(edit.range.start.line, edit.range.start.character), - Offset(edit.range.end.line, edit.range.end.character))); + pair.first->second.emplace_back(edit.new_text, filesystem::get_path_from_uri(file_it->first).string(), std::make_pair(Offset(edit.range.start.line, edit.range.start.character), Offset(edit.range.end.line, edit.range.end.character))); break; } } @@ -1036,8 +1034,7 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vector{}); - pair.first->second.emplace_back(edit.new_text, filesystem::get_path_from_uri(file_it->first).string(), std::make_pair(Offset(edit.range.start.line, edit.range.start.character), - Offset(edit.range.end.line, edit.range.end.character))); + pair.first->second.emplace_back(edit.new_text, filesystem::get_path_from_uri(file_it->first).string(), std::make_pair(Offset(edit.range.start.line, edit.range.start.character), Offset(edit.range.end.line, edit.range.end.character))); break; } } diff --git a/src/terminal.cpp b/src/terminal.cpp index 21a2ed7..fe5d913 100644 --- a/src/terminal.cpp +++ b/src/terminal.cpp @@ -55,7 +55,12 @@ Terminal::Terminal() : Source::SearchView() { } }; class ParseAnsiEscapeSequence { - enum class State { none = 1, escaped, parameter_bytes, intermediate_bytes }; + enum class State { + none = 1, + escaped, + parameter_bytes, + intermediate_bytes + }; State state = State::none; std::string parameters; size_t length = 0; @@ -205,11 +210,8 @@ int Terminal::process(const std::string &command, const boost::filesystem::path std::unique_ptr process; if(use_pipes) - process = std::make_unique(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); - }); + process = std::make_unique( + 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(command, path.string()); @@ -225,7 +227,8 @@ int Terminal::process(std::istream &stdin_stream, std::ostream &stdout_stream, c if(scroll_to_bottom) scroll_to_bottom(); - TinyProcessLib::Process process(command, path.string(), [&stdout_stream](const char *bytes, size_t n) { + 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)) { @@ -233,13 +236,11 @@ int Terminal::process(std::istream &stdin_stream, std::ostream &stdout_stream, c next_char_iter++; umessage.replace(iter, next_char_iter, "?"); } - stdout_stream.write(umessage.data(), n); - }, [this, stderr_stream](const char *bytes, size_t n) { + 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); + async_print(std::string(bytes, n), true); }, true); if(process.get_id() <= 0) { async_print("\e[31mError\e[m: failed to run command: " + command + "\n", true); @@ -266,7 +267,8 @@ std::shared_ptr Terminal::async_process(const std::stri scroll_to_bottom(); stdin_buffer.clear(); - auto process = std::make_shared(command, path.string(), [this, quiet](const char *bytes, size_t n) { + auto process = std::make_shared( + command, path.string(), [this, quiet](const char *bytes, size_t n) { if(!quiet) { // Print stdout message sequentially to avoid the GUI becoming unresponsive std::promise message_printed; @@ -275,8 +277,7 @@ std::shared_ptr Terminal::async_process(const std::stri message_printed.set_value(); }); message_printed.get_future().get(); - } - }, [this, quiet](const char *bytes, size_t n) { + } }, [this, quiet](const char *bytes, size_t n) { if(!quiet) { // Print stderr message sequentially to avoid the GUI becoming unresponsive std::promise message_printed; @@ -285,8 +286,7 @@ std::shared_ptr Terminal::async_process(const std::stri message_printed.set_value(); }); message_printed.get_future().get(); - } - }, true); + } }, true); auto pid = process->get_id(); if(pid <= 0) { @@ -384,8 +384,9 @@ boost::optional Terminal::find_link(const std::string &line, siz size_t subs = (sub == 1 || sub == 1 + 4 + 3 + 3 || sub == 1 + 4 + 3 + 3 + 4 + 3 + 3 + 3 + 3 || - sub == 1 + 4 + 3 + 3 + 4 + 3 + 3 + 3 + 3 + 4) ? - 4 : 3; + sub == 1 + 4 + 3 + 3 + 4 + 3 + 3 + 3 + 3 + 4) + ? 4 + : 3; if(sm.length(sub + 1)) { auto start_pos = static_cast(sm.position(sub + 1) - sm.length(sub)); auto end_pos = static_cast(sm.position(sub + subs - 1) + sm.length(sub + subs - 1)); diff --git a/src/usages_clang.cpp b/src/usages_clang.cpp index cd0e44c..cb27cca 100644 --- a/src/usages_clang.cpp +++ b/src/usages_clang.cpp @@ -75,25 +75,27 @@ Usages::Clang::Cache::Cache(boost::filesystem::path project_path_, boost::filesy }; VisitorData visitor_data{this->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(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; + clang_getInclusions( + translation_unit->cx_tu, [](CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData data) { + auto visitor_data = static_cast(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> Usages::Clang::Cache::get_similar_token_offsets(clangmm::Cursor::Kind kind, const std::string &spelling, @@ -163,9 +165,11 @@ boost::optional> Usages::Clang::get_usages(co std::unique_ptr message; std::atomic canceled(false); auto create_message = [&canceled] { - return std::make_unique("Please wait while finding usages", [&canceled] { - canceled = true; - }, true); + return std::make_unique( + "Please wait while finding usages", [&canceled] { + canceled = true; + }, + true); }; size_t tasks = cache_in_progress_count; std::atomic tasks_completed = {0}; @@ -330,15 +334,17 @@ void Usages::Clang::cache(const boost::filesystem::path &project_path, const boo 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(data); + clang_visitChildren( + translation_unit_cursor, [](CXCursor cx_cursor, CXCursor cx_parent, CXClientData data) { + auto visitor_data = static_cast(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); @@ -528,15 +534,17 @@ void Usages::Clang::add_usages_from_includes(const boost::filesystem::path &proj 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(data); + clang_visitChildren( + translation_unit_cursor, [](CXCursor cx_cursor, CXCursor cx_parent, CXClientData data) { + auto visitor_data = static_cast(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); + 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); + 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); diff --git a/src/window.cpp b/src/window.cpp index 3fac2d9..9c62abd 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -45,7 +45,8 @@ Window::Window() { .juci_terminal_scrolledwindow {padding-left: 3px;} .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); @@ -1323,10 +1324,12 @@ void Window::set_menu_actions() { 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); + 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("Run Arguments"); EntryBox::get().buttons.emplace_back("Set Run Arguments", [entry_it]() { @@ -1379,20 +1382,22 @@ void Window::set_menu_actions() { 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.empty()) { - last_run_command = content; - auto directory_folder = Project::get_preferably_directory_folder(); - if(Config::get().terminal.clear_on_run_command) - Terminal::get().clear(); - Terminal::get().async_print("\e[2mRunning: " + content + "\e[m\n"); - - Terminal::get().async_process(content, directory_folder, [content](int exit_status) { - Terminal::get().async_print("\e[2m" + content + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); - }); - } - EntryBox::get().hide(); - }, 30); + EntryBox::get().entries.emplace_back( + last_run_command, [this](const std::string &content) { + if(!content.empty()) { + last_run_command = content; + auto directory_folder = Project::get_preferably_directory_folder(); + if(Config::get().terminal.clear_on_run_command) + Terminal::get().clear(); + Terminal::get().async_print("\e[2mRunning: " + content + "\e[m\n"); + + Terminal::get().async_process(content, directory_folder, [content](int exit_status) { + Terminal::get().async_print("\e[2m" + content + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\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]() { @@ -1422,10 +1427,12 @@ void Window::set_menu_actions() { 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); + 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 Run Arguments"); @@ -1491,17 +1498,19 @@ void Window::set_menu_actions() { }); 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.empty()) { - if(Project::current) { - if(Config::get().terminal.clear_on_run_command) - Terminal::get().clear(); - Project::current->debug_run_command(content); - } - last_run_debug_command = content; - } - EntryBox::get().hide(); - }, 30); + EntryBox::get().entries.emplace_back( + last_run_debug_command, [this](const std::string &content) { + if(!content.empty()) { + if(Project::current) { + if(Config::get().terminal.clear_on_run_command) + Terminal::get().clear(); + 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]() { diff --git a/tests/filesystem_test.cpp b/tests/filesystem_test.cpp index 6ffb164..166c852 100644 --- a/tests/filesystem_test.cpp +++ b/tests/filesystem_test.cpp @@ -91,4 +91,4 @@ int main() { g_assert(uri == "file:///ro%20ot/te%20st%C3%A6%C3%B8%C3%A5.txt"); g_assert(path == filesystem::get_path_from_uri(uri)); } -} \ No newline at end of file +} diff --git a/tests/process_test.cpp b/tests/process_test.cpp index 0a2f202..7acca6e 100644 --- a/tests/process_test.cpp +++ b/tests/process_test.cpp @@ -14,11 +14,8 @@ int main() { } { - TinyProcessLib::Process process("echo Test && ls an_incorrect_path", "", [output](const char *bytes, size_t n) { - *output += std::string(bytes, n); - }, [error](const char *bytes, size_t n) { - *error += std::string(bytes, n); - }); + TinyProcessLib::Process process( + "echo Test && ls an_incorrect_path", "", [output](const char *bytes, size_t n) { *output += std::string(bytes, n); }, [error](const char *bytes, size_t n) { *error += std::string(bytes, n); }); g_assert(process.get_exit_status() > 0); g_assert(output->substr(0, 4) == "Test"); g_assert(!error->empty()); @@ -27,9 +24,11 @@ int main() { } { - TinyProcessLib::Process process("bash", "", [output](const char *bytes, size_t n) { - *output += std::string(bytes, n); - }, nullptr, true); + TinyProcessLib::Process process( + "bash", "", [output](const char *bytes, size_t n) { + *output += std::string(bytes, n); + }, + nullptr, true); process.write("echo Test\n"); process.write("exit\n"); g_assert(process.get_exit_status() == 0); @@ -38,9 +37,11 @@ int main() { } { - TinyProcessLib::Process process("cat", "", [output](const char *bytes, size_t n) { - *output += std::string(bytes, n); - }, nullptr, true); + TinyProcessLib::Process process( + "cat", "", [output](const char *bytes, size_t n) { + *output += std::string(bytes, n); + }, + nullptr, true); process.write("Test\n"); process.close_stdin(); g_assert(process.get_exit_status() == 0); diff --git a/tests/terminal_test.cpp b/tests/terminal_test.cpp index 82fc365..3fbb686 100644 --- a/tests/terminal_test.cpp +++ b/tests/terminal_test.cpp @@ -238,9 +238,11 @@ int main() { { terminal.clear(); std::promise done; - terminal.async_process("echo test", "", [&done](int exit_status) { - done.set_value(exit_status); - }, true); + terminal.async_process( + "echo test", "", [&done](int exit_status) { + done.set_value(exit_status); + }, + true); auto future = done.get_future(); while(future.wait_for(std::chrono::milliseconds(10)) != std::future_status::ready) { while(Gtk::Main::events_pending()) @@ -269,9 +271,11 @@ int main() { { terminal.clear(); std::promise done; - terminal.async_process("testing_invalid_command", "", [&done](int exit_status) { - done.set_value(exit_status); - }, true); + terminal.async_process( + "testing_invalid_command", "", [&done](int exit_status) { + done.set_value(exit_status); + }, + true); auto future = done.get_future(); while(future.wait_for(std::chrono::milliseconds(10)) != std::future_status::ready) { while(Gtk::Main::events_pending()) From 946052c2a8c2b429eacceb3ea725288bd1c59a96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Sverre=20Lien=20Sell=C3=A6g?= Date: Wed, 9 Sep 2020 14:42:44 +0200 Subject: [PATCH 011/375] format raw strings --- src/files.hpp | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/files.hpp b/src/files.hpp index e1f08c9..410d04d 100644 --- a/src/files.hpp +++ b/src/files.hpp @@ -5,7 +5,8 @@ /// 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": "", @@ -19,18 +20,18 @@ const std::string default_config_file = R"RAW({ "style": "juci-light", "font_comment": "Use \"\" for default font, and for instance \"Monospace 12\" to also set size",)RAW" #ifdef __APPLE__ - R"RAW( + R"RAW( "font": "Menlo",)RAW" #else #ifdef _WIN32 - R"RAW( + R"RAW( "font": "Consolas",)RAW" #else - R"RAW( + R"RAW( "font": "Monospace",)RAW" #endif #endif - R"RAW( + 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", @@ -90,13 +91,13 @@ const std::string default_config_file = R"RAW({ "debug_build_path": "/debug", "cmake": {)RAW" #ifdef _WIN32 - R"RAW( + R"RAW( "command": "cmake -G\"MSYS Makefiles\"",)RAW" #else - R"RAW( + R"RAW( "command": "cmake",)RAW" #endif - R"RAW( + R"RAW( "compile_command": "cmake --build ." }, "meson": { @@ -107,13 +108,13 @@ const std::string default_config_file = R"RAW({ "default_build_management_system": "cmake", "save_on_compile_or_run": true,)RAW" #ifdef JUCI_USE_UCTAGS - R"RAW( + R"RAW( "ctags_command": "uctags",)RAW" #else - R"RAW( + R"RAW( "ctags_command": "ctags",)RAW" #endif - R"RAW( + R"RAW( "grep_command": "grep", "cargo_command": "cargo", "python_command": "python -u", @@ -193,26 +194,26 @@ const std::string default_config_file = R"RAW({ "debug_show_breakpoints": "b", "debug_goto_stop": "l",)RAW" #ifdef __linux - R"RAW( + R"RAW( "window_next_tab": "Tab", "window_previous_tab": "Tab",)RAW" #else - R"RAW( + R"RAW( "window_next_tab": "Right", "window_previous_tab": "Left",)RAW" #endif - R"RAW( + R"RAW( "window_goto_tab": "", "window_toggle_split": "", "window_split_source_buffer": "",)RAW" #ifdef __APPLE__ - R"RAW( + R"RAW( "window_toggle_full_screen": "f",)RAW" #else - R"RAW( + R"RAW( "window_toggle_full_screen": "F11",)RAW" #endif - R"RAW( + R"RAW( "window_toggle_tabs": "", "window_toggle_zen_mode": "", "window_clear_terminal": "" From 0a2f52f04462622865a4bb67fc6eb081e7baf8af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Sverre=20Lien=20Sell=C3=A6g?= Date: Wed, 9 Sep 2020 14:44:22 +0200 Subject: [PATCH 012/375] use if statement instead of ternary operator --- src/source.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/source.cpp b/src/source.cpp index 6ee6d23..b0a8f67 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -105,8 +105,13 @@ std::string Source::FixIt::string(BaseView &view) { std::string to_pos, text; if(type != Type::insert) { to_pos = std::to_string(offsets.second.line + 1) + ':' + std::to_string(offsets.second.index + 1); - text = in_current_view ? view.get_buffer()->get_text(view.get_iter_at_line_index(offsets.first.line, offsets.first.index), - view.get_iter_at_line_index(offsets.second.line, offsets.second.index)) : ""; + if(in_current_view) { + text = view.get_buffer()->get_text(view.get_iter_at_line_index(offsets.first.line, offsets.first.index), + view.get_iter_at_line_index(offsets.second.line, offsets.second.index)); + } + else { + text = ""; + } } if(type == Type::insert) From ead0a8c8b606cb7ba30e4bfacfdb86b54d5c77da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Sverre=20Lien=20Sell=C3=A6g?= Date: Wed, 9 Sep 2020 15:26:32 +0200 Subject: [PATCH 013/375] fix formatting in tooltips test --- tests/tooltips_test.cpp | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/tests/tooltips_test.cpp b/tests/tooltips_test.cpp index 7c5614f..ba10d53 100644 --- a/tests/tooltips_test.cpp +++ b/tests/tooltips_test.cpp @@ -592,7 +592,8 @@ int main() { * * @param timemillis is a number of milliseconds * passed since Jan 1, 1970. -*/)", true); +*/)", + true); g_assert(tooltip->buffer->get_text() == R"(Constructor that sets the time to a given value. Parameters: @@ -604,7 +605,8 @@ Parameters: * * * @return Whether @a test is at the end of a line. -*/)", true); +*/)", + true); g_assert(tooltip->buffer->get_text() == R"(Returns true if test points to the start. Test pointing to the \n of a \r\n pair will not. @@ -615,7 +617,8 @@ Returns Whether test is at the end of a line.)"); * test * \a test * test -*/)", true); +*/)", + true); g_assert(tooltip->buffer->get_text() == "test test test"); } { @@ -627,7 +630,8 @@ Returns Whether test is at the end of a line.)"); * * More testing * end -*/)", true); +*/)", + true); g_assert(tooltip->buffer->get_text() == R"(Testing - t - t2 @@ -645,7 +649,8 @@ More testing end)"); * * More testing * end - */)", true); + */)", + true); g_assert(tooltip->buffer->get_text() == R"(Testing - t @@ -663,7 +668,8 @@ More testing end)"); \sa Test(), ~Test(), testMeToo() and publicVar() \test testing @test testing - */)", true); + */)", + true); g_assert(tooltip->buffer->get_text() == R"(A normal member taking two arguments and returning an integer value. Parameters: @@ -685,7 +691,8 @@ See also Test(), ~Test(), testMeToo() and publicVar() * Brief description continued. * * Detailed description starts here. - */)", true); + */)", + true); g_assert(tooltip->buffer->get_text() == R"(Brief description. Brief description continued. Brief description continued. Brief description continued. @@ -696,7 +703,8 @@ Detailed description starts here.)"); * \code * int a = 2; * \endcode -*/)", true); +*/)", + true); g_assert(tooltip->buffer->get_text() == "int a = 2;"); } { @@ -704,7 +712,8 @@ Detailed description starts here.)"); * @code * int a = 2; * @endcode -*/)", true); +*/)", + true); g_assert(tooltip->buffer->get_text() == "int a = 2;"); } { @@ -714,7 +723,8 @@ Detailed description starts here.)"); * int a = 2; * \endcode * test -*/)", true); +*/)", + true); g_assert(tooltip->buffer->get_text() == "test\nint a = 2;\ntest"); } { @@ -726,7 +736,8 @@ Detailed description starts here.)"); * \endcode * * test -*/)", true); +*/)", + true); g_assert(tooltip->buffer->get_text() == "test\n\nint a = 2;\n\ntest"); } { From 889c80de7065e232a02b58b51aaa1ac5f48d95d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Sverre=20Lien=20Sell=C3=A6g?= Date: Wed, 9 Sep 2020 15:27:51 +0200 Subject: [PATCH 014/375] add ci step to catch lint errors --- .gitlab-ci.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b560503..864c033 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,6 +2,7 @@ variables: GIT_SUBMODULE_STRATEGY: recursive stages: + - lint - test - chore @@ -54,6 +55,18 @@ address-sanitizer: - make -j$(nproc) - broadwayd & CTEST_OUTPUT_ON_FAILURE=1 LSAN_OPTIONS=detect_leaks=0 make test +check-format: + image: cppit/jucipp:arch + stage: lint + script: + - 'find src -name "*.cpp" -exec clang-format --Werror --assume-filename={} {} -n 2>> lint-errors.txt \;' + - 'find src -name "*.hpp" -exec clang-format --Werror --assume-filename={} {} -n 2>> lint-errors.txt \;' + - 'find tests -name "*.cpp" -exec clang-format --Werror --assume-filename={} {} -n 2>> lint-errors.txt \;' + - 'find tests -name "*.hpp" -exec clang-format --Werror --assume-filename={} {} -n 2>> lint-errors.txt \;' + - 'HAS_ERRORS=$(cat lint-errors.txt | wc -l)' + - '[ "$HAS_ERRORS" == "0" ] || cat lint-errors.txt' + - '[ "$HAS_ERRORS" == "0" ]' + Clean appveyor cache: stage: chore when: manual From 007785a74d8899c9fdeeb3451c56847dc73e9d78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Sverre=20Lien=20Sell=C3=A6g?= Date: Thu, 10 Sep 2020 13:39:58 +0200 Subject: [PATCH 015/375] remove documentation about code style as it is no longer practise --- README.md | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/README.md b/README.md index e4d71b2..3e33f87 100644 --- a/README.md +++ b/README.md @@ -81,30 +81,3 @@ See [custom styling](docs/custom_styling.md). ## Documentation See [how to build the API doc](docs/api.md). - -## Coding style -Due to poor lambda support in clang-format, a custom clang-format is used with the following patch applied: -```diff -diff --git a/lib/Format/ContinuationIndenter.cpp b/lib/Format/ContinuationIndenter.cpp -index bb8efd61a3..e80a487055 100644 ---- a/lib/Format/ContinuationIndenter.cpp -+++ b/lib/Format/ContinuationIndenter.cpp -@@ -276,6 +276,8 @@ LineState ContinuationIndenter::getInitialState(unsigned FirstIndent, - } - - bool ContinuationIndenter::canBreak(const LineState &State) { -+ if(Style.ColumnLimit==0) -+ return true; - const FormatToken &Current = *State.NextToken; - const FormatToken &Previous = *Current.Previous; - assert(&Previous == Current.Previous); -@@ -325,6 +327,8 @@ bool ContinuationIndenter::canBreak(const LineState &State) { - } - - bool ContinuationIndenter::mustBreak(const LineState &State) { -+ if(Style.ColumnLimit==0) -+ return false; - const FormatToken &Current = *State.NextToken; - const FormatToken &Previous = *Current.Previous; - if (Current.MustBreakBefore || Current.is(TT_InlineASMColon)) -``` From 6d40ecd8cca02e478a6857ddb0533c189a213120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Sverre=20Lien=20Sell=C3=A6g?= Date: Thu, 10 Sep 2020 13:42:56 +0200 Subject: [PATCH 016/375] remove unnecessary else statement --- src/source.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/source.cpp b/src/source.cpp index b0a8f67..407613a 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -109,9 +109,6 @@ std::string Source::FixIt::string(BaseView &view) { text = view.get_buffer()->get_text(view.get_iter_at_line_index(offsets.first.line, offsets.first.index), view.get_iter_at_line_index(offsets.second.line, offsets.second.index)); } - else { - text = ""; - } } if(type == Type::insert) From d518aab030150934192c79e81a655d8f1ff4f933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Sverre=20Lien=20Sell=C3=A6g?= Date: Thu, 10 Sep 2020 13:49:14 +0200 Subject: [PATCH 017/375] update lambdas to new format --- src/autocomplete.cpp | 3 ++- src/directories.cpp | 6 ++++-- src/git.cpp | 3 ++- src/info.cpp | 3 ++- src/notebook.cpp | 3 ++- src/project.cpp | 10 ++++++---- src/selection_dialog.cpp | 3 ++- src/source.cpp | 12 ++++++++---- src/source_base.cpp | 9 ++++++--- src/source_clang.cpp | 12 ++++++++---- src/source_diff.cpp | 15 ++++++++++----- src/source_generic.cpp | 6 ++++-- src/source_language_protocol.cpp | 18 ++++++++++++------ src/source_spellcheck.cpp | 12 ++++++++---- 14 files changed, 76 insertions(+), 39 deletions(-) diff --git a/src/autocomplete.cpp b/src/autocomplete.cpp index 55d8fc7..0787039 100644 --- a/src/autocomplete.cpp +++ b/src/autocomplete.cpp @@ -31,7 +31,8 @@ Autocomplete::Autocomplete(Gsv::View *view, bool &interactive_completion, guint return true; } return false; - }, false); + }, + false); view->signal_focus_out_event().connect([this](GdkEventFocus *event) { stop(); diff --git a/src/directories.cpp b/src/directories.cpp index e5396fb..d6cd6dd 100644 --- a/src/directories.cpp +++ b/src/directories.cpp @@ -678,7 +678,8 @@ void Directories::add_or_update_path(const boost::filesystem::path &dir_path, co 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); + }, + 500); } }); @@ -698,7 +699,8 @@ void Directories::add_or_update_path(const boost::filesystem::path &dir_path, co if(directories.find(path_and_row->first.string()) != directories.end()) colorize_path(path_and_row->first, false); return false; - }, 500); + }, + 500); } }); } diff --git a/src/git.cpp b/src/git.cpp index 26a6da3..9000403 100644 --- a/src/git.cpp +++ b/src/git.cpp @@ -122,7 +122,8 @@ Git::Repository::Repository(const boost::filesystem::path &path) { if(monitor_event != Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { this->clear_saved_status(); } - }, false); + }, + false); } Git::Repository::~Repository() { diff --git a/src/info.cpp b/src/info.cpp index 539101a..bd8220a 100644 --- a/src/info.cpp +++ b/src/info.cpp @@ -29,7 +29,8 @@ void Info::print(const std::string &text) { timeout_connection = Glib::signal_timeout().connect([this]() { hide(); return false; - }, timeout); + }, + timeout); label.set_text(text); show(); diff --git a/src/notebook.cpp b/src/notebook.cpp index 9e06364..3eaab1c 100644 --- a/src/notebook.cpp +++ b/src/notebook.cpp @@ -621,7 +621,8 @@ void Notebook::toggle_split() { Glib::signal_timeout().connect([this] { set_position(get_width() / 2); return false; - }, 200); + }, + 200); } else { for(size_t c = size() - 1; c != static_cast(-1); --c) { diff --git a/src/project.cpp b/src/project.cpp index 3fe4c86..d48af3e 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -972,10 +972,12 @@ void Project::Clang::recreate_build() { void Project::Markdown::compile_and_run() { if(auto view = Notebook::get().get_current_view()) { auto command = Config::get().project.markdown_command + ' ' + filesystem::escape_argument(filesystem::get_short_path(view->file_path).string()); - Terminal::get().async_process(command, "", [command](int exit_status) { - if(exit_status == 127) - Terminal::get().async_print("\e[31mError\e[m: executable not found: " + command + "\n", true); - }, true); + Terminal::get().async_process( + command, "", [command](int exit_status) { + if(exit_status == 127) + Terminal::get().async_print("\e[31mError\e[m: executable not found: " + command + "\n", true); + }, + true); } } diff --git a/src/selection_dialog.cpp b/src/selection_dialog.cpp index d6f312c..7ac3c24 100644 --- a/src/selection_dialog.cpp +++ b/src/selection_dialog.cpp @@ -47,7 +47,8 @@ SelectionDialogBase::SelectionDialogBase(Gtk::TextView *text_view, const boost:: search_entry.signal_changed().connect([this] { if(on_search_entry_changed) on_search_entry_changed(search_entry.get_text()); - }, false); + }, + false); list_view_text.set_search_entry(search_entry); diff --git a/src/source.cpp b/src/source.cpp index 407613a..59877dc 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -598,7 +598,8 @@ void Source::View::setup_signals() { show_diagnostic_tooltips(rectangle); } return false; - }, 100); + }, + 100); } if(clickable_tag_applied) { @@ -615,7 +616,8 @@ void Source::View::setup_signals() { apply_clickable_tag(iter); clickable_tag_applied = true; return false; - }, 100); + }, + 100); } auto last_mouse_pos = std::make_pair(on_motion_last_x, on_motion_last_y); @@ -658,14 +660,16 @@ void Source::View::setup_signals() { show_diagnostic_tooltips(rectangle); } return false; - }, 500); + }, + 500); delayed_tag_similar_symbols_connection.disconnect(); delayed_tag_similar_symbols_connection = Glib::signal_timeout().connect([this] { apply_similar_symbol_tag(); similar_symbol_tag_applied = true; return false; - }, 100); + }, + 100); if(SelectionDialog::get()) SelectionDialog::get()->hide(); diff --git a/src/source_base.cpp b/src/source_base.cpp index 62e41b0..72b1ffc 100644 --- a/src/source_base.cpp +++ b/src/source_base.cpp @@ -358,7 +358,8 @@ void Source::BaseView::monitor_file() { } Recursive::f(view); return false; - }, 1000); + }, + 1000); } }; delayed_monitor_changed_connection.disconnect(); @@ -376,7 +377,8 @@ void Source::BaseView::monitor_file() { delayed_monitor_changed_connection = Glib::signal_timeout().connect([this]() { check_last_write_time(); return false; - }, 1000); // Has to wait 1 second (std::time_t is in seconds) + }, + 1000); // Has to wait 1 second (std::time_t is in seconds) } }); } @@ -1339,7 +1341,8 @@ void Source::BaseView::setup_extra_cursor_signals() { *erase_selection = true; } } - }, false); + }, + false); get_buffer()->signal_erase().connect([this, erase_backward_length, erase_forward_length, erase_selection](const Gtk::TextIter & /*iter_start*/, const Gtk::TextIter & /*iter_end*/) { if(enable_multiple_cursors && (*erase_backward_length != 0 || *erase_forward_length != 0)) { enable_multiple_cursors = false; diff --git a/src/source_clang.cpp b/src/source_clang.cpp index f98c456..87c935b 100644 --- a/src/source_clang.cpp +++ b/src/source_clang.cpp @@ -824,8 +824,10 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa autocomplete.run(); } return false; - }, 500); - }, false); + }, + 500); + }, + false); // Remove argument completions signal_key_press_event().connect([this](GdkEventKey *event) { @@ -838,7 +840,8 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa CompletionDialog::get()->hide(); } return false; - }, false); + }, + false); autocomplete.is_restart_key = [this](guint keyval) { auto iter = get_buffer()->get_insert()->get_iter(); @@ -2060,7 +2063,8 @@ void Source::ClangView::full_reparse() { delayed_full_reparse_connection = Glib::signal_timeout().connect([this] { full_reparse(); return false; - }, 100); + }, + 100); return; } diff --git a/src/source_diff.cpp b/src/source_diff.cpp index 22f683a..8044b1e 100644 --- a/src/source_diff.cpp +++ b/src/source_diff.cpp @@ -144,8 +144,10 @@ void Source::DiffView::configure() { delayed_buffer_changed_connection = Glib::signal_timeout().connect([this]() { parse_state = ParseState::starting; return false; - }, 250); - }, false); + }, + 250); + }, + false); buffer_erase_connection = get_buffer()->signal_erase().connect([this](const Gtk::TextIter &start_iter, const Gtk::TextIter &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 @@ -157,8 +159,10 @@ void Source::DiffView::configure() { delayed_buffer_changed_connection = Glib::signal_timeout().connect([this]() { parse_state = ParseState::starting; return false; - }, 250); - }, false); + }, + 250); + }, + false); monitor_changed_connection = repository->monitor->signal_changed().connect([this](const Glib::RefPtr &file, const Glib::RefPtr &, @@ -171,7 +175,8 @@ void Source::DiffView::configure() { LockGuard lock(parse_mutex); diff = nullptr; return false; - }, 500); + }, + 500); } }); diff --git a/src/source_generic.cpp b/src/source_generic.cpp index d893cfe..e18d1d1 100644 --- a/src/source_generic.cpp +++ b/src/source_generic.cpp @@ -130,7 +130,8 @@ void Source::GenericView::setup_buffer_words() { } } } - }, false); + }, + false); // Add all words between start and end of insert get_buffer()->signal_insert().connect([this](const Gtk::TextIter &iter, const Glib::ustring &text, int bytes) { @@ -168,7 +169,8 @@ void Source::GenericView::setup_buffer_words() { buffer_words.erase(it); } } - }, false); + }, + false); // Add new word resulting from erased text get_buffer()->signal_erase().connect([this](const Gtk::TextIter &start_, const Gtk::TextIter & /*end*/) { diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 085098d..092e3c2 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -1404,11 +1404,13 @@ void Source::LanguageProtocolView::setup_signals() { std::string text = text_; escape_text(text); client->write_notification("textDocument/didChange", R"("textDocument":{"uri":")" + this->uri + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + R"({"range":{"start":{"line": )" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(}},"text":")" + text + "\"}" + "]"); - }, false); + }, + false); get_buffer()->signal_erase().connect([this](const Gtk::TextIter &start, const Gtk::TextIter &end) { client->write_notification("textDocument/didChange", R"("textDocument":{"uri":")" + this->uri + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + R"({"range":{"start":{"line": )" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(end.get_line()) + ",\"character\":" + std::to_string(end.get_line_offset()) + R"(}},"text":""})" + "]"); - }, false); + }, + false); } else if(capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::full) { get_buffer()->signal_changed().connect([this]() { @@ -1460,8 +1462,10 @@ void Source::LanguageProtocolView::setup_autocomplete() { autocomplete->run(); } return false; - }, 500); - }, false); + }, + 500); + }, + false); // Remove argument completions signal_key_press_event().connect([this](GdkEventKey *event) { @@ -1474,7 +1478,8 @@ void Source::LanguageProtocolView::setup_autocomplete() { CompletionDialog::get()->hide(); } return false; - }, false); + }, + false); } autocomplete->is_restart_key = [this](guint keyval) { @@ -1825,7 +1830,8 @@ void Source::LanguageProtocolView::update_type_coverage() { --update_type_coverage_retries; update_type_coverage(); return false; - }, 1000); + }, + 1000); }); } return; diff --git a/src/source_spellcheck.cpp b/src/source_spellcheck.cpp index 92f14cb..649c4ee 100644 --- a/src/source_spellcheck.cpp +++ b/src/source_spellcheck.cpp @@ -20,7 +20,8 @@ Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path, } return false; - }, false); + }, + false); //The following signal is added in case SpellCheckView is not subclassed signal_key_press_event().connect([this](GdkEventKey *event) { @@ -124,7 +125,8 @@ Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path, get_buffer()->remove_tag(spellcheck_error_tag, begin_no_spellcheck_iter, iter); } return false; - }, 1000); + }, + 1000); }); // In case of for instance text paste or undo/redo @@ -141,7 +143,8 @@ Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path, get_buffer()->remove_tag(spellcheck_error_tag, word.first, word.second); } } - }, false); + }, + false); get_buffer()->signal_mark_set().connect([this](const Gtk::TextIter &iter, const Glib::RefPtr &mark) { if(mark->get_name() == "insert") { @@ -178,7 +181,8 @@ Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path, SelectionDialog::get()->show(); } return false; - }, 500); + }, + 500); } }); From 3c726f52dad13b25c8208d3f8dc5fd5c8d69c607 Mon Sep 17 00:00:00 2001 From: eidheim Date: Thu, 10 Sep 2020 18:39:44 +0200 Subject: [PATCH 018/375] Formatting cleanup --- src/autocomplete.cpp | 17 +-- src/directories.cpp | 26 ++-- src/files.hpp | 40 +++--- src/git.cpp | 17 +-- src/info.cpp | 11 +- src/notebook.cpp | 11 +- src/selection_dialog.cpp | 11 +- src/source.cpp | 92 +++++++------- src/source_base.cpp | 87 ++++++------- src/source_clang.cpp | 97 ++++++++------- src/source_diff.cpp | 105 ++++++++-------- src/source_generic.cpp | 78 ++++++------ src/source_language_protocol.cpp | 207 +++++++++++++++++-------------- src/source_spellcheck.cpp | 186 +++++++++++++-------------- src/terminal.cpp | 82 +++++++----- tests/process_test.cpp | 8 +- 16 files changed, 575 insertions(+), 500 deletions(-) diff --git a/src/autocomplete.cpp b/src/autocomplete.cpp index 0787039..0c50990 100644 --- a/src/autocomplete.cpp +++ b/src/autocomplete.cpp @@ -25,14 +25,15 @@ Autocomplete::Autocomplete(Gsv::View *view, bool &interactive_completion, guint stop(); }); - view->signal_key_release_event().connect([](GdkEventKey *event) { - if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) { - if(CompletionDialog::get()->on_key_release(event)) - return true; - } - return false; - }, - false); + view->signal_key_release_event().connect( + [](GdkEventKey *event) { + if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) { + if(CompletionDialog::get()->on_key_release(event)) + return true; + } + return false; + }, + false); view->signal_focus_out_event().connect([this](GdkEventFocus *event) { stop(); diff --git a/src/directories.cpp b/src/directories.cpp index d6cd6dd..5508dc4 100644 --- a/src/directories.cpp +++ b/src/directories.cpp @@ -674,12 +674,13 @@ void Directories::add_or_update_path(const boost::filesystem::path &dir_path, co 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); + *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); } }); @@ -695,12 +696,13 @@ void Directories::add_or_update_path(const boost::filesystem::path &dir_path, co 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); + *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); } }); } diff --git a/src/files.hpp b/src/files.hpp index 410d04d..058103c 100644 --- a/src/files.hpp +++ b/src/files.hpp @@ -4,9 +4,11 @@ /// 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(", +const std::string default_config_file = + 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": "", @@ -20,18 +22,18 @@ const std::string default_config_file = R"RAW({ "style": "juci-light", "font_comment": "Use \"\" for default font, and for instance \"Monospace 12\" to also set size",)RAW" #ifdef __APPLE__ - R"RAW( + R"RAW( "font": "Menlo",)RAW" #else #ifdef _WIN32 - R"RAW( + R"RAW( "font": "Consolas",)RAW" #else - R"RAW( + R"RAW( "font": "Monospace",)RAW" #endif #endif - R"RAW( + 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", @@ -91,13 +93,13 @@ const std::string default_config_file = R"RAW({ "debug_build_path": "/debug", "cmake": {)RAW" #ifdef _WIN32 - R"RAW( + R"RAW( "command": "cmake -G\"MSYS Makefiles\"",)RAW" #else - R"RAW( + R"RAW( "command": "cmake",)RAW" #endif - R"RAW( + R"RAW( "compile_command": "cmake --build ." }, "meson": { @@ -108,13 +110,13 @@ const std::string default_config_file = R"RAW({ "default_build_management_system": "cmake", "save_on_compile_or_run": true,)RAW" #ifdef JUCI_USE_UCTAGS - R"RAW( + R"RAW( "ctags_command": "uctags",)RAW" #else - R"RAW( + R"RAW( "ctags_command": "ctags",)RAW" #endif - R"RAW( + R"RAW( "grep_command": "grep", "cargo_command": "cargo", "python_command": "python -u", @@ -194,26 +196,26 @@ const std::string default_config_file = R"RAW({ "debug_show_breakpoints": "b", "debug_goto_stop": "l",)RAW" #ifdef __linux - R"RAW( + R"RAW( "window_next_tab": "Tab", "window_previous_tab": "Tab",)RAW" #else - R"RAW( + R"RAW( "window_next_tab": "Right", "window_previous_tab": "Left",)RAW" #endif - R"RAW( + R"RAW( "window_goto_tab": "", "window_toggle_split": "", "window_split_source_buffer": "",)RAW" #ifdef __APPLE__ - R"RAW( + R"RAW( "window_toggle_full_screen": "f",)RAW" #else - R"RAW( + R"RAW( "window_toggle_full_screen": "F11",)RAW" #endif - R"RAW( + R"RAW( "window_toggle_tabs": "", "window_toggle_zen_mode": "", "window_clear_terminal": "" diff --git a/src/git.cpp b/src/git.cpp index 9000403..a912252 100644 --- a/src/git.cpp +++ b/src/git.cpp @@ -116,14 +116,15 @@ Git::Repository::Repository(const boost::filesystem::path &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 &file, - const Glib::RefPtr &, - Gio::FileMonitorEvent monitor_event) { - if(monitor_event != Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { - this->clear_saved_status(); - } - }, - false); + monitor_changed_connection = monitor->signal_changed().connect( + [this](const Glib::RefPtr &file, + const Glib::RefPtr &, + Gio::FileMonitorEvent monitor_event) { + if(monitor_event != Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { + this->clear_saved_status(); + } + }, + false); } Git::Repository::~Repository() { diff --git a/src/info.cpp b/src/info.cpp index bd8220a..75602f9 100644 --- a/src/info.cpp +++ b/src/info.cpp @@ -26,11 +26,12 @@ void Info::print(const std::string &text) { //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 = Glib::signal_timeout().connect( + [this]() { + hide(); + return false; + }, + timeout); label.set_text(text); show(); diff --git a/src/notebook.cpp b/src/notebook.cpp index 3eaab1c..28ded3a 100644 --- a/src/notebook.cpp +++ b/src/notebook.cpp @@ -618,11 +618,12 @@ void Notebook::toggle_split() { 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); + Glib::signal_timeout().connect( + [this] { + set_position(get_width() / 2); + return false; + }, + 200); } else { for(size_t c = size() - 1; c != static_cast(-1); --c) { diff --git a/src/selection_dialog.cpp b/src/selection_dialog.cpp index 7ac3c24..06bc213 100644 --- a/src/selection_dialog.cpp +++ b/src/selection_dialog.cpp @@ -44,11 +44,12 @@ SelectionDialogBase::SelectionDialogBase(Gtk::TextView *text_view, const boost:: 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); diff --git a/src/source.cpp b/src/source.cpp index 59877dc..74dd2ae 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -588,18 +588,19 @@ void Source::View::setup_signals() { if(on_motion_last_x != event->x || on_motion_last_y != event->y) { delayed_tooltips_connection.disconnect(); if((event->state & GDK_BUTTON1_MASK) == 0) { - delayed_tooltips_connection = Glib::signal_timeout().connect([this, x = event->x, y = event->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); + delayed_tooltips_connection = Glib::signal_timeout().connect( + [this, x = event->x, y = event->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); } if(clickable_tag_applied) { @@ -608,16 +609,17 @@ void Source::View::setup_signals() { } if((event->state & primary_modifier_mask) && !(event->state & GDK_SHIFT_MASK) && !(event->state & GDK_BUTTON1_MASK)) { delayed_tag_clickable_connection.disconnect(); - delayed_tag_clickable_connection = Glib::signal_timeout().connect([this, x = event->x, y = event->y]() { - int buffer_x, buffer_y; - window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, x, y, buffer_x, buffer_y); - Gtk::TextIter iter; - get_iter_at_location(iter, buffer_x, buffer_y); - apply_clickable_tag(iter); - clickable_tag_applied = true; - return false; - }, - 100); + delayed_tag_clickable_connection = Glib::signal_timeout().connect( + [this, x = event->x, y = event->y]() { + int buffer_x, buffer_y; + window_to_buffer_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, x, y, buffer_x, buffer_y); + Gtk::TextIter iter; + get_iter_at_location(iter, buffer_x, buffer_y); + apply_clickable_tag(iter); + clickable_tag_applied = true; + return false; + }, + 100); } auto last_mouse_pos = std::make_pair(on_motion_last_x, on_motion_last_y); @@ -646,30 +648,32 @@ void Source::View::setup_signals() { hide_tooltips(); delayed_tooltips_connection.disconnect(); - 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); + 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); delayed_tag_similar_symbols_connection.disconnect(); - delayed_tag_similar_symbols_connection = Glib::signal_timeout().connect([this] { - apply_similar_symbol_tag(); - similar_symbol_tag_applied = true; - return false; - }, - 100); + delayed_tag_similar_symbols_connection = Glib::signal_timeout().connect( + [this] { + apply_similar_symbol_tag(); + similar_symbol_tag_applied = true; + return false; + }, + 100); if(SelectionDialog::get()) SelectionDialog::get()->hide(); diff --git a/src/source_base.cpp b/src/source_base.cpp index 72b1ffc..367fc1f 100644 --- a/src/source_base.cpp +++ b/src/source_base.cpp @@ -343,23 +343,24 @@ void Source::BaseView::monitor_file() { public: static void f(BaseView *view, boost::optional previous_last_write_time = {}, bool check_called = false) { view->delayed_monitor_changed_connection.disconnect(); - view->delayed_monitor_changed_connection = Glib::signal_timeout().connect([view, previous_last_write_time, check_called]() { - boost::system::error_code ec; - auto last_write_time = boost::filesystem::last_write_time(view->file_path, ec); - if(!ec && last_write_time != view->last_write_time) { - if(last_write_time == previous_last_write_time) { // If no change has happened in the last second (std::time_t is in seconds). - if(!check_called) // To avoid several info messages when file is changed but not reloaded. - view->check_last_write_time(last_write_time); - Recursive::f(view, last_write_time, true); + view->delayed_monitor_changed_connection = Glib::signal_timeout().connect( + [view, previous_last_write_time, check_called]() { + boost::system::error_code ec; + auto last_write_time = boost::filesystem::last_write_time(view->file_path, ec); + if(!ec && last_write_time != view->last_write_time) { + if(last_write_time == previous_last_write_time) { // If no change has happened in the last second (std::time_t is in seconds). + if(!check_called) // To avoid several info messages when file is changed but not reloaded. + view->check_last_write_time(last_write_time); + Recursive::f(view, last_write_time, true); + return false; + } + Recursive::f(view, last_write_time); + return false; + } + Recursive::f(view); return false; - } - Recursive::f(view, last_write_time); - return false; - } - Recursive::f(view); - return false; - }, - 1000); + }, + 1000); } }; delayed_monitor_changed_connection.disconnect(); @@ -374,11 +375,12 @@ void Source::BaseView::monitor_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); // Has to wait 1 second (std::time_t is in seconds) + delayed_monitor_changed_connection = Glib::signal_timeout().connect( + [this]() { + check_last_write_time(); + return false; + }, + 1000); // Has to wait 1 second (std::time_t is in seconds) } }); } @@ -1322,27 +1324,28 @@ void Source::BaseView::setup_extra_cursor_signals() { auto erase_backward_length = std::make_shared(0); auto erase_forward_length = std::make_shared(0); auto erase_selection = std::make_shared(false); - get_buffer()->signal_erase().connect([this, erase_backward_length, erase_forward_length, erase_selection](const Gtk::TextIter &iter_start, const Gtk::TextIter &iter_end) { - if(enable_multiple_cursors && (!extra_cursors.empty())) { - auto insert_offset = get_buffer()->get_insert()->get_iter().get_offset(); - *erase_backward_length = insert_offset - iter_start.get_offset(); - *erase_forward_length = iter_end.get_offset() - insert_offset; - - if(*erase_backward_length == 0) { - auto iter = get_buffer()->get_insert()->get_iter(); - iter.forward_chars(*erase_forward_length); - if(iter == get_buffer()->get_selection_bound()->get_iter()) - *erase_selection = true; - } - else if(*erase_forward_length == 0) { - auto iter = get_buffer()->get_insert()->get_iter(); - iter.backward_chars(*erase_backward_length); - if(iter == get_buffer()->get_selection_bound()->get_iter()) - *erase_selection = true; - } - } - }, - false); + get_buffer()->signal_erase().connect( + [this, erase_backward_length, erase_forward_length, erase_selection](const Gtk::TextIter &iter_start, const Gtk::TextIter &iter_end) { + if(enable_multiple_cursors && (!extra_cursors.empty())) { + auto insert_offset = get_buffer()->get_insert()->get_iter().get_offset(); + *erase_backward_length = insert_offset - iter_start.get_offset(); + *erase_forward_length = iter_end.get_offset() - insert_offset; + + if(*erase_backward_length == 0) { + auto iter = get_buffer()->get_insert()->get_iter(); + iter.forward_chars(*erase_forward_length); + if(iter == get_buffer()->get_selection_bound()->get_iter()) + *erase_selection = true; + } + else if(*erase_forward_length == 0) { + auto iter = get_buffer()->get_insert()->get_iter(); + iter.backward_chars(*erase_backward_length); + if(iter == get_buffer()->get_selection_bound()->get_iter()) + *erase_selection = true; + } + } + }, + false); get_buffer()->signal_erase().connect([this, erase_backward_length, erase_forward_length, erase_selection](const Gtk::TextIter & /*iter_start*/, const Gtk::TextIter & /*iter_end*/) { if(enable_multiple_cursors && (*erase_backward_length != 0 || *erase_forward_length != 0)) { enable_multiple_cursors = false; diff --git a/src/source_clang.cpp b/src/source_clang.cpp index 87c935b..d7596ec 100644 --- a/src/source_clang.cpp +++ b/src/source_clang.cpp @@ -643,8 +643,9 @@ void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle) }; VisitorData visitor_data{cursor.get_source_range().get_offsets(), cursor.get_spelling(), {}}; auto start_cursor = cursor; - for(auto parent = cursor.get_semantic_parent(); parent.get_kind() != clangmm::Cursor::Kind::TranslationUnit && - parent.get_kind() != clangmm::Cursor::Kind::ClassDecl; + for(auto parent = cursor.get_semantic_parent(); + parent.get_kind() != clangmm::Cursor::Kind::TranslationUnit && + parent.get_kind() != clangmm::Cursor::Kind::ClassDecl; parent = parent.get_semantic_parent()) start_cursor = parent; clang_visitChildren( @@ -801,47 +802,50 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa }; // 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_parameters) - autocomplete.stop(); - show_parameters = 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_argument()) { - autocomplete.stop(); - autocomplete.run(); - } - return false; - }, - 500); - }, - false); + get_buffer()->signal_changed().connect( + [this] { + if(!interactive_completion) + return; + if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) + return; + if(!has_focus()) + return; + if(show_parameters) + autocomplete.stop(); + show_parameters = 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_argument()) { + autocomplete.stop(); + autocomplete.run(); + } + return false; + }, + 500); + }, + false); // Remove argument completions - signal_key_press_event().connect([this](GdkEventKey *event) { - if(show_parameters && CompletionDialog::get() && CompletionDialog::get()->is_visible() && - event->keyval != GDK_KEY_Down && event->keyval != GDK_KEY_Up && - event->keyval != GDK_KEY_Return && event->keyval != GDK_KEY_KP_Enter && - event->keyval != GDK_KEY_ISO_Left_Tab && event->keyval != GDK_KEY_Tab && - (event->keyval < GDK_KEY_Shift_L || event->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); + signal_key_press_event().connect( + [this](GdkEventKey *event) { + if(show_parameters && CompletionDialog::get() && CompletionDialog::get()->is_visible() && + event->keyval != GDK_KEY_Down && event->keyval != GDK_KEY_Up && + event->keyval != GDK_KEY_Return && event->keyval != GDK_KEY_KP_Enter && + event->keyval != GDK_KEY_ISO_Left_Tab && event->keyval != GDK_KEY_Tab && + (event->keyval < GDK_KEY_Shift_L || event->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(); @@ -2060,11 +2064,12 @@ void Source::ClangView::full_reparse() { return; if(full_reparse_running) { - delayed_full_reparse_connection = Glib::signal_timeout().connect([this] { - full_reparse(); - return false; - }, - 100); + delayed_full_reparse_connection = Glib::signal_timeout().connect( + [this] { + full_reparse(); + return false; + }, + 100); return; } diff --git a/src/source_diff.cpp b/src/source_diff.cpp index 8044b1e..c54f89b 100644 --- a/src/source_diff.cpp +++ b/src/source_diff.cpp @@ -118,65 +118,70 @@ void Source::DiffView::configure() { parse_stop = false; monitor_changed = false; - buffer_insert_connection = get_buffer()->signal_insert().connect([this](const Gtk::TextIter &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; + buffer_insert_connection = get_buffer()->signal_insert().connect( + [this](const Gtk::TextIter &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; } - } - 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); + //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::TextIter &start_iter, const Gtk::TextIter &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_erase_connection = get_buffer()->signal_erase().connect( + [this](const Gtk::TextIter &start_iter, const Gtk::TextIter &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); + 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 &file, const Glib::RefPtr &, 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; - LockGuard lock(parse_mutex); - diff = nullptr; - return false; - }, - 500); + delayed_monitor_changed_connection = Glib::signal_timeout().connect( + [this]() { + monitor_changed = true; + parse_state = ParseState::starting; + LockGuard lock(parse_mutex); + diff = nullptr; + return false; + }, + 500); } }); diff --git a/src/source_generic.cpp b/src/source_generic.cpp index e18d1d1..9fa4759 100644 --- a/src/source_generic.cpp +++ b/src/source_generic.cpp @@ -112,26 +112,27 @@ void Source::GenericView::setup_buffer_words() { } // Remove changed word at insert - get_buffer()->signal_insert().connect([this](const Gtk::TextIter &iter_, const Glib::ustring &text, int bytes) { - auto iter = iter_; - if(!is_token_char(*iter)) - iter.backward_char(); - - if(is_token_char(*iter)) { - auto word = get_token_iters(iter); - if(word.second.get_offset() - word.first.get_offset() >= 3) { - LockGuard lock(buffer_words_mutex); - auto it = buffer_words.find(get_buffer()->get_text(word.first, word.second)); - if(it != buffer_words.end()) { - if(it->second > 1) - --(it->second); - else - buffer_words.erase(it); + get_buffer()->signal_insert().connect( + [this](const Gtk::TextIter &iter_, const Glib::ustring &text, int bytes) { + auto iter = iter_; + if(!is_token_char(*iter)) + iter.backward_char(); + + if(is_token_char(*iter)) { + auto word = get_token_iters(iter); + if(word.second.get_offset() - word.first.get_offset() >= 3) { + LockGuard lock(buffer_words_mutex); + auto it = buffer_words.find(get_buffer()->get_text(word.first, word.second)); + if(it != buffer_words.end()) { + if(it->second > 1) + --(it->second); + else + buffer_words.erase(it); + } + } } - } - } - }, - false); + }, + false); // Add all words between start and end of insert get_buffer()->signal_insert().connect([this](const Gtk::TextIter &iter, const Glib::ustring &text, int bytes) { @@ -152,25 +153,26 @@ void Source::GenericView::setup_buffer_words() { }); // Remove words within text that was removed - get_buffer()->signal_erase().connect([this](const Gtk::TextIter &start_, const Gtk::TextIter &end_) { - auto start = start_; - auto end = end_; - if(!is_token_char(*start)) - start.backward_char(); - end.forward_char(); - auto words = get_words(start, end); - LockGuard lock(buffer_words_mutex); - for(auto &word : words) { - auto it = buffer_words.find(get_buffer()->get_text(word.first, word.second)); - if(it != buffer_words.end()) { - if(it->second > 1) - --(it->second); - else - buffer_words.erase(it); - } - } - }, - false); + get_buffer()->signal_erase().connect( + [this](const Gtk::TextIter &start_, const Gtk::TextIter &end_) { + auto start = start_; + auto end = end_; + if(!is_token_char(*start)) + start.backward_char(); + end.forward_char(); + auto words = get_words(start, end); + LockGuard lock(buffer_words_mutex); + for(auto &word : words) { + auto it = buffer_words.find(get_buffer()->get_text(word.first, word.second)); + if(it != buffer_words.end()) { + if(it->second > 1) + --(it->second); + else + buffer_words.erase(it); + } + } + }, + false); // Add new word resulting from erased text get_buffer()->signal_erase().connect([this](const Gtk::TextIter &start_, const Gtk::TextIter & /*end*/) { diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 092e3c2..254c6c2 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -51,9 +51,15 @@ LanguageProtocol::TextEdit::TextEdit(const boost::property_tree::ptree &pt, std: LanguageProtocol::Client::Client(boost::filesystem::path root_path_, std::string language_id_) : root_path(std::move(root_path_)), language_id(std::move(language_id_)) { process = std::make_unique( - filesystem::escape_argument(language_id + "-language-server"), root_path.string(), [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, TinyProcessLib::Config{1048576}); + filesystem::escape_argument(language_id + "-language-server"), root_path.string(), + [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, TinyProcessLib::Config{1048576}); } std::shared_ptr LanguageProtocol::Client::get(const boost::filesystem::path &file_path, const std::string &language_id) { @@ -126,7 +132,8 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang LockGuard lock(read_write_mutex); process_id = process->get_id(); } - write_request(nullptr, "initialize", "\"processId\":" + std::to_string(process_id) + R"(,"rootUri":")" + filesystem::get_uri_from_path(root_path) + R"(","capabilities": { + write_request( + nullptr, "initialize", "\"processId\":" + std::to_string(process_id) + R"(,"rootUri":")" + filesystem::get_uri_from_path(root_path) + R"(","capabilities": { "workspace": { "symbol": { "dynamicRegistration": true } }, "textDocument": { "synchronization": { "dynamicRegistration": true, "didSave": true }, @@ -168,38 +175,38 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang "checkOnSave": { "enable": true } }, "trace": "off")", - [this, &result_processed](const boost::property_tree::ptree &result, bool error) { - if(!error) { - if(auto capabilities_pt = result.get_child_optional("capabilities")) { - try { - capabilities.text_document_sync = static_cast(capabilities_pt->get("textDocumentSync")); - } - catch(...) { - capabilities.text_document_sync = static_cast(capabilities_pt->get("textDocumentSync.change", 0)); - } - capabilities.hover = capabilities_pt->get("hoverProvider", false); - capabilities.completion = static_cast(capabilities_pt->get_child_optional("completionProvider")); - capabilities.signature_help = static_cast(capabilities_pt->get_child_optional("signatureHelpProvider")); - capabilities.definition = capabilities_pt->get("definitionProvider", false); - capabilities.references = capabilities_pt->get("referencesProvider", false); - capabilities.document_highlight = capabilities_pt->get("documentHighlightProvider", false); - capabilities.workspace_symbol = capabilities_pt->get("workspaceSymbolProvider", false); - capabilities.document_symbol = capabilities_pt->get("documentSymbolProvider", false); - capabilities.document_formatting = capabilities_pt->get("documentFormattingProvider", false); - capabilities.document_range_formatting = capabilities_pt->get("documentRangeFormattingProvider", false); - capabilities.rename = capabilities_pt->get("renameProvider", false); - if(!capabilities.rename) - capabilities.rename = capabilities_pt->get("renameProvider.prepareProvider", false); - capabilities.code_action = capabilities_pt->get("codeActionProvider", false); - if(!capabilities.code_action) - capabilities.code_action = static_cast(capabilities_pt->get_child_optional("codeActionProvider.codeActionKinds")); - capabilities.type_coverage = capabilities_pt->get("typeCoverageProvider", false); - } + [this, &result_processed](const boost::property_tree::ptree &result, bool error) { + if(!error) { + if(auto capabilities_pt = result.get_child_optional("capabilities")) { + try { + capabilities.text_document_sync = static_cast(capabilities_pt->get("textDocumentSync")); + } + catch(...) { + capabilities.text_document_sync = static_cast(capabilities_pt->get("textDocumentSync.change", 0)); + } + capabilities.hover = capabilities_pt->get("hoverProvider", false); + capabilities.completion = static_cast(capabilities_pt->get_child_optional("completionProvider")); + capabilities.signature_help = static_cast(capabilities_pt->get_child_optional("signatureHelpProvider")); + capabilities.definition = capabilities_pt->get("definitionProvider", false); + capabilities.references = capabilities_pt->get("referencesProvider", false); + capabilities.document_highlight = capabilities_pt->get("documentHighlightProvider", false); + capabilities.workspace_symbol = capabilities_pt->get("workspaceSymbolProvider", false); + capabilities.document_symbol = capabilities_pt->get("documentSymbolProvider", false); + capabilities.document_formatting = capabilities_pt->get("documentFormattingProvider", false); + capabilities.document_range_formatting = capabilities_pt->get("documentRangeFormattingProvider", false); + capabilities.rename = capabilities_pt->get("renameProvider", false); + if(!capabilities.rename) + capabilities.rename = capabilities_pt->get("renameProvider.prepareProvider", false); + capabilities.code_action = capabilities_pt->get("codeActionProvider", false); + if(!capabilities.code_action) + capabilities.code_action = static_cast(capabilities_pt->get_child_optional("codeActionProvider.codeActionKinds")); + capabilities.type_coverage = capabilities_pt->get("typeCoverageProvider", false); + } - write_notification("initialized", ""); - } - result_processed.set_value(); - }); + write_notification("initialized", ""); + } + result_processed.set_value(); + }); result_processed.get_future().get(); initialized = true; @@ -1024,7 +1031,11 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vector{}); - pair.first->second.emplace_back(edit.new_text, filesystem::get_path_from_uri(file_it->first).string(), std::make_pair(Offset(edit.range.start.line, edit.range.start.character), Offset(edit.range.end.line, edit.range.end.character))); + pair.first->second.emplace_back( + edit.new_text, + filesystem::get_path_from_uri(file_it->first).string(), + std::make_pair(Offset(edit.range.start.line, edit.range.start.character), + Offset(edit.range.end.line, edit.range.end.character))); break; } } @@ -1034,7 +1045,11 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vector{}); - pair.first->second.emplace_back(edit.new_text, filesystem::get_path_from_uri(file_it->first).string(), std::make_pair(Offset(edit.range.start.line, edit.range.start.character), Offset(edit.range.end.line, edit.range.end.character))); + pair.first->second.emplace_back( + edit.new_text, + filesystem::get_path_from_uri(file_it->first).string(), + std::make_pair(Offset(edit.range.start.line, edit.range.start.character), + Offset(edit.range.end.line, edit.range.end.character))); break; } } @@ -1400,17 +1415,19 @@ Source::Offset Source::LanguageProtocolView::get_declaration(const Gtk::TextIter void Source::LanguageProtocolView::setup_signals() { if(capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::incremental) { - get_buffer()->signal_insert().connect([this](const Gtk::TextIter &start, const Glib::ustring &text_, int bytes) { - std::string text = text_; - escape_text(text); - client->write_notification("textDocument/didChange", R"("textDocument":{"uri":")" + this->uri + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + R"({"range":{"start":{"line": )" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(}},"text":")" + text + "\"}" + "]"); - }, - false); - - get_buffer()->signal_erase().connect([this](const Gtk::TextIter &start, const Gtk::TextIter &end) { - client->write_notification("textDocument/didChange", R"("textDocument":{"uri":")" + this->uri + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + R"({"range":{"start":{"line": )" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(end.get_line()) + ",\"character\":" + std::to_string(end.get_line_offset()) + R"(}},"text":""})" + "]"); - }, - false); + get_buffer()->signal_insert().connect( + [this](const Gtk::TextIter &start, const Glib::ustring &text_, int bytes) { + std::string text = text_; + escape_text(text); + client->write_notification("textDocument/didChange", R"("textDocument":{"uri":")" + this->uri + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + R"({"range":{"start":{"line": )" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(}},"text":")" + text + "\"}" + "]"); + }, + false); + + get_buffer()->signal_erase().connect( + [this](const Gtk::TextIter &start, const Gtk::TextIter &end) { + client->write_notification("textDocument/didChange", R"("textDocument":{"uri":")" + this->uri + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + R"({"range":{"start":{"line": )" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(end.get_line()) + ",\"character\":" + std::to_string(end.get_line_offset()) + R"(}},"text":""})" + "]"); + }, + false); } else if(capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::full) { get_buffer()->signal_changed().connect([this]() { @@ -1439,47 +1456,50 @@ void Source::LanguageProtocolView::setup_autocomplete() { if(capabilities.signature_help) { // 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(autocomplete_show_arguments) - autocomplete->stop(); - autocomplete_show_arguments = false; - autocomplete_delayed_show_arguments_connection.disconnect(); - autocomplete_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_argument()) { - autocomplete->stop(); - autocomplete->run(); - } - return false; - }, - 500); - }, - false); + get_buffer()->signal_changed().connect( + [this] { + if(!interactive_completion) + return; + if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) + return; + if(!has_focus()) + return; + if(autocomplete_show_arguments) + autocomplete->stop(); + autocomplete_show_arguments = false; + autocomplete_delayed_show_arguments_connection.disconnect(); + autocomplete_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_argument()) { + autocomplete->stop(); + autocomplete->run(); + } + return false; + }, + 500); + }, + false); // Remove argument completions - signal_key_press_event().connect([this](GdkEventKey *event) { - if(autocomplete_show_arguments && CompletionDialog::get() && CompletionDialog::get()->is_visible() && - event->keyval != GDK_KEY_Down && event->keyval != GDK_KEY_Up && - event->keyval != GDK_KEY_Return && event->keyval != GDK_KEY_KP_Enter && - event->keyval != GDK_KEY_ISO_Left_Tab && event->keyval != GDK_KEY_Tab && - (event->keyval < GDK_KEY_Shift_L || event->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); + signal_key_press_event().connect( + [this](GdkEventKey *event) { + if(autocomplete_show_arguments && CompletionDialog::get() && CompletionDialog::get()->is_visible() && + event->keyval != GDK_KEY_Down && event->keyval != GDK_KEY_Up && + event->keyval != GDK_KEY_Return && event->keyval != GDK_KEY_KP_Enter && + event->keyval != GDK_KEY_ISO_Left_Tab && event->keyval != GDK_KEY_Tab && + (event->keyval < GDK_KEY_Shift_L || event->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) { @@ -1826,12 +1846,13 @@ void Source::LanguageProtocolView::update_type_coverage() { if(update_type_coverage_retries > 0) { // Retry typeCoverage request, since these requests can fail while waiting for language server to start dispatcher.post([this] { update_type_coverage_connection.disconnect(); - update_type_coverage_connection = Glib::signal_timeout().connect([this]() { - --update_type_coverage_retries; - update_type_coverage(); - return false; - }, - 1000); + update_type_coverage_connection = Glib::signal_timeout().connect( + [this]() { + --update_type_coverage_retries; + update_type_coverage(); + return false; + }, + 1000); }); } return; diff --git a/src/source_spellcheck.cpp b/src/source_spellcheck.cpp index 649c4ee..787c709 100644 --- a/src/source_spellcheck.cpp +++ b/src/source_spellcheck.cpp @@ -13,15 +13,16 @@ Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path, 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; - } + 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) { @@ -83,68 +84,70 @@ Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path, } } 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; + 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; - 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); + 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) - return; - - if(disable_spellcheck) { - 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_insert().connect( + [this](const Gtk::TextIter &start_iter, const Glib::ustring &inserted_string, int) { + if(!spellcheck_checker) + return; + + if(disable_spellcheck) { + 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::TextIter &iter, const Glib::RefPtr &mark) { if(mark->get_name() == "insert") { @@ -155,34 +158,35 @@ Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path, delayed_spellcheck_suggestions_connection.disconnect(); if(get_buffer()->get_has_selection()) return; - delayed_spellcheck_suggestions_connection = Glib::signal_timeout().connect([this]() { - if(get_buffer()->get_insert()->get_iter().has_tag(spellcheck_error_tag)) { - SelectionDialog::create(this, 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_suggestions_connection = Glib::signal_timeout().connect( + [this]() { + if(get_buffer()->get_insert()->get_iter().has_tag(spellcheck_error_tag)) { + SelectionDialog::create(this, 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(); } - } - 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); } }); diff --git a/src/terminal.cpp b/src/terminal.cpp index fe5d913..cfe1490 100644 --- a/src/terminal.cpp +++ b/src/terminal.cpp @@ -211,7 +211,13 @@ int Terminal::process(const std::string &command, const boost::filesystem::path std::unique_ptr process; if(use_pipes) process = std::make_unique( - 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); }); + 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(command, path.string()); @@ -228,19 +234,24 @@ int Terminal::process(std::istream &stdin_stream, std::ostream &stdout_stream, c scroll_to_bottom(); 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); + 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("\e[31mError\e[m: failed to run command: " + command + "\n", true); @@ -268,25 +279,30 @@ std::shared_ptr Terminal::async_process(const std::stri stdin_buffer.clear(); auto process = std::make_shared( - command, path.string(), [this, quiet](const char *bytes, size_t n) { - if(!quiet) { - // Print stdout message sequentially to avoid the GUI becoming unresponsive - std::promise message_printed; - dispatcher.post([message = std::string(bytes, n), &message_printed]() mutable { - Terminal::get().print(std::move(message)); - message_printed.set_value(); - }); - message_printed.get_future().get(); - } }, [this, quiet](const char *bytes, size_t n) { - if(!quiet) { - // Print stderr message sequentially to avoid the GUI becoming unresponsive - std::promise message_printed; - dispatcher.post([message = std::string(bytes, n), &message_printed]() mutable { - Terminal::get().print(std::move(message), true); - message_printed.set_value(); - }); - message_printed.get_future().get(); - } }, true); + command, path.string(), + [this, quiet](const char *bytes, size_t n) { + if(!quiet) { + // Print stdout message sequentially to avoid the GUI becoming unresponsive + std::promise message_printed; + dispatcher.post([message = std::string(bytes, n), &message_printed]() mutable { + Terminal::get().print(std::move(message)); + message_printed.set_value(); + }); + message_printed.get_future().get(); + } + }, + [this, quiet](const char *bytes, size_t n) { + if(!quiet) { + // Print stderr message sequentially to avoid the GUI becoming unresponsive + std::promise message_printed; + dispatcher.post([message = std::string(bytes, n), &message_printed]() mutable { + Terminal::get().print(std::move(message), true); + message_printed.set_value(); + }); + message_printed.get_future().get(); + } + }, + true); auto pid = process->get_id(); if(pid <= 0) { diff --git a/tests/process_test.cpp b/tests/process_test.cpp index 7acca6e..fd0664e 100644 --- a/tests/process_test.cpp +++ b/tests/process_test.cpp @@ -15,7 +15,13 @@ int main() { { TinyProcessLib::Process process( - "echo Test && ls an_incorrect_path", "", [output](const char *bytes, size_t n) { *output += std::string(bytes, n); }, [error](const char *bytes, size_t n) { *error += std::string(bytes, n); }); + "echo Test && ls an_incorrect_path", "", + [output](const char *bytes, size_t n) { + *output += std::string(bytes, n); + }, + [error](const char *bytes, size_t n) { + *error += std::string(bytes, n); + }); g_assert(process.get_exit_status() > 0); g_assert(output->substr(0, 4) == "Test"); g_assert(!error->empty()); From 9c0685cb010fa329cc6734a7e2c629a5f63cde78 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 15 Sep 2020 09:43:07 +0200 Subject: [PATCH 019/375] No longer scroll terminal to botton when for instance running prettier or clang-format --- src/terminal.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/terminal.cpp b/src/terminal.cpp index cfe1490..fd09b6f 100644 --- a/src/terminal.cpp +++ b/src/terminal.cpp @@ -205,9 +205,6 @@ Terminal::Terminal() : Source::SearchView() { } int Terminal::process(const std::string &command, const boost::filesystem::path &path, bool use_pipes) { - if(scroll_to_bottom) - scroll_to_bottom(); - std::unique_ptr process; if(use_pipes) process = std::make_unique( @@ -230,9 +227,6 @@ int Terminal::process(const std::string &command, const boost::filesystem::path } int Terminal::process(std::istream &stdin_stream, std::ostream &stdout_stream, const std::string &command, const boost::filesystem::path &path, std::ostream *stderr_stream) { - if(scroll_to_bottom) - scroll_to_bottom(); - TinyProcessLib::Process process( command, path.string(), [&stdout_stream](const char *bytes, size_t n) { From b24113ae569e1c831c8af27ed85e6e5b884854a3 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 15 Sep 2020 09:55:01 +0200 Subject: [PATCH 020/375] Improved terminal link tagging of Node.js error output --- src/terminal.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/terminal.cpp b/src/terminal.cpp index fd09b6f..4e10735 100644 --- a/src/terminal.cpp +++ b/src/terminal.cpp @@ -382,8 +382,8 @@ boost::optional Terminal::find_link(const std::string &line, siz "^[^:]*: ([A-Z]:)?([^:]+):([0-9]+): .* Assertion .* failed\\.$|" // gcc assert() "^ERROR:([A-Z]:)?([^:]+):([0-9]+):.*$|" // g_assert (glib.h) "^([A-Z]:)?([\\/][^:]+):([0-9]+)$|" // Node.js - "^ at .*?\\(([A-Z]:)?([\\/][^:]+):([0-9]+):([0-9]+)\\)$|" // Node.js stack trace - "^ at .*?\\(([A-Z]:)?([^:]+):([0-9]+):([0-9]+)\\)$|" // Node.js Jest + "^ +at .*?\\(([A-Z]:)?([^:]+):([0-9]+):([0-9]+)\\).*$|" // Node.js stack trace + "^ +at ([A-Z]:)?([^:]+):([0-9]+):([0-9]+).*$|" // Node.js stack trace "^ File \"([A-Z]:)?([^\"]+)\", line ([0-9]+), in .*$", // Python std::regex::optimize); std::smatch sm; From 06d9854e15af5b545718381c771f3f1fc22a445b Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 15 Sep 2020 09:59:16 +0200 Subject: [PATCH 021/375] Make terminal link tag higher priority than colored tags --- src/terminal.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/terminal.cpp b/src/terminal.cpp index 4e10735..318fe66 100644 --- a/src/terminal.cpp +++ b/src/terminal.cpp @@ -18,12 +18,6 @@ Terminal::Terminal() : Source::SearchView() { 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; - - invisible_tag = get_buffer()->create_tag(); - invisible_tag->property_invisible() = true; - red_tag = get_buffer()->create_tag(); green_tag = get_buffer()->create_tag(); yellow_tag = get_buffer()->create_tag(); @@ -32,6 +26,12 @@ Terminal::Terminal() : Source::SearchView() { cyan_tag = get_buffer()->create_tag(); gray_tag = get_buffer()->create_tag(); + link_tag = get_buffer()->create_tag(); + link_tag->property_underline() = Pango::Underline::UNDERLINE_SINGLE; + + invisible_tag = get_buffer()->create_tag(); + invisible_tag->property_invisible() = true; + link_mouse_cursor = Gdk::Cursor::create(Gdk::CursorType::HAND1); default_mouse_cursor = Gdk::Cursor::create(Gdk::CursorType::XTERM); From 5d1cd3282aa234101f1577a9058b3ec347168f3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Sverre=20Lien=20Sell=C3=A6g?= Date: Tue, 15 Sep 2020 11:07:06 +0200 Subject: [PATCH 022/375] add root hint to docs --- docs/language_servers.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/language_servers.md b/docs/language_servers.md index 0d4f6b5..c50f6c5 100644 --- a/docs/language_servers.md +++ b/docs/language_servers.md @@ -10,6 +10,7 @@ Install language server, and create executable to enable server in juCi++: ```sh npm install -g flow-bin +# usually as root: echo '#!/bin/bash flow lsp' > /usr/local/bin/javascript-language-server @@ -29,6 +30,7 @@ Install language server, and create executable to enable server in juCi++: ```sh npm install -g typescript-language-server typescript +# usually as root: echo '#!/bin/bash `npm root -g`/typescript-language-server/lib/cli.js --stdio' > /usr/local/bin/javascript-language-server @@ -50,6 +52,7 @@ Install language server, and create symbolic link to enable server in juCi++: ```sh pip3 install python-language-server[rope,pycodestyle,yapf] +# usually as root: ln -s `which pyls` /usr/local/bin/python-language-server ``` @@ -69,6 +72,7 @@ git clone https://github.com/rust-analyzer/rust-analyzer cd rust-analyzer cargo xtask install --server +# usually as root: ln -s ~/.cargo/bin/rust-analyzer /usr/local/bin/rust-language-server ``` From 25ce06b79c381dfb01a86b90a1be56de8853db92 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 15 Sep 2020 12:50:34 +0200 Subject: [PATCH 023/375] Added missing try around boost::property_tree::read_json call --- src/source_language_protocol.cpp | 85 +++++++++++++++++--------------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 254c6c2..7264458 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -269,54 +269,59 @@ void LanguageProtocol::Client::parse_server_message() { } } - 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(Config::get().log.language_server) { - std::cout << "language server: "; - boost::property_tree::write_json(std::cout, pt); - } + try { + 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(Config::get().log.language_server) { + std::cout << "language server: "; + boost::property_tree::write_json(std::cout, pt); + } - auto message_id = pt.get_optional("id"); - { - LockGuard lock(read_write_mutex); - if(auto result = pt.get_child_optional("result")) { - 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); - lock.unlock(); - function(*result, false); - lock.lock(); + auto message_id = pt.get_optional("id"); + { + LockGuard lock(read_write_mutex); + if(auto result = pt.get_child_optional("result")) { + 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); + lock.unlock(); + function(*result, false); + lock.lock(); + } } } - } - else if(auto error = pt.get_child_optional("error")) { - if(!Config::get().log.language_server) - 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); + else if(auto error = pt.get_child_optional("error")) { + if(!Config::get().log.language_server) + 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); + lock.unlock(); + function(*error, true); + lock.lock(); + } + } + } + else if(auto method = pt.get_optional("method")) { + if(auto params = pt.get_child_optional("params")) { lock.unlock(); - function(*error, true); + if(message_id) + handle_server_request(*message_id, *method, *params); + else + handle_server_notification(*method, *params); lock.lock(); } } } - else if(auto method = pt.get_optional("method")) { - if(auto params = pt.get_child_optional("params")) { - lock.unlock(); - if(message_id) - handle_server_request(*message_id, *method, *params); - else - handle_server_notification(*method, *params); - lock.lock(); - } - } + } + catch(...) { + Terminal::get().async_print("\e[31mError\e[m: failed to parse message from language server\n", true); } server_message_stream = std::stringstream(); From 2074b03b7aed28334031159cd00f2b269cab002a Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 15 Sep 2020 20:34:38 +0200 Subject: [PATCH 024/375] Added Posix file link tagging to terminal --- src/terminal.cpp | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/terminal.cpp b/src/terminal.cpp index 318fe66..e4b65e1 100644 --- a/src/terminal.cpp +++ b/src/terminal.cpp @@ -374,17 +374,18 @@ bool Terminal::on_motion_notify_event(GdkEventMotion *event) { } boost::optional Terminal::find_link(const std::string &line, size_t pos, size_t length) { - const static std::regex link_regex("^([A-Z]:)?([^:]+):([0-9]+):([0-9]+): .*$|" // C/C++ compile warning/error/rename usages - "^In file included from ([A-Z]:)?([^:]+):([0-9]+)[:,]$|" // C/C++ extra compile warning/error info - "^ from ([A-Z]:)?([^:]+):([0-9]+)[:,]$|" // C/C++ extra compile warning/error info (gcc) - "^ +--> ([A-Z]:)?([^:]+):([0-9]+):([0-9]+)$|" // Rust - "^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) - "^([A-Z]:)?([\\/][^:]+):([0-9]+)$|" // Node.js - "^ +at .*?\\(([A-Z]:)?([^:]+):([0-9]+):([0-9]+)\\).*$|" // Node.js stack trace - "^ +at ([A-Z]:)?([^:]+):([0-9]+):([0-9]+).*$|" // Node.js stack trace - "^ File \"([A-Z]:)?([^\"]+)\", line ([0-9]+), in .*$", // Python + const static std::regex link_regex("^([A-Z]:)?([^:]+):([0-9]+):([0-9]+): .*$|" // C/C++ compile warning/error/rename usages + "^In file included from ([A-Z]:)?([^:]+):([0-9]+)[:,]$|" // C/C++ extra compile warning/error info + "^ from ([A-Z]:)?([^:]+):([0-9]+)[:,]$|" // C/C++ extra compile warning/error info (gcc) + "^ +--> ([A-Z]:)?([^:]+):([0-9]+):([0-9]+)$|" // Rust + "^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) + "^([A-Z]:)?([\\\\/][^:]+):([0-9]+)$|" // Node.js + "^ +at .*?\\(([A-Z]:)?([^:]+):([0-9]+):([0-9]+)\\).*$|" // Node.js stack trace + "^ +at ([A-Z]:)?([^:]+):([0-9]+):([0-9]+).*$|" // Node.js stack trace + "^ File \"([A-Z]:)?([^\"]+)\", line ([0-9]+), in .*$|" // Python + "^.*?([A-Z]:)?([a-zA-Z0-9._\\\\/][a-zA-Z0-9._\\-\\\\/]*):([0-9]+):([0-9]+).*$", // Posix file:line:column std::regex::optimize); std::smatch sm; if(std::regex_match(line.cbegin() + pos, @@ -394,7 +395,8 @@ boost::optional Terminal::find_link(const std::string &line, siz size_t subs = (sub == 1 || sub == 1 + 4 + 3 + 3 || sub == 1 + 4 + 3 + 3 + 4 + 3 + 3 + 3 + 3 || - sub == 1 + 4 + 3 + 3 + 4 + 3 + 3 + 3 + 3 + 4) + sub == 1 + 4 + 3 + 3 + 4 + 3 + 3 + 3 + 3 + 4 || + sub == 1 + 4 + 3 + 3 + 4 + 3 + 3 + 3 + 3 + 4 + 4 + 3) ? 4 : 3; if(sm.length(sub + 1)) { From 04b5aace73461a5090720e3cda0ad5424cd5d640 Mon Sep 17 00:00:00 2001 From: eidheim Date: Sun, 20 Sep 2020 13:02:32 +0200 Subject: [PATCH 025/375] Fixes #440: segmentation fault when running regex on very long lines. Also minor cleanup of Terminall::find_link, and added extra Termina::find_link tests. --- src/terminal.cpp | 11 +++--- src/terminal.hpp | 2 +- tests/terminal_test.cpp | 79 ++++++++++++++++++++++++++++++++++++----- 3 files changed, 77 insertions(+), 15 deletions(-) diff --git a/src/terminal.cpp b/src/terminal.cpp index e4b65e1..e278878 100644 --- a/src/terminal.cpp +++ b/src/terminal.cpp @@ -373,7 +373,10 @@ bool Terminal::on_motion_notify_event(GdkEventMotion *event) { return Gtk::TextView::on_motion_notify_event(event); } -boost::optional Terminal::find_link(const std::string &line, size_t pos, size_t length) { +boost::optional Terminal::find_link(const std::string &line) { + if(line.size() >= 1000) // Due to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86164 + return {}; + const static std::regex link_regex("^([A-Z]:)?([^:]+):([0-9]+):([0-9]+): .*$|" // C/C++ compile warning/error/rename usages "^In file included from ([A-Z]:)?([^:]+):([0-9]+)[:,]$|" // C/C++ extra compile warning/error info "^ from ([A-Z]:)?([^:]+):([0-9]+)[:,]$|" // C/C++ extra compile warning/error info (gcc) @@ -385,12 +388,10 @@ boost::optional Terminal::find_link(const std::string &line, siz "^ +at .*?\\(([A-Z]:)?([^:]+):([0-9]+):([0-9]+)\\).*$|" // Node.js stack trace "^ +at ([A-Z]:)?([^:]+):([0-9]+):([0-9]+).*$|" // Node.js stack trace "^ File \"([A-Z]:)?([^\"]+)\", line ([0-9]+), in .*$|" // Python - "^.*?([A-Z]:)?([a-zA-Z0-9._\\\\/][a-zA-Z0-9._\\-\\\\/]*):([0-9]+):([0-9]+).*$", // Posix file:line:column + "^.*?([A-Z]:)?([a-zA-Z0-9._\\\\/][a-zA-Z0-9._\\-\\\\/]*):([0-9]+):([0-9]+).*$", // Posix path:line:column std::regex::optimize); std::smatch sm; - if(std::regex_match(line.cbegin() + pos, - line.cbegin() + (length == std::string::npos ? line.size() : std::min(pos + length, line.size())), - sm, link_regex)) { + if(std::regex_match(line, sm, link_regex)) { for(size_t sub = 1; sub < link_regex.mark_count();) { size_t subs = (sub == 1 || sub == 1 + 4 + 3 + 3 || diff --git a/src/terminal.hpp b/src/terminal.hpp index 0269fb9..a89673b 100644 --- a/src/terminal.hpp +++ b/src/terminal.hpp @@ -57,7 +57,7 @@ private: std::string path; int line, line_index; }; - boost::optional find_link(const std::string &line, size_t pos = 0, size_t length = std::string::npos); + static boost::optional find_link(const std::string &line); Mutex processes_mutex; std::vector> processes GUARDED_BY(processes_mutex); diff --git a/tests/terminal_test.cpp b/tests/terminal_test.cpp index 3fbb686..72e829a 100644 --- a/tests/terminal_test.cpp +++ b/tests/terminal_test.cpp @@ -26,7 +26,7 @@ int main() { // Testing links { - auto link = terminal.find_link("~/test/test.cc:7:41: error: expected ';' after expression."); + auto link = Terminal::find_link("~/test/test.cc:7:41: error: expected ';' after expression."); assert(link); assert(link->start_pos == 0); assert(link->end_pos == 19); @@ -35,7 +35,34 @@ int main() { assert(link->line_index == 41); } { - auto link = terminal.find_link("Assertion failed: (false), function main, file ~/test/test.cc, line 15."); + auto link = Terminal::find_link("In file included from ./test/test.cc:2,"); + assert(link); + assert(link->start_pos == 22); + assert(link->end_pos == 38); + assert(link->path == "./test/test.cc"); + assert(link->line == 2); + assert(link->line_index == 1); + } + { + auto link = Terminal::find_link(" from ./test/test.cc:2:"); + assert(link); + assert(link->start_pos == 22); + assert(link->end_pos == 38); + assert(link->path == "./test/test.cc"); + assert(link->line == 2); + assert(link->line_index == 1); + } + { + auto link = Terminal::find_link(" --> src/main.rs:16:4"); + assert(link); + assert(link->start_pos == 6); + assert(link->end_pos == 22); + assert(link->path == "src/main.rs"); + assert(link->line == 16); + assert(link->line_index == 4); + } + { + auto link = Terminal::find_link("Assertion failed: (false), function main, file ~/test/test.cc, line 15."); assert(link); assert(link->start_pos == 47); assert(link->end_pos == 70); @@ -44,7 +71,7 @@ int main() { assert(link->line_index == 1); } { - auto link = terminal.find_link("test: ~/examples/main.cpp:17: int main(int, char**): Assertion `false' failed."); + auto link = Terminal::find_link("test: ~/examples/main.cpp:17: int main(int, char**): Assertion `false' failed."); assert(link); assert(link->start_pos == 6); assert(link->end_pos == 28); @@ -53,7 +80,7 @@ int main() { assert(link->line_index == 1); } { - auto link = terminal.find_link("ERROR:~/test/test.cc:36:int main(): assertion failed: (false)"); + auto link = Terminal::find_link("ERROR:~/test/test.cc:36:int main(): assertion failed: (false)"); assert(link); assert(link->start_pos == 6); assert(link->end_pos == 23); @@ -62,16 +89,34 @@ int main() { assert(link->line_index == 1); } { - auto link = terminal.find_link(" --> src/main.rs:16:4"); + auto link = Terminal::find_link("/test/test.js:10"); assert(link); - assert(link->start_pos == 6); - assert(link->end_pos == 22); - assert(link->path == "src/main.rs"); + assert(link->start_pos == 0); + assert(link->end_pos == 16); + assert(link->path == "/test/test.js"); + assert(link->line == 10); + assert(link->line_index == 1); + } + { + auto link = Terminal::find_link(" at something (src/main.js:16:4)"); + assert(link); + assert(link->start_pos == 16); + assert(link->end_pos == 32); + assert(link->path == "src/main.js"); + assert(link->line == 16); + assert(link->line_index == 4); + } + { + auto link = Terminal::find_link(" at src/main.js:16:4"); + assert(link); + assert(link->start_pos == 5); + assert(link->end_pos == 21); + assert(link->path == "src/main.js"); assert(link->line == 16); assert(link->line_index == 4); } { - auto link = terminal.find_link(R"( File "/home/test/test.py", line 4, in )"); + auto link = Terminal::find_link(R"( File "/home/test/test.py", line 4, in )"); assert(link); assert(link->start_pos == 8); assert(link->end_pos == 35); @@ -79,6 +124,22 @@ int main() { assert(link->line == 4); assert(link->line_index == 1); } + { + auto link = Terminal::find_link("Testing ./posix_path/test.txt:10:20 here"); + assert(link); + assert(link->start_pos == 8); + assert(link->end_pos == 35); + assert(link->path == "./posix_path/test.txt"); + assert(link->line == 10); + assert(link->line_index == 20); + } + { + // Avoid https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86164 + std::string long_line; + for(size_t i = 0; i < 50000; ++i) + long_line += "x"; + assert(!Terminal::find_link("/home/test/test.txt:1:1: " + long_line)); + } // Testing print { From a89a965a8094434b51ec79273d189c4517867660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Sverre=20Lien=20Sell=C3=A6g?= Date: Thu, 17 Sep 2020 17:18:42 +0200 Subject: [PATCH 026/375] add support for glsl shaders closes #439 --- docs/language_servers.md | 14 ++++++++++++++ src/source.cpp | 7 +++++++ src/source_base.cpp | 2 +- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/language_servers.md b/docs/language_servers.md index c50f6c5..ac39a8e 100644 --- a/docs/language_servers.md +++ b/docs/language_servers.md @@ -78,3 +78,17 @@ ln -s ~/.cargo/bin/rust-analyzer /usr/local/bin/rust-language-server * Additional setup within a Rust project: * Add an empty `.rust-format` file to enable style format on save + +## GLSL +Install language server, and create a script to enable server in juCi++: +```sh +git clone https://github.com/svenstaro/glsl-language-server --recursive +cd glsl-language-server +mkdir build +cd build +cmake .. +make +# usually as root: +make install +echo "/usr/local/bin/glslls --stdin" > /usr/local/bin/glsl-language-server +``` diff --git a/src/source.cpp b/src/source.cpp index 74dd2ae..fdad44d 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -66,6 +66,13 @@ Glib::RefPtr Source::guess_language(const boost::filesystem::path language = language_manager->get_language("cpphdr"); else if(file_path.extension() == ".ts" || file_path.extension() == ".tsx" || file_path.extension() == ".jsx" || file_path.extension() == ".flow") language = language_manager->get_language("js"); + else if(file_path.extension() == ".vert" || // listed on https://github.com/KhronosGroup/glslang + file_path.extension() == ".frag" || + file_path.extension() == ".tesc" || + file_path.extension() == ".tese" || + file_path.extension() == ".geom" || + file_path.extension() == ".comp") + language = language_manager->get_language("glsl"); else if(!file_path.has_extension()) { for(auto &part : file_path) { if(part == "include") { diff --git a/src/source_base.cpp b/src/source_base.cpp index 367fc1f..bab0ef8 100644 --- a/src/source_base.cpp +++ b/src/source_base.cpp @@ -160,7 +160,7 @@ Source::BaseView::BaseView(const boost::filesystem::path &file_path, const Glib: language_id == "c-sharp" || language_id == "html" || language_id == "cuda" || language_id == "php" || language_id == "rust" || language_id == "swift" || language_id == "go" || language_id == "scala" || language_id == "opencl" || - language_id == "json" || language_id == "css") + language_id == "json" || language_id == "css" || language_id == "glsl") is_bracket_language = true; } From 67289736f973556b5a5bb1585d11967458fb0015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Sverre=20Lien=20Sell=C3=A6g?= Date: Mon, 21 Sep 2020 01:02:41 +0200 Subject: [PATCH 027/375] update docs to use sh instead of bash and add chmod for glsl --- docs/language_servers.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/language_servers.md b/docs/language_servers.md index ac39a8e..40dd30d 100644 --- a/docs/language_servers.md +++ b/docs/language_servers.md @@ -11,8 +11,7 @@ Install language server, and create executable to enable server in juCi++: npm install -g flow-bin # usually as root: -echo '#!/bin/bash -flow lsp' > /usr/local/bin/javascript-language-server +echo -e '#!`which sh`\nflow lsp' > /usr/local/bin/javascript-language-server chmod 755 /usr/local/bin/javascript-language-server ``` @@ -31,8 +30,7 @@ Install language server, and create executable to enable server in juCi++: npm install -g typescript-language-server typescript # usually as root: -echo '#!/bin/bash -`npm root -g`/typescript-language-server/lib/cli.js --stdio' > /usr/local/bin/javascript-language-server +echo -e '#!`which sh`\n`npm root -g`/typescript-language-server/lib/cli.js --stdio' > /usr/local/bin/javascript-language-server chmod 755 /usr/local/bin/javascript-language-server rm -f /usr/local/bin/typescript-language-server @@ -90,5 +88,6 @@ cmake .. make # usually as root: make install -echo "/usr/local/bin/glslls --stdin" > /usr/local/bin/glsl-language-server +echo -e "#!`which sh`\n/usr/local/bin/glslls --stdin" > /usr/local/bin/glsl-language-server +chmod 755 /usr/local/bin/glsl-language-server ``` From d96121fa5a5fbfd08ee8a89b1262d124a96ce2d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Sverre=20Lien=20Sell=C3=A6g?= Date: Thu, 24 Sep 2020 09:00:16 +0200 Subject: [PATCH 028/375] update to multiline echo and set shebangs to /bin/sh --- docs/language_servers.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/language_servers.md b/docs/language_servers.md index 40dd30d..65e6821 100644 --- a/docs/language_servers.md +++ b/docs/language_servers.md @@ -11,8 +11,8 @@ Install language server, and create executable to enable server in juCi++: npm install -g flow-bin # usually as root: -echo -e '#!`which sh`\nflow lsp' > /usr/local/bin/javascript-language-server - +echo '#!/bin/sh +flow lsp' > /usr/local/bin/javascript-language-server chmod 755 /usr/local/bin/javascript-language-server ``` @@ -30,7 +30,8 @@ Install language server, and create executable to enable server in juCi++: npm install -g typescript-language-server typescript # usually as root: -echo -e '#!`which sh`\n`npm root -g`/typescript-language-server/lib/cli.js --stdio' > /usr/local/bin/javascript-language-server +echo '#!/bin/sh +`npm root -g`/typescript-language-server/lib/cli.js --stdio' > /usr/local/bin/javascript-language-server chmod 755 /usr/local/bin/javascript-language-server rm -f /usr/local/bin/typescript-language-server @@ -88,6 +89,7 @@ cmake .. make # usually as root: make install -echo -e "#!`which sh`\n/usr/local/bin/glslls --stdin" > /usr/local/bin/glsl-language-server +echo '#!/bin/sh +/usr/local/bin/glslls --stdin' > /usr/local/bin/glsl-language-server chmod 755 /usr/local/bin/glsl-language-server ``` From f8301c264807ae8567a9494254e1c1f9b687e666 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Sverre=20Lien=20Sell=C3=A6g?= Date: Thu, 24 Sep 2020 09:17:52 +0200 Subject: [PATCH 029/375] remove unnecessary space --- src/files.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/files.hpp b/src/files.hpp index 058103c..e94e999 100644 --- a/src/files.hpp +++ b/src/files.hpp @@ -159,8 +159,8 @@ const std::string default_config_file = "source_center_cursor": "l", "source_cursor_history_back": "Left", "source_cursor_history_forward": "Right", - "source_show_completion_comment" : "Add completion keybinding to disable interactive autocompletion", - "source_show_completion" : "", + "source_show_completion_comment": "Add completion keybinding to disable interactive autocompletion", + "source_show_completion": "", "source_find_file": "p", "source_find_symbol": "f", "source_find_pattern": "f", From d8902c25f3216d573b3cda3e93ddd5c31f20dd82 Mon Sep 17 00:00:00 2001 From: eidheim Date: Thu, 24 Sep 2020 10:54:01 +0200 Subject: [PATCH 030/375] Removed unnecessary newline --- docs/language_servers.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/language_servers.md b/docs/language_servers.md index 65e6821..c0de42a 100644 --- a/docs/language_servers.md +++ b/docs/language_servers.md @@ -32,7 +32,6 @@ npm install -g typescript-language-server typescript # usually as root: echo '#!/bin/sh `npm root -g`/typescript-language-server/lib/cli.js --stdio' > /usr/local/bin/javascript-language-server - chmod 755 /usr/local/bin/javascript-language-server rm -f /usr/local/bin/typescript-language-server cp /usr/local/bin/javascript-language-server /usr/local/bin/typescript-language-server From 88b4900e3597bbb6a2120e75d2d8657e021cb2a9 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 25 Sep 2020 10:04:20 +0200 Subject: [PATCH 031/375] Added menu item Open With Default Application to right click directory menus --- src/directories.cpp | 24 +++++++++++++++++++++++- src/directories.hpp | 6 +++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/directories.cpp b/src/directories.cpp index 5508dc4..fc34c42 100644 --- a/src/directories.cpp +++ b/src/directories.cpp @@ -358,7 +358,7 @@ Directories::Directories() : Gtk::ListViewText(1) { 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.append(menu_item_new_separator); menu_item_rename.set_label("Rename"); menu_item_rename.signal_activate().connect([this] { @@ -468,6 +468,28 @@ Directories::Directories() : Gtk::ListViewText(1) { }); menu.append(menu_item_delete); + menu.append(menu_item_open_separator); + menu_root.append(menu_root_item_separator); + + auto open_label = "Open With Default Application"; + auto open_function = [this] { + if(menu_popup_row_path.empty()) + return; +#ifdef __APPLE__ + Terminal::get().async_process("open " + menu_popup_row_path.string(), "", nullptr, true); +#else + Terminal::get().async_process("xdg-open " + menu_popup_row_path.string(), "", nullptr, true); +#endif + }; + + menu_item_open.set_label(open_label); + menu_item_open.signal_activate().connect(open_function); + menu.append(menu_item_open); + + menu_root_item_open.set_label(open_label); + menu_root_item_open.signal_activate().connect(open_function); + menu_root.append(menu_root_item_open); + menu.show_all(); menu.accelerate(*this); diff --git a/src/directories.hpp b/src/directories.hpp index a06d7db..bfbe0ce 100644 --- a/src/directories.hpp +++ b/src/directories.hpp @@ -87,11 +87,15 @@ private: Gtk::Menu menu; Gtk::MenuItem menu_item_new_file; Gtk::MenuItem menu_item_new_folder; - Gtk::SeparatorMenuItem menu_item_separator; + Gtk::SeparatorMenuItem menu_item_new_separator; Gtk::MenuItem menu_item_rename; Gtk::MenuItem menu_item_delete; + Gtk::SeparatorMenuItem menu_item_open_separator; + Gtk::MenuItem menu_item_open; Gtk::Menu menu_root; Gtk::MenuItem menu_root_item_new_file; Gtk::MenuItem menu_root_item_new_folder; + Gtk::SeparatorMenuItem menu_root_item_separator; + Gtk::MenuItem menu_root_item_open; boost::filesystem::path menu_popup_row_path; }; From 3498b4bf82fccbcf97b3302b9cdb6ac57d30a416 Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 30 Sep 2020 09:55:41 +0200 Subject: [PATCH 032/375] Fixes LanguageManager::guess_language calls in certain circumstances: use filename as argument instead of entire path --- src/source.cpp | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/source.cpp b/src/source.cpp index fdad44d..117f98c 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -50,28 +50,29 @@ Glib::RefPtr Source::StyleSchemeManager::get_default() Glib::RefPtr 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); + auto filename = file_path.filename().string(); + auto content_type = Gio::content_type_guess(filename, nullptr, 0, result_uncertain); if(result_uncertain) content_type.clear(); - auto language = language_manager->guess_language(file_path.string(), content_type); + auto language = language_manager->guess_language(filename, content_type); if(!language) { - auto filename = file_path.filename().string(); + auto extension = file_path.extension().string(); if(filename == "CMakeLists.txt") language = language_manager->get_language("cmake"); else if(filename == "meson.build") language = language_manager->get_language("meson"); else if(filename == "Makefile") language = language_manager->get_language("makefile"); - else if(file_path.extension() == ".tcc") + else if(extension == ".tcc") language = language_manager->get_language("cpphdr"); - else if(file_path.extension() == ".ts" || file_path.extension() == ".tsx" || file_path.extension() == ".jsx" || file_path.extension() == ".flow") + else if(extension == ".ts" || extension == ".tsx" || extension == ".jsx" || extension == ".flow") language = language_manager->get_language("js"); - else if(file_path.extension() == ".vert" || // listed on https://github.com/KhronosGroup/glslang - file_path.extension() == ".frag" || - file_path.extension() == ".tesc" || - file_path.extension() == ".tese" || - file_path.extension() == ".geom" || - file_path.extension() == ".comp") + else if(extension == ".vert" || // listed on https://github.com/KhronosGroup/glslang + extension == ".frag" || + extension == ".tesc" || + extension == ".tese" || + extension == ".geom" || + extension == ".comp") language = language_manager->get_language("glsl"); else if(!file_path.has_extension()) { for(auto &part : file_path) { @@ -221,7 +222,7 @@ Source::View::View(const boost::filesystem::path &file_path, const Glib::RefPtr< if(language_id == "cmake" || language_id == "makefile" || language_id == "python" || language_id == "python3" || language_id == "sh" || language_id == "perl" || language_id == "ruby" || language_id == "r" || language_id == "asm" || - language_id == "automake" || language_id == "yaml") + language_id == "automake" || language_id == "yaml" || language_id == "docker") comment_characters = "#"; else if(language_id == "latex" || language_id == "matlab" || language_id == "octave" || language_id == "bibtex") comment_characters = "%"; From b59d4befe64853c7acbd6d6dab88114adf9f20b8 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 6 Oct 2020 09:59:08 +0200 Subject: [PATCH 033/375] Fixed markup on ctags results --- src/ctags.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ctags.cpp b/src/ctags.cpp index ccde85e..823a3a4 100644 --- a/src/ctags.cpp +++ b/src/ctags.cpp @@ -138,6 +138,7 @@ Ctags::Location Ctags::get_location(const std::string &line_, bool add_markup, b if(add_markup) { location.source = Glib::Markup::escape_text(location.source); std::string symbol = Glib::Markup::escape_text(location.symbol); + auto symbol_size = symbol.size(); if(symbol_ends_with_open_parenthesis) symbol += '('; bool first = true; @@ -145,9 +146,9 @@ Ctags::Location Ctags::get_location(const std::string &line_, bool add_markup, b for(size_t i = 0; i < location.source.size(); i++) { if(!escaped) { if(starts_with(location.source, i, symbol)) { - location.source.insert(i + symbol.size(), ""); + location.source.insert(i + symbol_size, ""); location.source.insert(i, ""); - i += 7 + symbol.size() - 1; + i += 7 + symbol_size - 1; first = false; } else { From 60e6730b78ffb4284f838129fa549b4d40b374dc Mon Sep 17 00:00:00 2001 From: eidheim Date: Mon, 5 Oct 2020 14:42:47 +0200 Subject: [PATCH 034/375] Improved support for non-ascii symbol names --- lib/libclangmm | 2 +- src/autocomplete.cpp | 3 +- src/autocomplete.hpp | 2 +- src/ctags.cpp | 6 ++-- src/debug_lldb.cpp | 2 +- src/selection_dialog.cpp | 2 +- src/source.cpp | 6 ++-- src/source_base.cpp | 47 +++++++++++++------------ src/source_base.hpp | 5 ++- src/source_clang.cpp | 59 ++++++++++++++++++++++---------- src/source_generic.cpp | 8 ++--- src/source_language_protocol.cpp | 53 ++++++++++++++++++++-------- src/source_spellcheck.cpp | 4 +-- src/usages_clang.cpp | 2 +- tests/source_test.cpp | 12 +++++++ 15 files changed, 137 insertions(+), 76 deletions(-) diff --git a/lib/libclangmm b/lib/libclangmm index c807211..fc0df20 160000 --- a/lib/libclangmm +++ b/lib/libclangmm @@ -1 +1 @@ -Subproject commit c807211edcd3894f0920fcc1c01476d898f93f8f +Subproject commit fc0df2022518af9a04b28e4964fb84ace5b1fe81 diff --git a/src/autocomplete.cpp b/src/autocomplete.cpp index 0c50990..6893cca 100644 --- a/src/autocomplete.cpp +++ b/src/autocomplete.cpp @@ -65,8 +65,7 @@ void Autocomplete::run() { if(pass_buffer_and_strip_word) { auto pos = iter.get_offset() - 1; buffer = view->get_buffer()->get_text(); - while(pos >= 0 && ((buffer[pos] >= 'a' && buffer[pos] <= 'z') || (buffer[pos] >= 'A' && buffer[pos] <= 'Z') || - (buffer[pos] >= '0' && buffer[pos] <= '9') || buffer[pos] == '_')) { + while(pos >= 0 && Source::BaseView::is_token_char(buffer[pos])) { buffer.replace(pos, 1, " "); column_nr--; pos--; diff --git a/src/autocomplete.hpp b/src/autocomplete.hpp index 353d876..f023fae 100644 --- a/src/autocomplete.hpp +++ b/src/autocomplete.hpp @@ -39,7 +39,7 @@ public: std::function()> get_parse_lock = [] { return nullptr; }; std::function stop_parse = [] {}; - std::function is_continue_key = [](guint keyval) { return (keyval >= '0' && keyval <= '9') || (keyval >= 'a' && keyval <= 'z') || (keyval >= 'A' && keyval <= 'Z') || keyval == '_' || gdk_keyval_to_unicode(keyval) >= 0x00C0; }; + std::function is_continue_key = [](guint keyval) { return Source::BaseView::is_token_char(gdk_keyval_to_unicode(keyval)); }; std::function is_restart_key = [](guint) { return false; }; std::function run_check = [] { return false; }; diff --git a/src/ctags.cpp b/src/ctags.cpp index 823a3a4..5550c3a 100644 --- a/src/ctags.cpp +++ b/src/ctags.cpp @@ -64,7 +64,7 @@ Ctags::Location Ctags::get_location(const std::string &line_, bool add_markup, b location.symbol = line.substr(0, symbol_end); if(9 < location.symbol.size() && location.symbol[8] == ' ' && starts_with(location.symbol, "operator")) { auto &chr = location.symbol[9]; - if(!((chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z') || (chr >= '0' && chr <= '9') || chr == '_')) + if(!Source::BaseView::is_token_char(chr)) location.symbol.erase(8, 1); } @@ -174,13 +174,13 @@ Ctags::Location Ctags::get_location(const std::string &line_, bool add_markup, b return location; } -///Split up a type into its various significant parts +// Split up a type into its various significant parts std::vector Ctags::get_type_parts(const std::string &type) { std::vector parts; size_t text_start = std::string::npos; 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(Source::BaseView::is_token_char(chr) || chr == '~') { if(text_start == std::string::npos) text_start = c; } diff --git a/src/debug_lldb.cpp b/src/debug_lldb.cpp index fcad197..c3aef2d 100644 --- a/src/debug_lldb.cpp +++ b/src/debug_lldb.cpp @@ -63,7 +63,7 @@ std::tuple, std::string, std::vector> Debu if(c > 0 && start_pos != std::string::npos) { auto argument = command.substr(start_pos, c - start_pos); if(executable.empty()) { - //Check for environment variable + // 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') || diff --git a/src/selection_dialog.cpp b/src/selection_dialog.cpp index 06bc213..c652b55 100644 --- a/src/selection_dialog.cpp +++ b/src/selection_dialog.cpp @@ -412,7 +412,7 @@ bool CompletionDialog::on_key_release(GdkEventKey *event) { } bool CompletionDialog::on_key_press(GdkEventKey *event) { - if((event->keyval >= '0' && event->keyval <= '9') || (event->keyval >= 'a' && event->keyval <= 'z') || (event->keyval >= 'A' && event->keyval <= 'Z') || event->keyval == '_' || gdk_keyval_to_unicode(event->keyval) >= 0x00C0 || event->keyval == GDK_KEY_BackSpace) { + if(Source::BaseView::is_token_char(gdk_keyval_to_unicode(event->keyval)) || event->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; diff --git a/src/source.cpp b/src/source.cpp index 117f98c..09ae0fd 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -1011,7 +1011,7 @@ void Source::View::extend_selection() { // It is impossible to identify <> used for templates by syntax alone, but // this function works in most cases. - auto is_template_arguments = [this](Gtk::TextIter start, Gtk::TextIter end) { + auto is_template_arguments = [](Gtk::TextIter start, Gtk::TextIter end) { if(*start != '<' || *end != '>' || start.get_line() != end.get_line()) return false; auto prev = start; @@ -1602,7 +1602,7 @@ void Source::View::show_or_hide() { else if(std::none_of(exact.begin(), exact.end(), [&text](const std::string &e) { return starts_with(text, e); }) && - std::none_of(followed_by_non_token_char.begin(), followed_by_non_token_char.end(), [this, &text](const std::string &e) { + std::none_of(followed_by_non_token_char.begin(), followed_by_non_token_char.end(), [&text](const std::string &e) { return starts_with(text, e) && text.size() > e.size() && !is_token_char(text[e.size()]); })) { end = get_buffer()->get_iter_at_line(end.get_line()); @@ -2622,7 +2622,7 @@ bool Source::View::on_key_press_event_bracket_language(GdkEventKey *event) { 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 == '_' || + if(is_token_char(*it) || *it == '-' || *it == ' ' || *it == '\t' || *it == '<' || *it == '>' || *it == '(' || *it == ':' || *it == '*' || *it == '&' || *it == '/' || it.ends_line() || !is_code_iter(it)) { continue; diff --git a/src/source_base.cpp b/src/source_base.cpp index bab0ef8..075712b 100644 --- a/src/source_base.cpp +++ b/src/source_base.cpp @@ -701,9 +701,7 @@ Gtk::TextIter Source::BaseView::get_tabs_end_iter() { } bool Source::BaseView::is_token_char(gunichar chr) { - if((chr >= 'A' && chr <= 'Z') || (chr >= 'a' && chr <= 'z') || (chr >= '0' && chr <= '9') || chr == '_' || chr >= 128) - return true; - return false; + return (chr >= 'A' && chr <= 'Z') || (chr >= 'a' && chr <= 'z') || (chr >= '0' && chr <= '9') || chr == '_' || chr == '$' || chr >= 128; } std::pair Source::BaseView::get_token_iters(Gtk::TextIter iter) { @@ -1424,63 +1422,70 @@ void Source::BaseView::insert_snippet(Gtk::TextIter iter, const std::string &sni } return false; }; - auto parse_variable = [&] { + + enum class ParseVariableResult { + parsed, + skipped, + not_found + }; + + auto parse_variable = [&]() -> ParseVariableResult { if(i >= snippet.size()) throw std::out_of_range("unexpected end"); if(compare_variable("TM_SELECTED_TEXT")) { Gtk::TextIter start, end; if(get_buffer()->get_selection_bounds(start, end)) { insert += get_buffer()->get_text(start, end); - return true; + return ParseVariableResult::parsed; } - return false; + return ParseVariableResult::skipped; } else if(compare_variable("TM_CURRENT_LINE")) { auto start = get_buffer()->get_iter_at_line(iter.get_line()); auto end = get_iter_at_line_end(iter.get_line()); insert += get_buffer()->get_text(start, end); erase_line = true; - return true; + return ParseVariableResult::parsed; } else if(compare_variable("TM_CURRENT_WORD")) { if(is_token_char(*iter)) { auto token_iters = get_token_iters(iter); insert += get_buffer()->get_text(token_iters.first, token_iters.second); erase_word = true; - return true; + return ParseVariableResult::parsed; } - return false; + return ParseVariableResult::skipped; } else if(compare_variable("TM_LINE_INDEX")) { insert += std::to_string(iter.get_line()); - return true; + return ParseVariableResult::parsed; } else if(compare_variable("TM_LINE_NUMBER")) { insert += std::to_string(iter.get_line() + 1); - return true; + return ParseVariableResult::parsed; } else if(compare_variable("TM_FILENAME_BASE")) { insert += file_path.stem().string(); - return true; + return ParseVariableResult::parsed; } else if(compare_variable("TM_FILENAME")) { insert += file_path.filename().string(); - return true; + return ParseVariableResult::parsed; } else if(compare_variable("TM_DIRECTORY")) { insert += file_path.parent_path().string(); - return true; + return ParseVariableResult::parsed; } else if(compare_variable("TM_FILEPATH")) { insert += file_path.string(); - return true; + return ParseVariableResult::parsed; } else if(compare_variable("CLIPBOARD")) { insert += Gtk::Clipboard::get()->wait_for_text(); - return true; + return ParseVariableResult::parsed; } // TODO: support other variables - return false; + return ParseVariableResult::not_found; }; std::function parse_snippet = [&](bool stop_at_curly_end) { @@ -1517,10 +1522,10 @@ void Source::BaseView::insert_snippet(Gtk::TextIter iter, const std::string &sni parameter_offsets_and_sizes_map[number].emplace_back(utf8_character_count(insert) - placeholder_character_count, placeholder_character_count); } else { - if(!parse_variable()) { + if(parse_variable() != ParseVariableResult::parsed) { if(snippet.at(i) == ':') { // Use default value ++i; - if(!parse_variable()) + if(parse_variable() != ParseVariableResult::parsed) parse_snippet(true); } } @@ -1535,8 +1540,8 @@ void Source::BaseView::insert_snippet(Gtk::TextIter iter, const std::string &sni } else if(parse_number(number)) parameter_offsets_and_sizes_map[number].emplace_back(utf8_character_count(insert), 0); - else - parse_variable(); + else if(parse_variable() == ParseVariableResult::not_found) + insert += '$'; } else { insert += snippet[i]; diff --git a/src/source_base.hpp b/src/source_base.hpp index c51510a..5dfa9d5 100644 --- a/src/source_base.hpp +++ b/src/source_base.hpp @@ -153,7 +153,10 @@ namespace Source { Gtk::TextIter get_tabs_end_iter(int line_nr); Gtk::TextIter get_tabs_end_iter(); - bool is_token_char(gunichar chr); + public: + static bool is_token_char(gunichar chr); + + protected: std::pair get_token_iters(Gtk::TextIter iter); std::string get_token(const Gtk::TextIter &iter); void cleanup_whitespace_characters(); diff --git a/src/source_clang.cpp b/src/source_clang.cpp index d7596ec..f1bebad 100644 --- a/src/source_clang.cpp +++ b/src/source_clang.cpp @@ -857,6 +857,7 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa autocomplete.run_check = [this]() { auto iter = get_buffer()->get_insert()->get_iter(); + auto prefix_end = iter; iter.backward_char(); if(!is_code_iter(iter)) return false; @@ -864,31 +865,57 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa enable_snippets = false; show_parameters = false; - auto line = ' ' + get_line_before(); - const static std::regex regex("^.*([a-zA-Z_\\)\\]\\>]|[^a-zA-Z0-9_][a-zA-Z_][a-zA-Z0-9_]*)(\\.|->)([a-zA-Z0-9_]*)$|" // . or -> - "^.*(::)([a-zA-Z0-9_]*)$|" // :: - "^.*[^a-zA-Z0-9_]([a-zA-Z_][a-zA-Z0-9_]{2,})$", // part of symbol - std::regex::optimize); - std::smatch sm; - if(std::regex_match(line, sm, regex)) { + size_t count = 0; + while(is_token_char(*iter) && iter.backward_char()) + ++count; + + auto prefix_start = iter; + if(prefix_start != prefix_end) + prefix_start.forward_char(); + + auto previous = iter; + if(*iter == '.') { + bool starts_with_num = false; + size_t count = 0; + while(iter.backward_char() && is_token_char(*iter)) { + ++count; + starts_with_num = Glib::Unicode::isdigit(*iter); + } + if((count >= 1 || *iter == ')' || *iter == ']') && !starts_with_num) { + { + LockGuard lock(autocomplete.prefix_mutex); + autocomplete.prefix = get_buffer()->get_text(prefix_start, prefix_end); + } + return true; + } + } + else if((previous.backward_char() && ((*previous == ':' && *iter == ':') || (*previous == '-' && *iter == '>')))) { + if(*iter == ':' || (previous.backward_char() && (is_token_char(*previous) || *previous == ')' || *previous == ']'))) { + { + LockGuard lock(autocomplete.prefix_mutex); + autocomplete.prefix = get_buffer()->get_text(prefix_start, prefix_end); + } + return true; + } + } + else if(count >= 3) { // part of symbol { LockGuard lock(autocomplete.prefix_mutex); - autocomplete.prefix = sm.length(2) ? sm[3].str() : sm.length(4) ? sm[5].str() : sm[6].str(); - if(!sm.length(2) && !sm.length(4)) - enable_snippets = true; + autocomplete.prefix = get_buffer()->get_text(prefix_start, prefix_end); } + enable_snippets = true; return true; } - else if(is_possible_argument()) { + if(is_possible_argument()) { show_parameters = true; LockGuard lock(autocomplete.prefix_mutex); autocomplete.prefix = ""; return true; } - else if(!interactive_completion) { + 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)) { + while(iter.backward_char() && is_token_char(*iter)) { } if(iter != end_iter) iter.forward_char(); @@ -1707,11 +1734,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file 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() == '_')) { + if(get_token(iter) == token.get_spelling()) { auto cursor = token.get_cursor(); auto kind = cursor.get_kind(); if(kind == clangmm::Cursor::Kind::FunctionDecl || kind == clangmm::Cursor::Kind::CXXMethod || diff --git a/src/source_generic.cpp b/src/source_generic.cpp index 9fa4759..a7d3f76 100644 --- a/src/source_generic.cpp +++ b/src/source_generic.cpp @@ -203,15 +203,11 @@ void Source::GenericView::setup_autocomplete() { autocomplete_insert.clear(); }; - autocomplete.is_restart_key = [](guint keyval) { - return false; - }; - autocomplete.run_check = [this]() { auto start = get_buffer()->get_insert()->get_iter(); auto end = start; size_t count = 0; - while(start.backward_char() && ((*start >= '0' && *start <= '9') || (*start >= 'a' && *start <= 'z') || (*start >= 'A' && *start <= 'Z') || *start == '_' || *start >= 0x00C0)) + while(start.backward_char() && is_token_char(*start)) ++count; if((start.is_start() || start.forward_char()) && count >= 3 && !(*start >= '0' && *start <= '9')) { LockGuard lock1(autocomplete.prefix_mutex); @@ -223,7 +219,7 @@ void Source::GenericView::setup_autocomplete() { 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)) { + while(iter.backward_char() && is_token_char(*iter)) { } if(iter != end_iter) iter.forward_char(); diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 7264458..269abaa 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -1531,6 +1531,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { autocomplete->run_check = [this, is_possible_jsx_property]() { auto iter = get_buffer()->get_insert()->get_iter(); + auto prefix_end = iter; iter.backward_char(); if(!is_code_iter(iter)) return false; @@ -1538,36 +1539,60 @@ void Source::LanguageProtocolView::setup_autocomplete() { autocomplete_enable_snippets = false; autocomplete_show_arguments = false; - auto line = ' ' + get_line_before(); - const static std::regex regex("^.*([a-zA-Z_\\)\\]\\>\"']|[^a-zA-Z0-9_][a-zA-Z_][a-zA-Z0-9_]*\\?{0,1})(\\.)([a-zA-Z0-9_]*)$|" // . - "^.*(::)([a-zA-Z0-9_]*)$|" // :: - "^.*[^a-zA-Z0-9_]([a-zA-Z_][a-zA-Z0-9_]{2,})$", // part of symbol - std::regex::optimize); - std::smatch sm; - if(std::regex_match(line, sm, regex)) { + size_t count = 0; + while(is_token_char(*iter) && iter.backward_char()) + ++count; + + auto prefix_start = iter; + if(prefix_start != prefix_end) + prefix_start.forward_char(); + + auto previous = iter; + if(*iter == '.') { + bool starts_with_num = false; + size_t count = 0; + while(iter.backward_char() && is_token_char(*iter)) { + ++count; + starts_with_num = Glib::Unicode::isdigit(*iter); + } + if((count >= 1 || *iter == ')' || *iter == ']' || *iter == '"' || *iter == '\'' || *iter == '?') && !starts_with_num) { + { + LockGuard lock(autocomplete->prefix_mutex); + autocomplete->prefix = get_buffer()->get_text(prefix_start, prefix_end); + } + return true; + } + } + else if((previous.backward_char() && *previous == ':' && *iter == ':')) { { LockGuard lock(autocomplete->prefix_mutex); - autocomplete->prefix = sm.length(2) ? sm[3].str() : sm.length(4) ? sm[5].str() : sm[6].str(); - if(!sm.length(2) && !sm.length(4)) - autocomplete_enable_snippets = true; + autocomplete->prefix = get_buffer()->get_text(prefix_start, prefix_end); + } + return true; + } + else if(count >= 3) { // part of symbol + { + LockGuard lock(autocomplete->prefix_mutex); + autocomplete->prefix = get_buffer()->get_text(prefix_start, prefix_end); } + autocomplete_enable_snippets = true; return true; } - else if(is_possible_jsx_property(iter)) { + if(is_possible_jsx_property(iter)) { LockGuard lock(autocomplete->prefix_mutex); autocomplete->prefix = ""; return true; } - else if(is_possible_argument()) { + if(is_possible_argument()) { autocomplete_show_arguments = true; LockGuard lock(autocomplete->prefix_mutex); autocomplete->prefix = ""; return true; } - else if(!interactive_completion) { + 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)) { + while(iter.backward_char() && is_token_char(*iter)) { } if(iter != end_iter) iter.forward_char(); diff --git a/src/source_spellcheck.cpp b/src/source_spellcheck.cpp index 787c709..b15c26c 100644 --- a/src/source_spellcheck.cpp +++ b/src/source_spellcheck.cpp @@ -495,9 +495,7 @@ bool Source::SpellCheckView::is_word_iter(const Gtk::TextIter &iter) { ++backslash_count; if(backslash_count % 2 == 1) return false; - if(*iter >= 0x2030) // Symbols and emojis - return false; - if(((*iter >= 'A' && *iter <= 'Z') || (*iter >= 'a' && *iter <= 'z') || *iter >= 128)) + if(Glib::Unicode::isalpha(*iter)) return true; if(*iter == '\'') return !is_code_iter(iter); diff --git a/src/usages_clang.cpp b/src/usages_clang.cpp index cb27cca..b6af6a7 100644 --- a/src/usages_clang.cpp +++ b/src/usages_clang.cpp @@ -588,7 +588,7 @@ std::pair, Usages::Cla const static std::regex include_regex(R"R(^#[ \t]*include[ \t]*"([^"]+)".*$)R", std::regex::optimize); auto is_spelling_char = [](char chr) { - return (chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z') || (chr >= '0' && chr <= '9') || chr == '_'; + return (chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z') || (chr >= '0' && chr <= '9') || chr == '_' || chr == '$' || static_cast(chr) >= 128; }; for(auto &path : paths) { diff --git a/tests/source_test.cpp b/tests/source_test.cpp index 0eb5018..10356a6 100644 --- a/tests/source_test.cpp +++ b/tests/source_test.cpp @@ -677,6 +677,18 @@ int main() { g_assert(buffer->get_insert()->get_iter().get_offset() == 13); } { + buffer->set_text(""); + view.insert_snippet(buffer->get_insert()->get_iter(), "test$test"); + g_assert(buffer->get_text() == "test$test"); + + buffer->set_text(""); + view.insert_snippet(buffer->get_insert()->get_iter(), "${TM_SELECTED_TEXT}"); + g_assert(buffer->get_text() == ""); + + buffer->set_text(""); + view.insert_snippet(buffer->get_insert()->get_iter(), "$TM_SELECTED_TEXT"); + g_assert(buffer->get_text() == ""); + buffer->set_text(""); view.insert_snippet(buffer->get_insert()->get_iter(), "\\$"); g_assert(buffer->get_text() == "$"); From e3f294f5c851f3a742045e2c19e47c7eecabd059 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 9 Oct 2020 12:30:58 +0200 Subject: [PATCH 035/375] Slight improvement to find_close_symbol_forward and find_open_symbol_backward --- src/source.cpp | 8 ++++---- tests/source_key_test.cpp | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/source.cpp b/src/source.cpp index 09ae0fd..b03bea8 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -1819,9 +1819,9 @@ bool Source::View::find_close_symbol_forward(Gtk::TextIter iter, Gtk::TextIter & else { long curly_count = 0; do { - if(*iter == positive_char && is_code_iter(iter)) + if(curly_count == 0 && *iter == positive_char && is_code_iter(iter)) count++; - else if(*iter == negative_char && is_code_iter(iter)) { + else if(curly_count == 0 && *iter == negative_char && is_code_iter(iter)) { if(count == 0) { found_iter = iter; return true; @@ -1859,14 +1859,14 @@ bool Source::View::find_open_symbol_backward(Gtk::TextIter iter, Gtk::TextIter & else { long curly_count = 0; do { - if(*iter == positive_char && is_code_iter(iter)) { + if(curly_count == 0 && *iter == positive_char && is_code_iter(iter)) { if(count == 0) { found_iter = iter; return true; } count++; } - else if(*iter == negative_char && is_code_iter(iter)) + else if(curly_count == 0 && *iter == negative_char && is_code_iter(iter)) count--; else if(*iter == '{' && is_code_iter(iter)) { if(curly_count == 0) diff --git a/tests/source_key_test.cpp b/tests/source_key_test.cpp index 8bb16ca..74ca2fc 100644 --- a/tests/source_key_test.cpp +++ b/tests/source_key_test.cpp @@ -2483,6 +2483,34 @@ int main() { iter.backward_chars(12); g_assert(buffer->get_insert()->get_iter() == iter); } + { + buffer->set_text(" test(\n" + "
{}}>"); + auto iter = buffer->end(); + buffer->place_cursor(iter); + view.on_key_press_event(&event); + g_assert(buffer->get_text() == " test(\n" + "
{}}>\n" + " \n" + "
"); + iter = buffer->end(); + iter.backward_chars(11); + g_assert(buffer->get_insert()->get_iter() == iter); + } + { + buffer->set_text(" test(\n" + " <>"); + auto iter = buffer->end(); + buffer->place_cursor(iter); + view.on_key_press_event(&event); + g_assert(buffer->get_text() == " test(\n" + " <>\n" + " \n" + " "); + iter = buffer->end(); + iter.backward_chars(8); + g_assert(buffer->get_insert()->get_iter() == iter); + } { buffer->set_text(" test(\n" "
\n" From 1722d1288ae74a94b95abf1ffd460d5d6b337229 Mon Sep 17 00:00:00 2001 From: eidheim Date: Sat, 10 Oct 2020 12:03:39 +0200 Subject: [PATCH 036/375] Fixed prettier error messages with ansi colors, and now add link tag to prettier error links --- src/source.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/source.cpp b/src/source.cpp index b03bea8..7000bcd 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -809,7 +809,7 @@ void Source::View::setup_format_style(bool is_generic_view) { } } else if(is_generic_view) { - const static std::regex regex(R"(^\[error\] [^:]*: (.*) \(([0-9]*):([0-9]*)\)$)", std::regex::optimize); + const static std::regex regex(R"(^\[.*error.*\] [^:]*: (.*) \(([0-9]*):([0-9]*)\)$)", std::regex::optimize); std::string line; std::getline(stderr_stream, line); std::smatch sm; @@ -825,7 +825,7 @@ void Source::View::setup_format_style(bool is_generic_view) { start.backward_char(); add_diagnostic_tooltip(start, end, true, [error_message = sm[1].str()](Tooltip &tooltip) { - tooltip.buffer->insert_at_cursor(error_message); + tooltip.insert_with_links_tagged(error_message); }); } catch(...) { From 4476df8b29bae9150c55b0ad2e2a905cb57bc29b Mon Sep 17 00:00:00 2001 From: eidheim Date: Mon, 26 Oct 2020 14:32:50 +0100 Subject: [PATCH 037/375] Updated minimum required cmake version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 041ec55..5bef971 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required (VERSION 2.8.8) +cmake_minimum_required(VERSION 3.0) project(juci) set(JUCI_VERSION "1.6.1.2") From dd9500da2a9f8d67cbcbce7dd4e593be0c1996b7 Mon Sep 17 00:00:00 2001 From: eidheim Date: Sun, 27 Dec 2020 09:42:21 +0100 Subject: [PATCH 038/375] Updated libclangmm submodule --- lib/libclangmm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/libclangmm b/lib/libclangmm index fc0df20..b342f4d 160000 --- a/lib/libclangmm +++ b/lib/libclangmm @@ -1 +1 @@ -Subproject commit fc0df2022518af9a04b28e4964fb84ace5b1fe81 +Subproject commit b342f4dd6de4fe509a692a4b4fcfc7e24aae9590 From 6d245cc02ac5ba52a6d2101da1b3a2a07f0ca804 Mon Sep 17 00:00:00 2001 From: eidheim Date: Sun, 27 Dec 2020 09:43:10 +0100 Subject: [PATCH 039/375] v1.6.2 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5bef971..5fb2cb0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.0) project(juci) -set(JUCI_VERSION "1.6.1.2") +set(JUCI_VERSION "1.6.2") set(CPACK_PACKAGE_NAME "jucipp") set(CPACK_PACKAGE_CONTACT "Ole Christian Eidheim ") From dae81957aa626a648cc7e7dc966ae49d4431fdf6 Mon Sep 17 00:00:00 2001 From: eidheim Date: Mon, 28 Dec 2020 19:14:33 +0100 Subject: [PATCH 040/375] Updated tiny-process-library submodule --- lib/tiny-process-library | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tiny-process-library b/lib/tiny-process-library index c9c8bf8..15e4f77 160000 --- a/lib/tiny-process-library +++ b/lib/tiny-process-library @@ -1 +1 @@ -Subproject commit c9c8bf810ddad8cd17882b9a9ee628a690e779f5 +Subproject commit 15e4f77f8254e4b093f6be128db50fe4b6bee120 From 4f9b3c5940a95eff2d13ddf8d08db7c946140322 Mon Sep 17 00:00:00 2001 From: eidheim Date: Mon, 28 Dec 2020 19:38:08 +0100 Subject: [PATCH 041/375] Fixed grep command: always show filename, even when folder contains only one file --- src/grep.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/grep.cpp b/src/grep.cpp index 270e7ca..8d3401d 100644 --- a/src/grep.cpp +++ b/src/grep.cpp @@ -29,15 +29,15 @@ Grep::Grep(const boost::filesystem::path &path, const std::string &pattern, bool if(extended_regex) flags += " -E"; - auto escaped_pattern = '"' + pattern + '"'; - for(size_t i = 1; i < escaped_pattern.size() - 1; ++i) { + auto escaped_pattern = " \"" + pattern + '"'; + for(size_t i = 2; i < escaped_pattern.size() - 1; ++i) { if(escaped_pattern[i] == '"') { escaped_pattern.insert(i, "\\"); ++i; } } - std::string command = Config::get().project.grep_command + " -R " + flags + " --color=always --binary-files=without-match " + exclude + " -n " + escaped_pattern + " *"; + std::string command = Config::get().project.grep_command + " -RHn --color=always --binary-files=without-match" + flags + exclude + escaped_pattern + " *"; std::stringstream stdin_stream; Terminal::get().process(stdin_stream, output, command, project_path); From 404ac10afa4a060945ef3030889fb8d7f4528798 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 29 Dec 2020 13:01:11 +0100 Subject: [PATCH 042/375] Go to Usage now shows filenames for all rows unless usages are in current file only --- src/window.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/window.cpp b/src/window.cpp index 9c62abd..ab5c59f 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -1157,20 +1157,19 @@ void Window::set_menu_actions() { std::vector rows; auto iter = view->get_buffer()->get_insert()->get_iter(); + std::sort(usages.begin(), usages.end()); + bool current_file_only = usages.begin()->first.file_path == usages.rbegin()->first.file_path && view->file_path == usages.begin()->first.file_path; 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) { + // Add file name if usage is not in current page + if(!current_file_only) 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 && + // Set dialog cursor to the last row if the textview cursor is at the same line + if((current_file_only || view->file_path == usage.first.file_path) && iter.get_line() == static_cast(usage.first.line) && iter.get_line_index() >= static_cast(usage.first.index)) { SelectionDialog::get()->set_cursor_at_last_row(); } From fa716a5d8c1c115a8bc5c683984d5f4f925affb4 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 29 Dec 2020 22:42:48 +0100 Subject: [PATCH 043/375] Updated cmake minimum required to 3.1 --- CMakeLists.txt | 2 +- lib/libclangmm | 2 +- src/window.cpp | 4 ++-- tests/lldb_test_files/CMakeLists.txt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5fb2cb0..237a2b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.1) project(juci) set(JUCI_VERSION "1.6.2") diff --git a/lib/libclangmm b/lib/libclangmm index b342f4d..1cdedf7 160000 --- a/lib/libclangmm +++ b/lib/libclangmm @@ -1 +1 @@ -Subproject commit b342f4dd6de4fe509a692a4b4fcfc7e24aae9590 +Subproject commit 1cdedf78937e659860e38d1cc51b182a30d24012 diff --git a/src/window.cpp b/src/window.cpp index ab5c59f..26a742e 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -340,7 +340,7 @@ void Window::set_menu_actions() { // Depending on default_build_management_system, generate build configuration if(Config::get().project.default_build_management_system == "cmake") { build_config_path = project_path / "CMakeLists.txt"; - build_config = "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"; + build_config = "cmake_minimum_required(VERSION 3.1)\n\nproject(" + project_name + ")\n\nset(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -std=c11 -Wall -Wextra\")\n\nadd_executable(" + project_name + " main.c)\n"; } else if(Config::get().project.default_build_management_system == "meson") { build_config_path = project_path / "meson.build"; @@ -390,7 +390,7 @@ void Window::set_menu_actions() { // Depending on default_build_management_system, generate build configuration if(Config::get().project.default_build_management_system == "cmake") { build_config_path = project_path / "CMakeLists.txt"; - build_config = "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"; + build_config = "cmake_minimum_required(VERSION 3.1)\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"; } else if(Config::get().project.default_build_management_system == "meson") { build_config_path = project_path / "meson.build"; diff --git a/tests/lldb_test_files/CMakeLists.txt b/tests/lldb_test_files/CMakeLists.txt index de74e74..ec256a6 100644 --- a/tests/lldb_test_files/CMakeLists.txt +++ b/tests/lldb_test_files/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.1) add_compile_options(-O0 -g) From 5d06b6fdddd29a4cdb1bf8ad1a8c4b72447f4f93 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 5 Jan 2021 10:54:04 +0100 Subject: [PATCH 044/375] Language client: slightly improved codeAction message where range is set to diagnostic range if only one diagnostic exists --- src/source_language_protocol.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 269abaa..0254999 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -996,20 +996,23 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vectorbegin(); - auto end = get_buffer()->end(); - auto range = "{\"start\":{\"line\": " + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(end.get_line()) + ",\"character\":" + std::to_string(end.get_line_offset()) + "}}"; + if(diagnostics.size() != 1) { // Use diagnostic range if only one diagnostic, otherwise use whole buffer + auto start = get_buffer()->begin(); + auto end = get_buffer()->end(); + range = "{\"start\":{\"line\": " + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(end.get_line()) + ",\"character\":" + std::to_string(end.get_line_offset()) + "}}"; + } auto request = (R"("textDocument":{"uri":")" + uri + "\"},\"range\":" + range + ",\"context\":{\"diagnostics\":[" + diagnostics_string + "],\"only\":[\"quickfix\"]}"); thread_pool.push([this, diagnostics = std::move(diagnostics), request = std::move(request), last_count]() mutable { From f853ae9d08fbab3abfefe21f4d2c8f2d78f95b77 Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 6 Jan 2021 13:25:38 +0100 Subject: [PATCH 045/375] Language client: workaround for buggy language servers that report double equal codeAction edits --- src/source.hpp | 15 ++++++++++++++- src/source_language_protocol.cpp | 8 ++++---- src/source_language_protocol.hpp | 2 +- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/source.hpp b/src/source.hpp index a6229e7..d7e0b86 100644 --- a/src/source.hpp +++ b/src/source.hpp @@ -30,7 +30,9 @@ namespace Source { Offset(unsigned line, unsigned index, boost::filesystem::path file_path_ = {}) : line(line), index(index), file_path(std::move(file_path_)) {} operator bool() const { return !file_path.empty(); } bool operator<(const Offset &rhs) const { - return file_path < rhs.file_path || (file_path == rhs.file_path && (line < rhs.line || (line == rhs.line && index < rhs.index))); + return file_path < rhs.file_path || + (file_path == rhs.file_path && (line < rhs.line || + (line == rhs.line && index < rhs.index))); } bool operator==(const Offset &rhs) const { return (file_path == rhs.file_path && line == rhs.line && index == rhs.index); } @@ -55,6 +57,17 @@ namespace Source { std::string source; std::string path; std::pair offsets; + + bool operator<(const FixIt &rhs) const { + + return type < rhs.type || + (type == rhs.type && (source < rhs.source || + (source == rhs.source && (path < rhs.path || + (path == rhs.path && offsets < rhs.offsets))))); + } + bool operator==(const FixIt &rhs) const { + return type == rhs.type && source == rhs.source && path == rhs.path && offsets == rhs.offsets; + } }; class View : public SpellCheckView, public DiffView { diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 0254999..a88fc13 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -1038,8 +1038,8 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vector{}); - pair.first->second.emplace_back( + auto pair = diagnostic.quickfixes.emplace(title, std::set{}); + pair.first->second.emplace( edit.new_text, filesystem::get_path_from_uri(file_it->first).string(), std::make_pair(Offset(edit.range.start.line, edit.range.start.character), @@ -1052,8 +1052,8 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vector{}); - pair.first->second.emplace_back( + auto pair = diagnostic.quickfixes.emplace(title, std::set{}); + pair.first->second.emplace( edit.new_text, filesystem::get_path_from_uri(file_it->first).string(), std::make_pair(Offset(edit.range.start.line, edit.range.start.character), diff --git a/src/source_language_protocol.hpp b/src/source_language_protocol.hpp index 4fa77e1..ba26ca9 100644 --- a/src/source_language_protocol.hpp +++ b/src/source_language_protocol.hpp @@ -71,7 +71,7 @@ namespace LanguageProtocol { Range range; int severity; std::vector related_informations; - std::map> quickfixes; + std::map> quickfixes; }; class TextEdit { From 7d1e49559f2e7244efa2f981fd4fddc0b76d0924 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 29 Jan 2021 13:00:11 +0100 Subject: [PATCH 046/375] Fixes #443: added preference item for keeping entry shown after running command --- CMakeLists.txt | 2 +- src/config.cpp | 1 + src/config.hpp | 1 + src/entrybox.cpp | 2 +- src/files.hpp | 3 ++- src/window.cpp | 5 ++++- 6 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 237a2b3..17dec30 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.1) project(juci) -set(JUCI_VERSION "1.6.2") +set(JUCI_VERSION "1.6.2.1") set(CPACK_PACKAGE_NAME "jucipp") set(CPACK_PACKAGE_CONTACT "Ole Christian Eidheim ") diff --git a/src/config.cpp b/src/config.cpp index da9a25e..d12e4bb 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -209,6 +209,7 @@ void Config::read(const boost::property_tree::ptree &cfg) { terminal.font = cfg.get("terminal.font"); terminal.clear_on_compile = cfg.get("terminal.clear_on_compile"); terminal.clear_on_run_command = cfg.get("terminal.clear_on_run_command"); + terminal.hide_entry_on_run_command = cfg.get("terminal.hide_entry_on_run_command"); log.libclang = cfg.get("log.libclang"); log.language_server = cfg.get("log.language_server"); diff --git a/src/config.hpp b/src/config.hpp index c954a78..ec44076 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -27,6 +27,7 @@ public: std::string font; bool clear_on_compile; bool clear_on_run_command; + bool hide_entry_on_run_command; }; class Project { diff --git a/src/entrybox.cpp b/src/entrybox.cpp index 4808400..7320f23 100644 --- a/src/entrybox.cpp +++ b/src/entrybox.cpp @@ -110,6 +110,6 @@ void EntryBox::show() { show_all(); if(entries.size() > 0) { entries.begin()->grab_focus(); - entries.begin()->select_region(0, entries.begin()->get_text_length()); + entries.begin()->select_region(0, -1); } } diff --git a/src/files.hpp b/src/files.hpp index e94e999..74a42b9 100644 --- a/src/files.hpp +++ b/src/files.hpp @@ -84,7 +84,8 @@ const std::string default_config_file = "font_comment": "Use \"\" to use source.font with slightly smaller size", "font": "", "clear_on_compile": true, - "clear_on_run_command": false + "clear_on_run_command": false, + "hide_entry_on_run_command": true }, "project": { "default_build_path_comment": "Use to insert the project top level directory name", diff --git a/src/window.cpp b/src/window.cpp index 26a742e..14fdfa8 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -1394,7 +1394,10 @@ void Window::set_menu_actions() { Terminal::get().async_print("\e[2m" + content + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); }); } - EntryBox::get().hide(); + if(Config::get().terminal.hide_entry_on_run_command) + EntryBox::get().hide(); + else + EntryBox::get().entries.front().select_region(0, -1); }, 30); auto entry_it = EntryBox::get().entries.begin(); From d4a1f165a855a711dd26c0db3c81999a174a28ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Sverre=20Lien=20Sell=C3=A6g?= Date: Sun, 7 Feb 2021 21:16:51 +0100 Subject: [PATCH 047/375] use ccache if it is installed to speed up development --- CMakeLists.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 17dec30..c556fdf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,16 @@ set(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}) set(CPACK_DEBIAN_PACKAGE_DEPENDS "cmake, make, g++, libclang-dev, liblldb-dev, clang-format, pkg-config, libboost-system-dev, libboost-filesystem-dev, libboost-serialization-dev libgtksourceviewmm-3.0-dev, aspell-en, libaspell-dev, libgit2-dev, universal-ctags") set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://gitlab.com/cppit/jucipp") set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) + +find_program(CCACHE_FOUND ccache) +if(CCACHE_FOUND) + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) + message(STATUS "using ccache.") +else() + message(STATUS "ccache was not found.") +endif(CCACHE_FOUND) + include(CPack) set(CMAKE_CXX_STANDARD 14) From 46a5e5dcd83ca10532081a08e0deaf3c166209cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Sverre=20Lien=20Sell=C3=A6g?= Date: Sun, 7 Feb 2021 21:02:57 +0100 Subject: [PATCH 048/375] disable dynamic registration of capabillities --- src/source_language_protocol.cpp | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index a88fc13..e0b4df0 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -134,36 +134,38 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang } write_request( nullptr, "initialize", "\"processId\":" + std::to_string(process_id) + R"(,"rootUri":")" + filesystem::get_uri_from_path(root_path) + R"(","capabilities": { - "workspace": { "symbol": { "dynamicRegistration": true } }, + "workspace": { + "symbol": { "dynamicRegistration": false } + }, "textDocument": { - "synchronization": { "dynamicRegistration": true, "didSave": true }, + "synchronization": { "dynamicRegistration": false, "didSave": true }, "completion": { - "dynamicRegistration": true, + "dynamicRegistration": false, "completionItem": { "snippetSupport": true, "documentationFormat": ["markdown", "plaintext"] } }, "hover": { - "dynamicRegistration": true, + "dynamicRegistration": false, "contentFormat": ["markdown", "plaintext"] }, "signatureHelp": { - "dynamicRegistration": true, + "dynamicRegistration": false, "signatureInformation": { "documentationFormat": ["markdown", "plaintext"] } }, - "definition": { "dynamicRegistration": true }, - "references": { "dynamicRegistration": true }, - "documentHighlight": { "dynamicRegistration": true }, - "documentSymbol": { "dynamicRegistration": true }, - "formatting": { "dynamicRegistration": true }, - "rangeFormatting": { "dynamicRegistration": true }, - "rename": { "dynamicRegistration": true }, + "definition": { "dynamicRegistration": false }, + "references": { "dynamicRegistration": false }, + "documentHighlight": { "dynamicRegistration": false }, + "documentSymbol": { "dynamicRegistration": false }, + "formatting": { "dynamicRegistration": false }, + "rangeFormatting": { "dynamicRegistration": false }, + "rename": { "dynamicRegistration": false }, "publishDiagnostics": { "relatedInformation":true }, "codeAction": { - "dynamicRegistration": true, + "dynamicRegistration": false, "codeActionLiteralSupport": { "codeActionKind": { "valueSet": ["quickfix"] } } From 8a8c21543a296c7ce68cb4c948532058d219dfa1 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 23 Feb 2021 11:23:19 +0100 Subject: [PATCH 049/375] Fixed potential crash when getting value from debug expression --- src/source_clang.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/source_clang.cpp b/src/source_clang.cpp index f1bebad..1332960 100644 --- a/src/source_clang.cpp +++ b/src/source_clang.cpp @@ -627,8 +627,8 @@ void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle) if(is_safe(cursor)) { // Do not call state altering expressions auto offsets = cursor.get_source_range().get_offsets(); - auto start = get_buffer()->get_iter_at_line_index(offsets.first.line - 1, offsets.first.index - 1); - auto end = get_buffer()->get_iter_at_line_index(offsets.second.line - 1, offsets.second.index - 1); + auto start = get_iter_at_line_index(offsets.first.line - 1, offsets.first.index - 1); + auto end = get_iter_at_line_index(offsets.second.line - 1, offsets.second.index - 1); std::string expression; // Get full expression from cursor parent: From 64205e2ee99e495648d6f2850c2f988f753103a6 Mon Sep 17 00:00:00 2001 From: eidheim Date: Thu, 25 Feb 2021 10:45:32 +0100 Subject: [PATCH 050/375] Improved smart insertions of '' and "" --- src/source.hpp | 3 ++- src/source_spellcheck.cpp | 42 +++++++++++++++++---------------------- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/src/source.hpp b/src/source.hpp index d7e0b86..6496847 100644 --- a/src/source.hpp +++ b/src/source.hpp @@ -4,6 +4,7 @@ #include "tooltips.hpp" #include #include +#include #include #include #include @@ -158,7 +159,7 @@ namespace Source { bool find_close_symbol_forward(Gtk::TextIter iter, Gtk::TextIter &found_iter, unsigned int positive_char, unsigned int negative_char); /// Iter will not be moved if iter is already at open symbol. bool find_open_symbol_backward(Gtk::TextIter iter, Gtk::TextIter &found_iter, unsigned int positive_char, unsigned int negative_char); - long symbol_count(Gtk::TextIter iter, unsigned int positive_char, unsigned int negative_char = 0); + long symbol_count(Gtk::TextIter iter, unsigned int positive_char, unsigned int negative_char = std::numeric_limits::max()); bool is_templated_function(Gtk::TextIter iter, Gtk::TextIter &parenthesis_end_iter); /// If insert is at an possible argument. Also based on last key press. bool is_possible_argument(); diff --git a/src/source_spellcheck.cpp b/src/source_spellcheck.cpp index b15c26c..cd0eb8e 100644 --- a/src/source_spellcheck.cpp +++ b/src/source_spellcheck.cpp @@ -387,8 +387,22 @@ bool Source::SpellCheckView::is_spellcheck_iter(const Gtk::TextIter &iter) { } } if(string_tag) { - if(iter.has_tag(string_tag) && !iter.begins_tag(string_tag)) + if(iter.has_tag(string_tag) && !iter.begins_tag(string_tag)) { + // Check for string literal symbols + if(*iter == '\'' || *iter == '"') { + auto previous_iter = iter; + if(previous_iter.backward_char()) { + if(previous_iter.begins_tag(string_tag)) { + if(*previous_iter == 'L' || *previous_iter == 'u' || *previous_iter == 'U' || *previous_iter == 'R') + return false; + } + else if(*previous_iter == '8' && previous_iter.backward_char() && previous_iter.begins_tag(string_tag) && + *previous_iter == 'u') + return false; + } + } 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; @@ -449,29 +463,9 @@ bool Source::SpellCheckView::is_code_iter(const Gtk::TextIter &iter) { if(!is_code_iter && (*iter == '\'' || *iter == '"')) { if(comment_tag && iter.ends_tag(comment_tag)) // ' or " at end of comments are not code iters return false; - if(*iter == '"') { - auto next_iter = iter; - next_iter.forward_char(); - return !is_spellcheck_iter(next_iter); - } - // Have to look up ' manually since it can be a word iter: - auto previous_iter = iter; - if(previous_iter.backward_char() && *previous_iter == '\'') { // First, handle iter inside '': - auto next_iter = iter; - next_iter.forward_char(); - return !is_spellcheck_iter(next_iter); - } - if(string_tag && (iter.has_tag(string_tag) || iter.ends_tag(string_tag))) { - long backslash_count = 0; - auto previous_iter = iter; - while(previous_iter.backward_char() && *previous_iter == '\\') - ++backslash_count; - if(backslash_count % 2 == 0) { - auto start_iter = iter; - if(start_iter.backward_to_tag_toggle(string_tag) && start_iter.begins_tag(string_tag) && *start_iter == '\'') - return true; - } - } + auto next_iter = iter; + next_iter.forward_char(); + return !is_spellcheck_iter(next_iter); } if(is_bracket_language) From 3b263156230948bda82bf1fd243150f47c3e1acf Mon Sep 17 00:00:00 2001 From: eidheim Date: Thu, 25 Feb 2021 11:14:00 +0100 Subject: [PATCH 051/375] Fixed debug stop status message for newer liblldb versions --- src/project.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/project.cpp b/src/project.cpp index d48af3e..397a303 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -437,9 +437,9 @@ void Project::LLDB::debug_start() { if(state == lldb::StateType::eStateStopped) { char buffer[100]; auto thread = process.GetSelectedThread(); - auto n = thread.GetStopDescription(buffer, 100); + auto n = thread.GetStopDescription(buffer, 100); // Returns number of bytes read. Might include null termination... Although maybe on newer versions only. if(n > 0) - status += " (" + std::string(buffer, n <= 100 ? n : 100) + ")"; + status += " (" + std::string(buffer, n <= 100 ? (buffer[n - 1] == '\0' ? n - 1 : n) : 100) + ")"; auto line_entry = thread.GetSelectedFrame().GetLineEntry(); if(line_entry.IsValid()) { lldb::SBStream stream; From a02e55e9e1fc1f51c3498ab4ccf88e633ed7d2e5 Mon Sep 17 00:00:00 2001 From: eidheim Date: Sat, 27 Feb 2021 12:52:03 +0100 Subject: [PATCH 052/375] Added support for Apple M1 --- CMakeLists.txt | 15 ++++++++++++++- lib/libclangmm | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c556fdf..87163ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,7 +43,20 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") endif() if(APPLE) - link_directories(/usr/local/lib /usr/local/opt/gettext/lib /usr/local/opt/libsigc++@2/lib) + # Added if expressions to avoid linking warnings: + if(EXISTS /usr/local/lib) + link_directories(/usr/local/lib) + endif() + if(EXISTS /usr/local/opt/gettext/lib) + link_directories(/usr/local/opt/gettext/lib) + endif() + if(EXISTS /usr/local/opt/libsigc++@2/lib) + link_directories(/usr/local/opt/libsigc++@2/lib) + endif() + if(EXISTS /opt/homebrew/lib) + link_directories(/opt/homebrew/lib) + endif() + include_directories(/usr/local/opt/gettext/include) set(CMAKE_MACOSX_RPATH 1) set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/usr/local/lib/pkgconfig:/opt/X11/lib/pkgconfig:/usr/local/opt/libffi/lib/pkgconfig:/usr/local/opt/libsigc++@2/lib/pkgconfig:/usr/local/opt/zlib/lib/pkgconfig:/usr/local/opt/libxml2/lib/pkgconfig") diff --git a/lib/libclangmm b/lib/libclangmm index 1cdedf7..1b48e1f 160000 --- a/lib/libclangmm +++ b/lib/libclangmm @@ -1 +1 @@ -Subproject commit 1cdedf78937e659860e38d1cc51b182a30d24012 +Subproject commit 1b48e1f4bd4f4ad2a6cb0102ac01dbf78c9efe57 From 2b99c04dcefd419b7b52a6f6cde6748cb8ec9091 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 2 Mar 2021 13:45:58 +0100 Subject: [PATCH 053/375] Updated libclangmm submodule --- lib/libclangmm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/libclangmm b/lib/libclangmm index 1b48e1f..a80c808 160000 --- a/lib/libclangmm +++ b/lib/libclangmm @@ -1 +1 @@ -Subproject commit 1b48e1f4bd4f4ad2a6cb0102ac01dbf78c9efe57 +Subproject commit a80c8081f599847ce0ea507d25a59565fda99ea0 From d23a707458103854e2d26508dd7c08e85c4b2dc6 Mon Sep 17 00:00:00 2001 From: Andreas Hammer Date: Fri, 5 Mar 2021 15:55:09 +0100 Subject: [PATCH 054/375] Fixed completion dialog wrap arround navigation --- src/selection_dialog.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/selection_dialog.cpp b/src/selection_dialog.cpp index c652b55..fc6ba80 100644 --- a/src/selection_dialog.cpp +++ b/src/selection_dialog.cpp @@ -432,6 +432,10 @@ bool CompletionDialog::on_key_press(GdkEventKey *event) { 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())); + cursor_changed(); + } } else list_view_text.set_cursor(list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); @@ -446,6 +450,14 @@ bool CompletionDialog::on_key_press(GdkEventKey *event) { 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)); + cursor_changed(); + } + } } else { auto last_it = list_view_text.get_model()->children().end(); From 08040b011b683e98361fc9426fa843a9a4d2447c Mon Sep 17 00:00:00 2001 From: eidheim Date: Sat, 6 Mar 2021 23:15:21 +0100 Subject: [PATCH 055/375] Selection dialog now also wraps arround navigation --- src/selection_dialog.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/selection_dialog.cpp b/src/selection_dialog.cpp index fc6ba80..ea68c91 100644 --- a/src/selection_dialog.cpp +++ b/src/selection_dialog.cpp @@ -269,6 +269,8 @@ bool SelectionDialog::on_key_press(GdkEventKey *event) { it++; if(it) list_view_text.set_cursor(list_view_text.get_model()->get_path(it)); + else + list_view_text.set_cursor(list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin())); } return true; } @@ -278,6 +280,12 @@ bool SelectionDialog::on_key_press(GdkEventKey *event) { it--; if(it) list_view_text.set_cursor(list_view_text.get_model()->get_path(it)); + else { + auto last_it = list_view_text.get_model()->children().end(); + last_it--; + if(last_it) + list_view_text.set_cursor(list_view_text.get_model()->get_path(last_it)); + } } return true; } From e08c39967f0d49357338977432e0ef3e4ef74812 Mon Sep 17 00:00:00 2001 From: eidheim Date: Sat, 6 Mar 2021 23:38:44 +0100 Subject: [PATCH 056/375] Debug::LLDB::get_variables no longer prefetch variable values --- src/debug_lldb.cpp | 15 +++++++++------ src/debug_lldb.hpp | 2 +- src/project.cpp | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/debug_lldb.cpp b/src/debug_lldb.cpp index c3aef2d..cb83b47 100644 --- a/src/debug_lldb.cpp +++ b/src/debug_lldb.cpp @@ -373,18 +373,21 @@ std::vector Debug::LLDB::get_variables() { auto frame = thread.GetFrameAtIndex(c_f); auto values = frame.GetVariables(true, true, true, false); for(uint32_t value_index = 0; value_index < values.GetSize(); value_index++) { - lldb::SBStream stream; - auto value = values.GetValueAtIndex(value_index); + auto value = std::make_shared(values.GetValueAtIndex(value_index)); Debug::LLDB::Variable variable; variable.thread_index_id = thread.GetIndexID(); variable.frame_index = c_f; - if(auto name = value.GetName()) + if(auto name = value->GetName()) variable.name = name; - value.GetDescription(stream); - variable.value = stream.GetData(); - auto declaration = value.GetDeclaration(); + variable.get_value = [value]() { + lldb::SBStream stream; + value->GetDescription(stream); + return std::string(stream.GetData()); + }; + + auto declaration = value->GetDeclaration(); if(declaration.IsValid()) { variable.declaration_found = true; variable.line_nr = declaration.GetLine(); diff --git a/src/debug_lldb.hpp b/src/debug_lldb.hpp index 21ea9ac..9507bb0 100644 --- a/src/debug_lldb.hpp +++ b/src/debug_lldb.hpp @@ -23,7 +23,7 @@ namespace Debug { uint32_t thread_index_id; uint32_t frame_index; std::string name; - std::string value; + std::function get_value; bool declaration_found; boost::filesystem::path file_path; int line_nr; diff --git a/src/project.cpp b/src/project.cpp index 397a303..73f4b2a 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -627,7 +627,7 @@ void Project::LLDB::debug_show_variables() { auto set_tooltip_buffer = [rows, index](Tooltip &tooltip) { auto variable = (*rows)[*index]; - Glib::ustring value = variable.value; + Glib::ustring value = variable.get_value(); if(!value.empty()) { Glib::ustring::iterator iter; while(!value.validate(iter)) { From 1d89eba3e438ddcf9ecaf2ce70108c081573be0f Mon Sep 17 00:00:00 2001 From: eidheim Date: Sun, 7 Mar 2021 08:11:57 +0100 Subject: [PATCH 057/375] Cleanup of Project::LLDB::debug_backtrace() and Project::LLDB::debug_show_variables() --- src/project.cpp | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src/project.cpp b/src/project.cpp index 73f4b2a..7e6ac9a 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -525,20 +525,20 @@ void Project::LLDB::debug_step_out() { void Project::LLDB::debug_backtrace() { if(debugging) { auto view = Notebook::get().get_current_view(); - auto backtrace = Debug::LLDB::get().get_backtrace(); + auto frames = Debug::LLDB::get().get_backtrace(); + + if(frames.size() == 0) { + Info::get().print("No backtrace found"); + return; + } if(view) SelectionDialog::create(view, true, true); else SelectionDialog::create(true, true); - std::vector rows; - if(backtrace.size() == 0) { - Info::get().print("No backtrace found"); - return; - } bool cursor_set = false; - for(auto &frame : backtrace) { + for(auto &frame : frames) { std::string row = "" + frame.module_filename + ""; //Shorten frame.function_name if it is too long @@ -551,7 +551,6 @@ void Project::LLDB::debug_backtrace() { auto file_path = frame.file_path.filename().string(); row += ":" + Glib::Markup::escape_text(file_path) + ":" + std::to_string(frame.line_nr) + " - " + Glib::Markup::escape_text(frame.function_name); } - rows.emplace_back(frame); SelectionDialog::get()->add_row(row); if(!cursor_set && view && frame.file_path == view->file_path) { SelectionDialog::get()->set_cursor_at_last_row(); @@ -559,8 +558,8 @@ void Project::LLDB::debug_backtrace() { } } - SelectionDialog::get()->on_select = [rows = std::move(rows)](unsigned int index, const std::string &text, bool hide_window) { - auto frame = rows[index]; + SelectionDialog::get()->on_select = [frames = std::move(frames)](unsigned int index, const std::string &text, bool hide_window) { + auto &frame = frames[index]; if(!frame.file_path.empty()) { if(Notebook::get().open(frame.file_path)) { Debug::LLDB::get().select_frame(frame.index); @@ -579,27 +578,26 @@ void Project::LLDB::debug_backtrace() { void Project::LLDB::debug_show_variables() { if(debugging) { auto view = Notebook::get().get_current_view(); - auto variables = Debug::LLDB::get().get_variables(); + auto variables = std::make_shared>(Debug::LLDB::get().get_variables()); + + if(variables->size() == 0) { + Info::get().print("No variables found"); + return; + } if(view) SelectionDialog::create(view, true, true); else SelectionDialog::create(true, true); - auto rows = std::make_shared>(); - if(variables.size() == 0) { - Info::get().print("No variables found"); - return; - } - for(auto &variable : variables) { + for(auto &variable : *variables) { std::string row = "#" + std::to_string(variable.thread_index_id) + ":#" + std::to_string(variable.frame_index) + ":" + variable.file_path.filename().string() + ":" + std::to_string(variable.line_nr) + " - " + Glib::Markup::escape_text(variable.name) + ""; - rows->emplace_back(variable); SelectionDialog::get()->add_row(row); } - SelectionDialog::get()->on_select = [rows](unsigned int index, const std::string &text, bool hide_window) { - auto variable = (*rows)[index]; + SelectionDialog::get()->on_select = [variables](unsigned int index, const std::string &text, bool hide_window) { + auto &variable = (*variables)[index]; Debug::LLDB::get().select_frame(variable.frame_index, variable.thread_index_id); if(!variable.file_path.empty()) { if(Notebook::get().open(variable.file_path)) { @@ -617,15 +615,15 @@ void Project::LLDB::debug_show_variables() { self->debug_variable_tooltips.clear(); }; - SelectionDialog::get()->on_change = [self = this->shared_from_this(), rows, view](boost::optional index, const std::string &text) { + SelectionDialog::get()->on_change = [self = this->shared_from_this(), variables, view](boost::optional index, const std::string &text) { if(!index) { self->debug_variable_tooltips.hide(); return; } self->debug_variable_tooltips.clear(); - auto set_tooltip_buffer = [rows, index](Tooltip &tooltip) { - auto variable = (*rows)[*index]; + auto set_tooltip_buffer = [variables, index](Tooltip &tooltip) { + auto &variable = (*variables)[*index]; Glib::ustring value = variable.get_value(); if(!value.empty()) { From 3d2f0810e1cbd5e53e0a6d1098d6a895269402e4 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 16 Mar 2021 09:53:14 +0100 Subject: [PATCH 058/375] Slight improvement to closing html element insertion --- src/source.cpp | 5 +++-- tests/source_key_test.cpp | 40 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/source.cpp b/src/source.cpp index 7000bcd..4db08b8 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -2424,8 +2424,9 @@ bool Source::View::on_key_press_event_bracket_language(GdkEventKey *event) { forward_to_code(close_element_iter); auto close_element_tabs_size = static_cast(get_tabs_end_iter(close_element_iter).get_line_offset()); if(!close_element_iter || close_element_tabs_size < tabs.size() || - (close_element_tabs_size == tabs.size() && *close_element_iter == '<' && close_element_iter.forward_char() && forward_to_code(close_element_iter) && - !(*close_element_iter == '/' && close_element_iter.forward_char() && forward_to_code(close_element_iter) && get_element(close_element_iter) == open_element_token))) { + (close_element_tabs_size == tabs.size() && (*close_element_iter == '{' || + (*close_element_iter == '<' && close_element_iter.forward_char() && forward_to_code(close_element_iter) && + !(*close_element_iter == '/' && close_element_iter.forward_char() && forward_to_code(close_element_iter) && get_element(close_element_iter) == open_element_token))))) { 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 + open_element_token.size() + 3)) { diff --git a/tests/source_key_test.cpp b/tests/source_key_test.cpp index 74ca2fc..1bdf942 100644 --- a/tests/source_key_test.cpp +++ b/tests/source_key_test.cpp @@ -2483,6 +2483,46 @@ int main() { iter.backward_chars(12); g_assert(buffer->get_insert()->get_iter() == iter); } + { + buffer->set_text(" test(\n" + "
\n" + "
\n" + "
\n"); + auto iter = buffer->get_iter_at_line(3); + iter.backward_char(); + buffer->place_cursor(iter); + view.on_key_press_event(&event); + g_assert(buffer->get_text() == " test(\n" + "
\n" + "
\n" + " \n" + "
\n" + "
\n"); + iter = buffer->get_iter_at_line(4); + iter.backward_char(); + g_assert(buffer->get_insert()->get_iter() == iter); + } + { + buffer->set_text(" test(\n" + "
\n" + "
\n" + " {'hello'}\n" + "
\n"); + auto iter = buffer->get_iter_at_line(3); + iter.backward_char(); + buffer->place_cursor(iter); + view.on_key_press_event(&event); + g_assert(buffer->get_text() == " test(\n" + "
\n" + "
\n" + " \n" + "
\n" + " {'hello'}\n" + "
\n"); + iter = buffer->get_iter_at_line(4); + iter.backward_char(); + g_assert(buffer->get_insert()->get_iter() == iter); + } { buffer->set_text(" test(\n" "
{}}>"); From 98e89fccec73ef0682d79bb16b2cb179cd134654 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 16 Mar 2021 13:26:56 +0100 Subject: [PATCH 059/375] Improved extend selection on markdown code blocks --- src/source.cpp | 38 ++++++++++++++++++++++++++++++++------ src/source_spellcheck.cpp | 12 ++++++------ src/source_spellcheck.hpp | 2 +- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/source.cpp b/src/source.cpp index 4db08b8..e64e33b 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -1398,19 +1398,17 @@ void Source::View::extend_selection() { return tabs; }; - // Forward to code iter - forward_to_code(start_stored); - if(start_stored > end_stored) - end_stored = start_stored; - // Forward start to non-empty line start = start_stored; + forward_to_code(start); start = get_buffer()->get_iter_at_line(start.get_line()); - while(!start.is_end() && (*start == ' ' || *start == '\t') && start.forward_char()) { + while(!start.is_end() && (*start == ' ' || *start == '\t' || start.ends_line()) && start.forward_char()) { } // Forward end to end of line end = end_stored; + if(start > end) + end = start; if(!end.ends_line()) end.forward_to_line_end(); @@ -1482,6 +1480,34 @@ void Source::View::extend_selection() { end = get_buffer()->end(); } } + + // Select no_spellcheck_tag block if markdown, and not about to select line + if(no_spellcheck_tag && language->get_id() == "markdown" && start_stored.has_tag(no_spellcheck_tag) && end_stored.has_tag(no_spellcheck_tag) && + !(start.starts_line() && end.ends_line() && start.has_tag(no_spellcheck_tag) && end.has_tag(no_spellcheck_tag))) { + start = start_stored; + end = end_stored; + if(!start.starts_tag(no_spellcheck_tag)) + start.backward_to_tag_toggle(no_spellcheck_tag); + if(!end.ends_tag(no_spellcheck_tag)) + end.forward_to_tag_toggle(no_spellcheck_tag); + auto prev = start; + while(*start == '`' && start.forward_char()) { + } + if(start.get_offset() - prev.get_offset() > 1) { + start.forward_to_line_end(); + start.forward_char(); + } + while(end.backward_char() && *end == '`') { + } + if(!end.ends_line()) + end.forward_char(); + + if(start == start_stored && end == end_stored) { + start = get_buffer()->begin(); + end = get_buffer()->end(); + } + } + get_buffer()->select_range(start, end); return; } diff --git a/src/source_spellcheck.cpp b/src/source_spellcheck.cpp index cd0eb8e..a0a787f 100644 --- a/src/source_spellcheck.cpp +++ b/src/source_spellcheck.cpp @@ -206,7 +206,7 @@ Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path, 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; + no_spellcheck_tag = tag; }); signal_tag_removed_connection = get_buffer()->get_tag_table()->signal_tag_removed().connect([this](const Glib::RefPtr &tag) { if(tag->property_name() == "gtksourceview:context-classes:comment") @@ -214,7 +214,7 @@ Source::SpellCheckView::SpellCheckView(const boost::filesystem::path &file_path, 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(); + no_spellcheck_tag.reset(); }); } @@ -342,8 +342,8 @@ bool Source::SpellCheckView::is_spellcheck_iter(const Gtk::TextIter &iter) { return true; } if(spellcheck_all) { - if(no_spell_check_tag) { - if(iter.has_tag(no_spell_check_tag) || iter.begins_tag(no_spell_check_tag) || iter.ends_tag(no_spell_check_tag)) + if(no_spellcheck_tag) { + if(iter.has_tag(no_spellcheck_tag) || iter.begins_tag(no_spellcheck_tag) || iter.ends_tag(no_spellcheck_tag)) return false; // workaround for gtksourceview bug if(iter.ends_line()) { @@ -352,7 +352,7 @@ bool Source::SpellCheckView::is_spellcheck_iter(const Gtk::TextIter &iter) { 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()) + if(next_iter.begins_tag(no_spellcheck_tag) || next_iter.is_end()) return false; } } @@ -360,7 +360,7 @@ bool Source::SpellCheckView::is_spellcheck_iter(const Gtk::TextIter &iter) { // for example, mark first " as not spellcheck 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)) + if(previous_iter.backward_char() && *previous_iter != '\'' && *previous_iter != '\"' && previous_iter.ends_tag(no_spellcheck_tag)) return false; } } diff --git a/src/source_spellcheck.hpp b/src/source_spellcheck.hpp index ab4d4ee..5d4d755 100644 --- a/src/source_spellcheck.hpp +++ b/src/source_spellcheck.hpp @@ -28,9 +28,9 @@ namespace Source { Glib::RefPtr comment_tag; Glib::RefPtr string_tag; + Glib::RefPtr no_spellcheck_tag; private: - Glib::RefPtr no_spell_check_tag; Glib::RefPtr spellcheck_error_tag; sigc::connection signal_tag_added_connection; From ba6ebe81622aa5fd18565e7410019720c40fac13 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 16 Mar 2021 15:20:13 +0100 Subject: [PATCH 060/375] Removed redundant check --- src/source.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/source.cpp b/src/source.cpp index e64e33b..70e77ef 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -1402,7 +1402,7 @@ void Source::View::extend_selection() { start = start_stored; forward_to_code(start); start = get_buffer()->get_iter_at_line(start.get_line()); - while(!start.is_end() && (*start == ' ' || *start == '\t' || start.ends_line()) && start.forward_char()) { + while(!start.is_end() && (*start == ' ' || *start == '\t') && start.forward_char()) { } // Forward end to end of line From 28ed5fef6017209dc8bf34015a63acdad599bc75 Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 17 Mar 2021 10:59:41 +0100 Subject: [PATCH 061/375] Slight correction of extend_selection() for markdown code blocks --- src/source.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/source.cpp b/src/source.cpp index 70e77ef..f4b4253 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -1481,9 +1481,9 @@ void Source::View::extend_selection() { } } - // Select no_spellcheck_tag block if markdown, and not about to select line + // Select no_spellcheck_tag block if markdown if(no_spellcheck_tag && language->get_id() == "markdown" && start_stored.has_tag(no_spellcheck_tag) && end_stored.has_tag(no_spellcheck_tag) && - !(start.starts_line() && end.ends_line() && start.has_tag(no_spellcheck_tag) && end.has_tag(no_spellcheck_tag))) { + (!start.has_tag(no_spellcheck_tag) || !end.has_tag(no_spellcheck_tag))) { start = start_stored; end = end_stored; if(!start.starts_tag(no_spellcheck_tag)) From 6dd97e41e68bfc5e311e04ebe9d1b4948e31ba86 Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 17 Mar 2021 13:40:10 +0100 Subject: [PATCH 062/375] Added LanguageProtocol::Diagnostic::code --- src/source_language_protocol.cpp | 9 +++++++-- src/source_language_protocol.hpp | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index e0b4df0..28c316f 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -41,7 +41,7 @@ LanguageProtocol::Location::Location(const boost::property_tree::ptree &pt, std: LanguageProtocol::Diagnostic::RelatedInformation::RelatedInformation(const boost::property_tree::ptree &pt) : message(pt.get("message")), location(pt.get_child("location")) {} -LanguageProtocol::Diagnostic::Diagnostic(const boost::property_tree::ptree &pt) : message(pt.get("message")), range(pt.get_child("range")), severity(pt.get("severity", 0)) { +LanguageProtocol::Diagnostic::Diagnostic(const boost::property_tree::ptree &pt) : message(pt.get("message")), range(pt.get_child("range")), severity(pt.get("severity", 0)), code(pt.get("code", "")) { auto related_information_it = pt.get_child("relatedInformation", boost::property_tree::ptree()); for(auto it = related_information_it.begin(); it != related_information_it.end(); ++it) related_informations.emplace_back(it->second); @@ -1008,7 +1008,12 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vectorbegin(); diff --git a/src/source_language_protocol.hpp b/src/source_language_protocol.hpp index ba26ca9..cfd5b28 100644 --- a/src/source_language_protocol.hpp +++ b/src/source_language_protocol.hpp @@ -69,7 +69,9 @@ namespace LanguageProtocol { Diagnostic(const boost::property_tree::ptree &pt); std::string message; Range range; + /// 1: error, 2: warning, 3: information, 4: hint int severity; + std::string code; std::vector related_informations; std::map> quickfixes; }; From 1a93e3fdf60f439a878d0bb0f5ca159d9326f5f1 Mon Sep 17 00:00:00 2001 From: eidheim Date: Thu, 18 Mar 2021 10:14:23 +0100 Subject: [PATCH 063/375] LanguageClient: added support for CodeAction response with edit.documentChanges --- src/source_language_protocol.cpp | 103 ++++++++++++++++++++++--------- src/source_language_protocol.hpp | 8 +++ 2 files changed, 82 insertions(+), 29 deletions(-) diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 28c316f..d54cc74 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -49,6 +49,12 @@ LanguageProtocol::Diagnostic::Diagnostic(const boost::property_tree::ptree &pt) LanguageProtocol::TextEdit::TextEdit(const boost::property_tree::ptree &pt, std::string new_text_) : range(pt.get_child("range")), new_text(new_text_.empty() ? pt.get("newText") : std::move(new_text_)) {} +LanguageProtocol::TextDocumentEdit::TextDocumentEdit(const boost::property_tree::ptree &pt) : file(filesystem::get_path_from_uri(pt.get("textDocument.uri", "")).string()) { + auto edits_it = pt.get_child("edits", boost::property_tree::ptree()); + for(auto it = edits_it.begin(); it != edits_it.end(); ++it) + edits.emplace_back(it->second); +} + LanguageProtocol::Client::Client(boost::filesystem::path root_path_, std::string language_id_) : root_path(std::move(root_path_)), language_id(std::move(language_id_)) { process = std::make_unique( filesystem::escape_argument(language_id + "-language-server"), root_path.string(), @@ -774,16 +780,9 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { } else if(auto changes_pt = result.get_child_optional("documentChanges")) { for(auto change_it = changes_pt->begin(); change_it != changes_pt->end(); ++change_it) { - if(auto document = change_it->second.get_child_optional("textDocument")) { - auto file = filesystem::get_path_from_uri(document->get("uri", "")); - if(filesystem::file_in_path(file, project_path)) { - std::vector edits; - 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) - edits.emplace_back(edit_it->second); - changes.emplace_back(Changes{file.string(), std::move(edits)}); - } - } + LanguageProtocol::TextDocumentEdit text_document_edit(change_it->second); + if(filesystem::file_in_path(text_document_edit.file, project_path)) + changes.emplace_back(Changes{std::move(text_document_edit.file), std::move(text_document_edit.edits)}); } } } @@ -1030,21 +1029,36 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vectorsecond.get("kind") == "quickfix") { + auto kind = it->second.get("kind", ""); + if(kind == "quickfix" || kind.empty()) { // Workaround for typescript-language-server (kind.empty()) auto title = it->second.get("title"); std::vector quickfix_diagnostics; if(auto diagnostics_pt = it->second.get_child_optional("diagnostics")) { for(auto it = diagnostics_pt->begin(); it != diagnostics_pt->end(); ++it) quickfix_diagnostics.emplace_back(it->second); } - auto changes = it->second.get_child("edit.changes"); - for(auto file_it = changes.begin(); file_it != changes.end(); ++file_it) { - for(auto edit_it = file_it->second.begin(); edit_it != file_it->second.end(); ++edit_it) { - LanguageProtocol::TextEdit edit(edit_it->second); - if(!quickfix_diagnostics.empty()) { - for(auto &diagnostic : diagnostics) { - for(auto &quickfix_diagnostic : quickfix_diagnostics) { - if(diagnostic.message == quickfix_diagnostic.message && diagnostic.range == quickfix_diagnostic.range) { + if(auto changes = it->second.get_child_optional("edit.changes")) { + for(auto file_it = changes->begin(); file_it != changes->end(); ++file_it) { + for(auto edit_it = file_it->second.begin(); edit_it != file_it->second.end(); ++edit_it) { + LanguageProtocol::TextEdit edit(edit_it->second); + if(!quickfix_diagnostics.empty()) { + for(auto &diagnostic : diagnostics) { + for(auto &quickfix_diagnostic : quickfix_diagnostics) { + if(diagnostic.message == quickfix_diagnostic.message && diagnostic.range == quickfix_diagnostic.range) { + auto pair = diagnostic.quickfixes.emplace(title, std::set{}); + pair.first->second.emplace( + edit.new_text, + filesystem::get_path_from_uri(file_it->first).string(), + std::make_pair(Offset(edit.range.start.line, edit.range.start.character), + Offset(edit.range.end.line, edit.range.end.character))); + break; + } + } + } + } + else { // Workaround for language server that does not report quickfix diagnostics + for(auto &diagnostic : diagnostics) { + if(edit.range.start.line == diagnostic.range.start.line) { auto pair = diagnostic.quickfixes.emplace(title, std::set{}); pair.first->second.emplace( edit.new_text, @@ -1056,16 +1070,47 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vector{}); - pair.first->second.emplace( - edit.new_text, - filesystem::get_path_from_uri(file_it->first).string(), - std::make_pair(Offset(edit.range.start.line, edit.range.start.character), - Offset(edit.range.end.line, edit.range.end.character))); - break; + } + } + else { + auto changes_pt = it->second.get_child_optional("edit.documentChanges"); + if(!changes_pt) { // Workaround for typescript-language-server + if(auto arguments_pt = it->second.get_child_optional("arguments")) { + if(!arguments_pt->empty()) + changes_pt = arguments_pt->begin()->second.get_child_optional("documentChanges"); + } + } + if(changes_pt) { + for(auto change_it = changes_pt->begin(); change_it != changes_pt->end(); ++change_it) { + LanguageProtocol::TextDocumentEdit text_document_edit(change_it->second); + for(auto &edit : text_document_edit.edits) { + if(!quickfix_diagnostics.empty()) { + for(auto &diagnostic : diagnostics) { + for(auto &quickfix_diagnostic : quickfix_diagnostics) { + if(diagnostic.message == quickfix_diagnostic.message && diagnostic.range == quickfix_diagnostic.range) { + auto pair = diagnostic.quickfixes.emplace(title, std::set{}); + pair.first->second.emplace( + edit.new_text, + text_document_edit.file, + std::make_pair(Offset(edit.range.start.line, edit.range.start.character), + Offset(edit.range.end.line, edit.range.end.character))); + break; + } + } + } + } + else { // Workaround for language server that does not report quickfix diagnostics + for(auto &diagnostic : diagnostics) { + if(edit.range.start.line == diagnostic.range.start.line) { + auto pair = diagnostic.quickfixes.emplace(title, std::set{}); + pair.first->second.emplace( + edit.new_text, + text_document_edit.file, + std::make_pair(Offset(edit.range.start.line, edit.range.start.character), + Offset(edit.range.end.line, edit.range.end.character))); + break; + } + } } } } diff --git a/src/source_language_protocol.hpp b/src/source_language_protocol.hpp index cfd5b28..0175017 100644 --- a/src/source_language_protocol.hpp +++ b/src/source_language_protocol.hpp @@ -83,6 +83,14 @@ namespace LanguageProtocol { std::string new_text; }; + class TextDocumentEdit { + public: + TextDocumentEdit(const boost::property_tree::ptree &pt); + + std::string file; + std::vector edits; + }; + class Capabilities { public: enum class TextDocumentSync { none = 0, From 979ae41520ab477234ce901b243d8c232623961d Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 23 Mar 2021 15:19:18 +0100 Subject: [PATCH 064/375] Fixes #444: stdout is now line buffered when running commands --- src/terminal.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/terminal.cpp b/src/terminal.cpp index e278878..d900baa 100644 --- a/src/terminal.cpp +++ b/src/terminal.cpp @@ -273,7 +273,7 @@ std::shared_ptr Terminal::async_process(const std::stri stdin_buffer.clear(); auto process = std::make_shared( - command, path.string(), + "STDBUF1=L " + command, path.string(), [this, quiet](const char *bytes, size_t n) { if(!quiet) { // Print stdout message sequentially to avoid the GUI becoming unresponsive From f03a22338396b5449437fc3a907e4d8372ce9b81 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 23 Mar 2021 17:44:42 +0100 Subject: [PATCH 065/375] Relted to #444: added Linux specific line buffering on async processes --- src/terminal.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/terminal.cpp b/src/terminal.cpp index d900baa..f58fb21 100644 --- a/src/terminal.cpp +++ b/src/terminal.cpp @@ -272,8 +272,19 @@ std::shared_ptr Terminal::async_process(const std::stri scroll_to_bottom(); stdin_buffer.clear(); +#if !defined(__APPLE__) && !defined(_WIN32) + static auto stdbuf = filesystem::find_executable("stdbuf").string(); +#endif + auto process = std::make_shared( - "STDBUF1=L " + command, path.string(), +#if defined(__APPLE__) + "STDBUF1=L " + command, +#elif defined(_WIN32) + command, +#else + !stdbuf.empty() ? std::vector{stdbuf, "-oL", "/bin/sh", "-c", command} : std::vector{"/bin/sh", "-c", command}, +#endif + path.string(), [this, quiet](const char *bytes, size_t n) { if(!quiet) { // Print stdout message sequentially to avoid the GUI becoming unresponsive From cf9758b19fac658d4a4193b4c34253a6696e4d9c Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 23 Mar 2021 20:44:06 +0100 Subject: [PATCH 066/375] Related to #444: on executing command on for instance Linux, make sure symbolic links are not resolved --- src/terminal.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/terminal.cpp b/src/terminal.cpp index f58fb21..748c737 100644 --- a/src/terminal.cpp +++ b/src/terminal.cpp @@ -274,17 +274,33 @@ std::shared_ptr Terminal::async_process(const std::stri #if !defined(__APPLE__) && !defined(_WIN32) static auto stdbuf = filesystem::find_executable("stdbuf").string(); + + std::string cd_path_and_command; + if(!path.empty()) { + auto path_escaped = path.string(); + size_t pos = 0; + // Based on https://www.reddit.com/r/cpp/comments/3vpjqg/a_new_platform_independent_process_library_for_c11/cxsxyb7 + while((pos = path_escaped.find('\'', pos)) != std::string::npos) { + path_escaped.replace(pos, 1, "'\\''"); + pos += 4; + } + cd_path_and_command = "cd '" + path_escaped + "' && " + command; // To avoid resolving symbolic links + } + else + cd_path_and_command = command; #endif auto process = std::make_shared( #if defined(__APPLE__) "STDBUF1=L " + command, + path.string(), #elif defined(_WIN32) command, + path.string(), #else - !stdbuf.empty() ? std::vector{stdbuf, "-oL", "/bin/sh", "-c", command} : std::vector{"/bin/sh", "-c", command}, + !stdbuf.empty() ? std::vector{stdbuf, "-oL", "/bin/sh", "-c", cd_path_and_command} : std::vector{"/bin/sh", "-c", cd_path_and_command}, + "", #endif - path.string(), [this, quiet](const char *bytes, size_t n) { if(!quiet) { // Print stdout message sequentially to avoid the GUI becoming unresponsive From 92b7cbdb2ba4a8445f5397a90dc1f5eb3d3d395f Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 24 Mar 2021 11:34:36 +0100 Subject: [PATCH 067/375] Callback of Terminal::get().async_process is now run in the main thread --- src/cmake.cpp | 24 ++--- src/meson.cpp | 24 ++--- src/project.cpp | 200 ++++++++++++++++++++-------------------- src/terminal.cpp | 12 ++- src/terminal.hpp | 1 + src/window.cpp | 2 +- tests/terminal_test.cpp | 48 +++++----- 7 files changed, 156 insertions(+), 155 deletions(-) diff --git a/src/cmake.cpp b/src/cmake.cpp index 8d11a1b..c6bac87 100644 --- a/src/cmake.cpp +++ b/src/cmake.cpp @@ -64,24 +64,24 @@ bool CMake::update_default_build(const boost::filesystem::path &default_build_pa Dialog::Message message("Creating/updating default build", [&canceled] { canceled = true; }); - std::promise promise; + boost::optional exit_status; auto process = Terminal::get().async_process(Config::get().project.cmake.command + ' ' + filesystem::escape_argument(project_path.string()) + " -DCMAKE_EXPORT_COMPILE_COMMANDS=ON", default_build_path, - [&promise](int exit_status) { - promise.set_value(exit_status); + [&exit_status](int exit_status_) { + exit_status = exit_status_; }); - auto future = promise.get_future(); bool killed = false; - while(future.wait_for(std::chrono::milliseconds(10)) != std::future_status::ready) { + while(!exit_status) { if(canceled && !killed) { process->kill(); killed = true; } while(Gtk::Main::events_pending()) Gtk::Main::iteration(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } message.hide(); - if(future.get() == 0) { + if(exit_status == 0) { #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) { @@ -124,24 +124,24 @@ bool CMake::update_debug_build(const boost::filesystem::path &debug_build_path, Dialog::Message message("Creating/updating debug build", [&canceled] { canceled = true; }); - std::promise promise; + boost::optional exit_status; auto process = Terminal::get().async_process(Config::get().project.cmake.command + ' ' + filesystem::escape_argument(project_path.string()) + " -DCMAKE_BUILD_TYPE=Debug", debug_build_path, - [&promise](int exit_status) { - promise.set_value(exit_status); + [&exit_status](int exit_status_) { + exit_status = exit_status_; }); - auto future = promise.get_future(); bool killed = false; - while(future.wait_for(std::chrono::milliseconds(10)) != std::future_status::ready) { + while(!exit_status) { if(canceled && !killed) { process->kill(); killed = true; } while(Gtk::Main::events_pending()) Gtk::Main::iteration(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } message.hide(); - return future.get() == 0; + return exit_status == 0; } boost::filesystem::path CMake::get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) { diff --git a/src/meson.cpp b/src/meson.cpp index a85afb1..a2a6bfa 100644 --- a/src/meson.cpp +++ b/src/meson.cpp @@ -64,24 +64,24 @@ bool Meson::update_default_build(const boost::filesystem::path &default_build_pa Dialog::Message message("Creating/updating default build", [&canceled] { canceled = true; }); - std::promise promise; + boost::optional exit_status; auto process = Terminal::get().async_process(Config::get().project.meson.command + ' ' + (compile_commands_exists ? "--internal regenerate " : "") + "--buildtype plain " + filesystem::escape_argument(project_path.string()), default_build_path, - [&promise](int exit_status) { - promise.set_value(exit_status); + [&exit_status](int exit_status_) { + exit_status = exit_status_; }); - auto future = promise.get_future(); bool killed = false; - while(future.wait_for(std::chrono::milliseconds(10)) != std::future_status::ready) { + while(!exit_status) { if(canceled && !killed) { process->kill(); killed = true; } while(Gtk::Main::events_pending()) Gtk::Main::iteration(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } message.hide(); - return future.get() == 0; + return exit_status == 0; } bool Meson::update_debug_build(const boost::filesystem::path &debug_build_path, bool force) { @@ -106,24 +106,24 @@ bool Meson::update_debug_build(const boost::filesystem::path &debug_build_path, Dialog::Message message("Creating/updating debug build", [&canceled] { canceled = true; }); - std::promise promise; + boost::optional exit_status; auto process = Terminal::get().async_process(Config::get().project.meson.command + ' ' + (compile_commands_exists ? "--internal regenerate " : "") + "--buildtype debug " + filesystem::escape_argument(project_path.string()), debug_build_path, - [&promise](int exit_status) { - promise.set_value(exit_status); + [&exit_status](int exit_status_) { + exit_status = exit_status_; }); - auto future = promise.get_future(); bool killed = false; - while(future.wait_for(std::chrono::milliseconds(10)) != std::future_status::ready) { + while(!exit_status) { if(canceled && !killed) { process->kill(); killed = true; } while(Gtk::Main::events_pending()) Gtk::Main::iteration(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } message.hide(); - return future.get() == 0; + return exit_status == 0; } boost::filesystem::path Meson::get_executable(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) { diff --git a/src/project.cpp b/src/project.cpp index 7e6ac9a..e9753d3 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -386,109 +386,107 @@ void Project::LLDB::debug_start() { if(exit_status != EXIT_SUCCESS) debugging = false; else { - self->dispatcher.post([self, run_arguments, project_path] { - std::vector> breakpoints; - for(size_t c = 0; c < Notebook::get().size(); c++) { - auto view = Notebook::get().get_view(c); - if(filesystem::file_in_path(view->file_path, *project_path)) { - auto iter = view->get_buffer()->begin(); - if(view->get_source_buffer()->get_source_marks_at_iter(iter, "debug_breakpoint").size() > 0) - breakpoints.emplace_back(view->file_path, iter.get_line() + 1); - while(view->get_source_buffer()->forward_iter_to_source_mark(iter, "debug_breakpoint")) - breakpoints.emplace_back(view->file_path, iter.get_line() + 1); - } + std::vector> breakpoints; + for(size_t c = 0; c < Notebook::get().size(); c++) { + auto view = Notebook::get().get_view(c); + if(filesystem::file_in_path(view->file_path, *project_path)) { + auto iter = view->get_buffer()->begin(); + if(view->get_source_buffer()->get_source_marks_at_iter(iter, "debug_breakpoint").size() > 0) + breakpoints.emplace_back(view->file_path, iter.get_line() + 1); + while(view->get_source_buffer()->forward_iter_to_source_mark(iter, "debug_breakpoint")) + breakpoints.emplace_back(view->file_path, iter.get_line() + 1); } + } - std::string remote_host; - auto debug_run_arguments_it = debug_run_arguments.find(project_path->string()); - if(debug_run_arguments_it != debug_run_arguments.end() && debug_run_arguments_it->second.remote_enabled) - remote_host = debug_run_arguments_it->second.remote_host_port; - - static auto on_exit_it = Debug::LLDB::get().on_exit.end(); - if(on_exit_it != Debug::LLDB::get().on_exit.end()) - Debug::LLDB::get().on_exit.erase(on_exit_it); - Debug::LLDB::get().on_exit.emplace_back([self, run_arguments](int exit_status) { - debugging = false; - Terminal::get().async_print("\e[2m" + *run_arguments + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); - self->dispatcher.post([] { - debug_update_status(""); - }); + std::string remote_host; + auto debug_run_arguments_it = debug_run_arguments.find(project_path->string()); + if(debug_run_arguments_it != debug_run_arguments.end() && debug_run_arguments_it->second.remote_enabled) + remote_host = debug_run_arguments_it->second.remote_host_port; + + static auto on_exit_it = Debug::LLDB::get().on_exit.end(); + if(on_exit_it != Debug::LLDB::get().on_exit.end()) + Debug::LLDB::get().on_exit.erase(on_exit_it); + Debug::LLDB::get().on_exit.emplace_back([self, run_arguments](int exit_status) { + debugging = false; + Terminal::get().async_print("\e[2m" + *run_arguments + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); + self->dispatcher.post([] { + debug_update_status(""); }); - on_exit_it = std::prev(Debug::LLDB::get().on_exit.end()); - - static auto on_event_it = Debug::LLDB::get().on_event.end(); - if(on_event_it != Debug::LLDB::get().on_event.end()) - Debug::LLDB::get().on_event.erase(on_event_it); - Debug::LLDB::get().on_event.emplace_back([self](const lldb::SBEvent &event) { - std::string status; - boost::filesystem::path stop_path; - unsigned stop_line = 0, stop_column = 0; - - LockGuard lock(Debug::LLDB::get().mutex); - auto process = lldb::SBProcess::GetProcessFromEvent(event); - auto state = lldb::SBProcess::GetStateFromEvent(event); - lldb::SBStream stream; - event.GetDescription(stream); - std::string event_desc = stream.GetData(); - event_desc.pop_back(); - auto pos = event_desc.rfind(" = "); - if(pos != std::string::npos && pos + 3 < event_desc.size()) - status = event_desc.substr(pos + 3); - if(state == lldb::StateType::eStateStopped) { - char buffer[100]; - auto thread = process.GetSelectedThread(); - auto n = thread.GetStopDescription(buffer, 100); // Returns number of bytes read. Might include null termination... Although maybe on newer versions only. - if(n > 0) - status += " (" + std::string(buffer, n <= 100 ? (buffer[n - 1] == '\0' ? n - 1 : n) : 100) + ")"; - auto line_entry = thread.GetSelectedFrame().GetLineEntry(); - if(line_entry.IsValid()) { - lldb::SBStream stream; - line_entry.GetFileSpec().GetDescription(stream); - auto line = line_entry.GetLine(); - status += " " + boost::filesystem::path(stream.GetData()).filename().string() + ":" + std::to_string(line); - auto column = line_entry.GetColumn(); - if(column == 0) - column = 1; - stop_path = filesystem::get_normal_path(stream.GetData()); - stop_line = line - 1; - stop_column = column - 1; - } + }); + on_exit_it = std::prev(Debug::LLDB::get().on_exit.end()); + + static auto on_event_it = Debug::LLDB::get().on_event.end(); + if(on_event_it != Debug::LLDB::get().on_event.end()) + Debug::LLDB::get().on_event.erase(on_event_it); + Debug::LLDB::get().on_event.emplace_back([self](const lldb::SBEvent &event) { + std::string status; + boost::filesystem::path stop_path; + unsigned stop_line = 0, stop_column = 0; + + LockGuard lock(Debug::LLDB::get().mutex); + auto process = lldb::SBProcess::GetProcessFromEvent(event); + auto state = lldb::SBProcess::GetStateFromEvent(event); + lldb::SBStream stream; + event.GetDescription(stream); + std::string event_desc = stream.GetData(); + event_desc.pop_back(); + auto pos = event_desc.rfind(" = "); + if(pos != std::string::npos && pos + 3 < event_desc.size()) + status = event_desc.substr(pos + 3); + if(state == lldb::StateType::eStateStopped) { + char buffer[100]; + auto thread = process.GetSelectedThread(); + auto n = thread.GetStopDescription(buffer, 100); // Returns number of bytes read. Might include null termination... Although maybe on newer versions only. + if(n > 0) + status += " (" + std::string(buffer, n <= 100 ? (buffer[n - 1] == '\0' ? n - 1 : n) : 100) + ")"; + auto line_entry = thread.GetSelectedFrame().GetLineEntry(); + if(line_entry.IsValid()) { + lldb::SBStream stream; + line_entry.GetFileSpec().GetDescription(stream); + auto line = line_entry.GetLine(); + status += " " + boost::filesystem::path(stream.GetData()).filename().string() + ":" + std::to_string(line); + auto column = line_entry.GetColumn(); + if(column == 0) + column = 1; + stop_path = filesystem::get_normal_path(stream.GetData()); + stop_line = line - 1; + stop_column = column - 1; } + } - self->dispatcher.post([status = std::move(status), stop_path = std::move(stop_path), stop_line, stop_column] { - debug_update_status(status); - Project::debug_stop.first = stop_path; - Project::debug_stop.second.first = stop_line; - Project::debug_stop.second.second = stop_column; - debug_update_stop(); - - if(Config::get().source.debug_place_cursor_at_stop && !stop_path.empty()) { - if(Notebook::get().open(stop_path)) { - auto view = Notebook::get().get_current_view(); - view->place_cursor_at_line_index(stop_line, stop_column); - view->scroll_to_cursor_delayed(true, false); - } + self->dispatcher.post([status = std::move(status), stop_path = std::move(stop_path), stop_line, stop_column] { + debug_update_status(status); + Project::debug_stop.first = stop_path; + Project::debug_stop.second.first = stop_line; + Project::debug_stop.second.second = stop_column; + debug_update_stop(); + + if(Config::get().source.debug_place_cursor_at_stop && !stop_path.empty()) { + if(Notebook::get().open(stop_path)) { + auto view = Notebook::get().get_current_view(); + view->place_cursor_at_line_index(stop_line, stop_column); + view->scroll_to_cursor_delayed(true, false); } - else if(auto view = Notebook::get().get_current_view()) - view->get_buffer()->place_cursor(view->get_buffer()->get_insert()->get_iter()); - }); - }); - on_event_it = std::prev(Debug::LLDB::get().on_event.end()); - - std::vector startup_commands; - if(dynamic_cast(self->build.get())) { - std::stringstream istream, ostream; - if(Terminal::get().process(istream, ostream, "rustc --print sysroot") == 0) { - auto sysroot = ostream.str(); - while(!sysroot.empty() && (sysroot.back() == '\n' || sysroot.back() == '\r')) - sysroot.pop_back(); - startup_commands.emplace_back("command script import \"" + sysroot + "/lib/rustlib/etc/lldb_rust_formatters.py\""); - startup_commands.emplace_back("type summary add --no-value --python-function lldb_rust_formatters.print_val -x \".*\" --category Rust"); - startup_commands.emplace_back("type category enable Rust"); } - } - Debug::LLDB::get().start(*run_arguments, *project_path, breakpoints, startup_commands, remote_host); + else if(auto view = Notebook::get().get_current_view()) + view->get_buffer()->place_cursor(view->get_buffer()->get_insert()->get_iter()); + }); }); + on_event_it = std::prev(Debug::LLDB::get().on_event.end()); + + std::vector startup_commands; + if(dynamic_cast(self->build.get())) { + std::stringstream istream, ostream; + if(Terminal::get().process(istream, ostream, "rustc --print sysroot") == 0) { + auto sysroot = ostream.str(); + while(!sysroot.empty() && (sysroot.back() == '\n' || sysroot.back() == '\r')) + sysroot.pop_back(); + startup_commands.emplace_back("command script import \"" + sysroot + "/lib/rustlib/etc/lldb_rust_formatters.py\""); + startup_commands.emplace_back("type summary add --no-value --python-function lldb_rust_formatters.print_val -x \".*\" --category Rust"); + startup_commands.emplace_back("type category enable Rust"); + } + } + Debug::LLDB::get().start(*run_arguments, *project_path, breakpoints, startup_commands, remote_host); } }); } @@ -893,7 +891,7 @@ void Project::Clang::compile_and_run() { compiling = false; if(exit_status == 0) { Terminal::get().async_process(arguments, project_path, [arguments](int exit_status) { - Terminal::get().async_print("\e[2m" + arguments + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); + Terminal::get().print("\e[2m" + arguments + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); }); } }); @@ -973,7 +971,7 @@ void Project::Markdown::compile_and_run() { Terminal::get().async_process( command, "", [command](int exit_status) { if(exit_status == 127) - Terminal::get().async_print("\e[31mError\e[m: executable not found: " + command + "\n", true); + Terminal::get().print("\e[31mError\e[m: executable not found: " + command + "\n", true); }, true); } @@ -1001,7 +999,7 @@ void Project::Python::compile_and_run() { Terminal::get().print("\e[2mRunning " + command + "\e[m\n"); Terminal::get().async_process(command, path, [command](int exit_status) { - Terminal::get().async_print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); + Terminal::get().print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); }); } @@ -1027,7 +1025,7 @@ void Project::JavaScript::compile_and_run() { Terminal::get().print("\e[2mRunning " + command + "\e[m\n"); Terminal::get().async_process(command, path, [command](int exit_status) { - Terminal::get().async_print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); + Terminal::get().print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); }); } @@ -1040,7 +1038,7 @@ void Project::HTML::compile_and_run() { Terminal::get().print("\e[2mRunning " + command + "\e[m\n"); Terminal::get().async_process(command, build->project_path, [command](int exit_status) { - Terminal::get().async_print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); + Terminal::get().print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); }); } else if(auto view = Notebook::get().get_current_view()) @@ -1088,7 +1086,7 @@ void Project::Rust::compile_and_run() { compiling = false; if(exit_status == 0) { Terminal::get().async_process(arguments, self->build->project_path, [arguments](int exit_status) { - Terminal::get().async_print("\e[2m" + arguments + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); + Terminal::get().print("\e[2m" + arguments + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); }); } }); diff --git a/src/terminal.cpp b/src/terminal.cpp index 748c737..3cc0f12 100644 --- a/src/terminal.cpp +++ b/src/terminal.cpp @@ -337,7 +337,7 @@ std::shared_ptr Terminal::async_process(const std::stri processes.emplace_back(process); } - std::thread exit_status_thread([this, process, pid, callback = std::move(callback)]() { + std::thread([this, process, pid, callback = std::move(callback)]() mutable { auto exit_status = process->get_exit_status(); { LockGuard lock(processes_mutex); @@ -348,10 +348,12 @@ std::shared_ptr Terminal::async_process(const std::stri } } } - if(callback) - callback(exit_status); - }); - exit_status_thread.detach(); + if(callback) { + dispatcher.post([callback = std::move(callback), exit_status] { + callback(exit_status); + }); + } + }).detach(); return process; } diff --git a/src/terminal.hpp b/src/terminal.hpp index a89673b..409cb5f 100644 --- a/src/terminal.hpp +++ b/src/terminal.hpp @@ -21,6 +21,7 @@ public: 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); + /// The callback is run in the main thread. std::shared_ptr async_process(const std::string &command, const boost::filesystem::path &path = "", std::function callback = nullptr, bool quiet = false); void kill_last_async_process(bool force = false); void kill_async_processes(bool force = false); diff --git a/src/window.cpp b/src/window.cpp index 14fdfa8..e51c222 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -1391,7 +1391,7 @@ void Window::set_menu_actions() { Terminal::get().async_print("\e[2mRunning: " + content + "\e[m\n"); Terminal::get().async_process(content, directory_folder, [content](int exit_status) { - Terminal::get().async_print("\e[2m" + content + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); + Terminal::get().print("\e[2m" + content + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); }); } if(Config::get().terminal.hide_entry_on_run_command) diff --git a/tests/terminal_test.cpp b/tests/terminal_test.cpp index 72e829a..d0e367f 100644 --- a/tests/terminal_test.cpp +++ b/tests/terminal_test.cpp @@ -281,68 +281,68 @@ int main() { // async_process tests { terminal.clear(); - std::promise done; - terminal.async_process("echo test", "", [&done](int exit_status) { - done.set_value(exit_status); + boost::optional exit_status; + terminal.async_process("echo test", "", [&exit_status](int exit_status_) { + exit_status = exit_status_; }); - auto future = done.get_future(); - while(future.wait_for(std::chrono::milliseconds(10)) != std::future_status::ready) { + while(!exit_status) { while(Gtk::Main::events_pending()) Gtk::Main::iteration(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } - assert(future.get() == 0); + assert(exit_status == 0); assert(buffer->get_text() == "test\n"); assert(!buffer->begin().starts_tag(terminal.bold_tag)); assert(!buffer->end().ends_tag(terminal.bold_tag)); } { terminal.clear(); - std::promise done; + boost::optional exit_status; terminal.async_process( - "echo test", "", [&done](int exit_status) { - done.set_value(exit_status); + "echo test", "", [&exit_status](int exit_status_) { + exit_status = exit_status_; }, true); - auto future = done.get_future(); - while(future.wait_for(std::chrono::milliseconds(10)) != std::future_status::ready) { + while(!exit_status) { while(Gtk::Main::events_pending()) Gtk::Main::iteration(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } - assert(future.get() == 0); + assert(exit_status == 0); assert(buffer->get_text() == ""); } { terminal.clear(); - std::promise done; - terminal.async_process("testing_invalid_command", "", [&done](int exit_status) { - done.set_value(exit_status); + boost::optional exit_status; + terminal.async_process("testing_invalid_command", "", [&exit_status](int exit_status_) { + exit_status = exit_status_; }); - auto future = done.get_future(); - while(future.wait_for(std::chrono::milliseconds(10)) != std::future_status::ready) { + while(!exit_status) { while(Gtk::Main::events_pending()) Gtk::Main::iteration(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } - assert(future.get() != 0); + assert(exit_status != 0); assert(buffer->begin().starts_tag(terminal.bold_tag)); assert(buffer->end().ends_tag(terminal.bold_tag)); assert(buffer->get_text() != ""); } { terminal.clear(); - std::promise done; + boost::optional exit_status; terminal.async_process( - "testing_invalid_command", "", [&done](int exit_status) { - done.set_value(exit_status); + "testing_invalid_command", "", [&exit_status](int exit_status_) { + exit_status = exit_status_; }, true); - auto future = done.get_future(); - while(future.wait_for(std::chrono::milliseconds(10)) != std::future_status::ready) { + while(!exit_status) { while(Gtk::Main::events_pending()) Gtk::Main::iteration(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } - assert(future.get() != 0); + assert(exit_status != 0); assert(buffer->get_text() == ""); } From ccd84bd166a64d3c1f62b02ecb445ce6c569a68c Mon Sep 17 00:00:00 2001 From: eidheim Date: Thu, 25 Mar 2021 09:27:05 +0100 Subject: [PATCH 068/375] Made use of Thread Safety Analysis EXCLUDES to avoid deadlocks in the future --- src/debug_lldb.hpp | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/debug_lldb.hpp b/src/debug_lldb.hpp index 9507bb0..5609b15 100644 --- a/src/debug_lldb.hpp +++ b/src/debug_lldb.hpp @@ -32,7 +32,7 @@ namespace Debug { private: LLDB(); - void destroy_(); + void destroy_() EXCLUDES(mutex); static bool initialized; @@ -58,32 +58,32 @@ namespace Debug { void start(const std::string &command, const boost::filesystem::path &path = "", const std::vector> &breakpoints = {}, - const std::vector &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 run_command(const std::string &command); - std::vector get_backtrace(); - std::vector get_variables(); - void select_frame(uint32_t frame_index, uint32_t thread_index_id = 0); + const std::vector &startup_commands = {}, const std::string &remote_host = "") EXCLUDES(mutex); + void continue_debug() EXCLUDES(mutex); //can't use continue as function name + void stop() EXCLUDES(mutex); + void kill() EXCLUDES(mutex); + void step_over() EXCLUDES(mutex); + void step_into() EXCLUDES(mutex); + void step_out() EXCLUDES(mutex); + std::pair run_command(const std::string &command) EXCLUDES(mutex); + std::vector get_backtrace() EXCLUDES(mutex); + std::vector get_variables() EXCLUDES(mutex); + void select_frame(uint32_t frame_index, uint32_t thread_index_id = 0) EXCLUDES(mutex); /// Get value using variable name and location - std::string get_value(const std::string &variable_name, const boost::filesystem::path &file_path, unsigned int line_nr, unsigned int line_index); + std::string get_value(const std::string &variable_name, const boost::filesystem::path &file_path, unsigned int line_nr, unsigned int line_index) EXCLUDES(mutex); /// Get value from expression - std::string get_value(const std::string &expression); - std::string get_return_value(const boost::filesystem::path &file_path, unsigned int line_nr, unsigned int line_index); + std::string get_value(const std::string &expression) EXCLUDES(mutex); + std::string get_return_value(const boost::filesystem::path &file_path, unsigned int line_nr, unsigned int line_index) EXCLUDES(mutex); - bool is_invalid(); - bool is_stopped(); - bool is_running(); + bool is_invalid() EXCLUDES(mutex); + bool is_stopped() EXCLUDES(mutex); + bool is_running() EXCLUDES(mutex); - 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 add_breakpoint(const boost::filesystem::path &file_path, int line_nr) EXCLUDES(mutex); + void remove_breakpoint(const boost::filesystem::path &file_path, int line_nr, int line_count) EXCLUDES(mutex); - void write(const std::string &buffer); + void write(const std::string &buffer) EXCLUDES(mutex); private: std::tuple, std::string, std::vector> parse_run_arguments(const std::string &command); From ab514950121dbdc867709ee5a2ff19d0db3c1a3f Mon Sep 17 00:00:00 2001 From: eidheim Date: Sat, 27 Mar 2021 07:56:29 +0100 Subject: [PATCH 069/375] Now marks clickable URIs in terminal output --- src/terminal.cpp | 21 ++++++++++++++++----- tests/terminal_test.cpp | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/terminal.cpp b/src/terminal.cpp index 3cc0f12..136ea28 100644 --- a/src/terminal.cpp +++ b/src/terminal.cpp @@ -36,21 +36,19 @@ Terminal::Terminal() : Source::SearchView() { default_mouse_cursor = Gdk::Cursor::create(Gdk::CursorType::XTERM); class DetectPossibleLink { - bool delimiter_found = false, dot_found = false, number_after_delimiter_and_dot_found = false; + bool delimiter_found = false, dot_found = false; public: bool operator()(char chr) { if(chr == '\n') { - auto all_found = delimiter_found && dot_found && number_after_delimiter_and_dot_found; - delimiter_found = dot_found = number_after_delimiter_and_dot_found = false; + auto all_found = delimiter_found && dot_found; + delimiter_found = dot_found = false; return all_found; } else if(chr == '/' || chr == '\\') delimiter_found = true; else if(chr == '.') dot_found = true; - else if(delimiter_found && dot_found && chr >= '0' && chr <= '9') - number_after_delimiter_and_dot_found = true; return false; } }; @@ -450,6 +448,14 @@ boost::optional Terminal::find_link(const std::string &line) { sub += subs; } } + const static std::regex uri_regex("^.*(https?://[\\w\\-.~:/?#%\\[\\]@!$&'()*+,;=]+[\\w\\-~/#@$*+;=]).*$", std::regex::optimize); + if(std::regex_match(line, sm, uri_regex)) { + auto start_pos = static_cast(sm.position(1)); + auto end_pos = static_cast(start_pos + sm.length(1)); + int start_pos_utf8 = utf8_character_count(line, 0, start_pos); + int end_pos_utf8 = start_pos_utf8 + utf8_character_count(line, start_pos, end_pos - start_pos); + return Link{start_pos_utf8, end_pos_utf8, sm[1].str(), 0, 0}; + } return {}; } @@ -577,6 +583,11 @@ bool Terminal::on_button_press_event(GdkEventButton *button_event) { if(!end.ends_line()) end.forward_to_line_end(); if(auto link = find_link(get_buffer()->get_text(start, end, false).raw())) { + if(starts_with(link->path, "http://") || starts_with(link->path, "https://")) { + Notebook::get().open_uri(link->path); + return true; + } + auto path = filesystem::get_long_path(link->path); if(path.is_relative()) { diff --git a/tests/terminal_test.cpp b/tests/terminal_test.cpp index d0e367f..ef24ec9 100644 --- a/tests/terminal_test.cpp +++ b/tests/terminal_test.cpp @@ -140,6 +140,24 @@ int main() { long_line += "x"; assert(!Terminal::find_link("/home/test/test.txt:1:1: " + long_line)); } + { + auto link = Terminal::find_link("https://test.org"); + assert(link); + assert(link->start_pos == 0); + assert(link->end_pos == 16); + assert(link->path == "https://test.org"); + assert(link->line == 0); + assert(link->line_index == 0); + } + { + auto link = Terminal::find_link("Testing https://test.org here"); + assert(link); + assert(link->start_pos == 8); + assert(link->end_pos == 24); + assert(link->path == "https://test.org"); + assert(link->line == 0); + assert(link->line_index == 0); + } // Testing print { From 84804a89b77da05eecd87626f394a46a44f0cd2c Mon Sep 17 00:00:00 2001 From: eidheim Date: Sat, 27 Mar 2021 08:09:30 +0100 Subject: [PATCH 070/375] Made "C/C++ project created" terminal message green --- src/window.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/window.cpp b/src/window.cpp index e51c222..91cc331 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -371,7 +371,7 @@ void Window::set_menu_actions() { Directories::get().open(project_path); Notebook::get().open(c_main_path); Directories::get().update(); - Terminal::get().print("C project " + project_name + " created.\n"); + Terminal::get().print("\e[32mC project " + project_name + " created.\e[m\n"); } else Terminal::get().print("\e[31mError\e[m: Could not create project " + project_path.string() + "\n", true); @@ -421,7 +421,7 @@ void Window::set_menu_actions() { Directories::get().open(project_path); Notebook::get().open(cpp_main_path); Directories::get().update(); - Terminal::get().print("C++ project " + project_name + " created.\n"); + Terminal::get().print("\e[32mC++ project " + project_name + " created.\e[m\n"); } else Terminal::get().print("\e[31mError\e[m: Could not create project " + project_path.string() + "\n", true); From d94024f38dbef1304e210f4f02a1fb8baaa73729 Mon Sep 17 00:00:00 2001 From: eidheim Date: Sat, 27 Mar 2021 13:29:01 +0100 Subject: [PATCH 071/375] Added comment to Filesystem::find_executable, and slight changed message on created C/C++ project --- src/filesystem.hpp | 1 + src/window.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/filesystem.hpp b/src/filesystem.hpp index 3f14e86..3056315 100644 --- a/src/filesystem.hpp +++ b/src/filesystem.hpp @@ -35,6 +35,7 @@ public: static const std::vector &get_executable_search_paths(); + /// Returns full executable path if found, or empty path otherwise. static boost::filesystem::path find_executable(const std::string &executable_name); /// Get uri from path diff --git a/src/window.cpp b/src/window.cpp index 91cc331..51f2526 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -371,7 +371,7 @@ void Window::set_menu_actions() { Directories::get().open(project_path); Notebook::get().open(c_main_path); Directories::get().update(); - Terminal::get().print("\e[32mC project " + project_name + " created.\e[m\n"); + Terminal::get().print("C project " + project_name + " \e[32mcreated\e[m.\n"); } else Terminal::get().print("\e[31mError\e[m: Could not create project " + project_path.string() + "\n", true); @@ -421,7 +421,7 @@ void Window::set_menu_actions() { Directories::get().open(project_path); Notebook::get().open(cpp_main_path); Directories::get().update(); - Terminal::get().print("\e[32mC++ project " + project_name + " created.\e[m\n"); + Terminal::get().print("C++ project " + project_name + " \e[32mcreated\e[m.\n"); } else Terminal::get().print("\e[31mError\e[m: Could not create project " + project_path.string() + "\n", true); From 77b60abf071518a7f8c2eaffbee8ab810fc70139 Mon Sep 17 00:00:00 2001 From: eidheim Date: Sat, 27 Mar 2021 13:53:01 +0100 Subject: [PATCH 072/375] Added installation instruction outputs upon detecting missing language servers when opening some source file types --- docs/language_servers.md | 6 ++++-- src/notebook.cpp | 24 +++++++++++++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/docs/language_servers.md b/docs/language_servers.md index c0de42a..aa43cfe 100644 --- a/docs/language_servers.md +++ b/docs/language_servers.md @@ -1,6 +1,8 @@ # Setup of tested language servers -## JavaScript with Flow static type checker +## JavaScript/TypeScript + +### JavaScript with Flow static type checker * Prerequisites: * Node.js * Recommended: @@ -19,7 +21,7 @@ chmod 755 /usr/local/bin/javascript-language-server * Additional setup within a JavaScript project: * Add a `.prettierrc` file to enable style format on save -## TypeScript or JavaScript without Flow +### TypeScript or JavaScript without Flow * Prerequisites: * Node.js * Recommended: diff --git a/src/notebook.cpp b/src/notebook.cpp index 28ded3a..8b81d7e 100644 --- a/src/notebook.cpp +++ b/src/notebook.cpp @@ -174,8 +174,30 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position 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 + else { + if(language) { + static std::set shown; + std::string language_id = language->get_id(); + if(shown.find(language_id) == shown.end()) { + if(language_id == "js") { + Terminal::get().print("\e[33mWarning\e[m: Could not find JavaScript/TypeScript language server.\n"); + Terminal::get().print("For installation instructions please visit: https://gitlab.com/cppit/jucipp/-/blob/master/docs/language_servers.md#javascripttypescript.\n"); + shown.emplace(language_id); + } + else if(language_id == "python") { + Terminal::get().print("\e[33mWarning\e[m: Could not find Python language server.\n"); + Terminal::get().print("For installation instructions please visit: https://gitlab.com/cppit/jucipp/-/blob/master/docs/language_servers.md#python3.\n"); + shown.emplace(language_id); + } + else if(language_id == "rust") { + Terminal::get().print("\e[33mWarning\e[m: Could not find Rust language server.\n"); + Terminal::get().print("For installation instructions please visit: https://gitlab.com/cppit/jucipp/-/blob/master/docs/language_servers.md#rust.\n"); + shown.emplace(language_id); + } + } + } source_views.emplace_back(new Source::GenericView(file_path, language)); + } auto view = source_views.back(); From cdf44cac9a613d8d5494beac4562ab5411703212 Mon Sep 17 00:00:00 2001 From: eidheim Date: Sat, 27 Mar 2021 14:01:19 +0100 Subject: [PATCH 073/375] Removed unnecessary line in python language server installation instructions --- docs/language_servers.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/language_servers.md b/docs/language_servers.md index aa43cfe..12cd3e8 100644 --- a/docs/language_servers.md +++ b/docs/language_servers.md @@ -12,7 +12,7 @@ Install language server, and create executable to enable server in juCi++: ```sh npm install -g flow-bin -# usually as root: +# Usually as root: echo '#!/bin/sh flow lsp' > /usr/local/bin/javascript-language-server chmod 755 /usr/local/bin/javascript-language-server @@ -31,7 +31,7 @@ Install language server, and create executable to enable server in juCi++: ```sh npm install -g typescript-language-server typescript -# usually as root: +# Usually as root: echo '#!/bin/sh `npm root -g`/typescript-language-server/lib/cli.js --stdio' > /usr/local/bin/javascript-language-server chmod 755 /usr/local/bin/javascript-language-server @@ -46,13 +46,12 @@ cp /usr/local/bin/javascript-language-server /usr/local/bin/typescriptreact-lang ## Python3 * Prerequisites: * Python3 - * In juCi++ preferences, set `project.python_command` to `PYTHONUNBUFFERED=1 python3` Install language server, and create symbolic link to enable server in juCi++: ```sh pip3 install python-language-server[rope,pycodestyle,yapf] -# usually as root: +# Usually as root: ln -s `which pyls` /usr/local/bin/python-language-server ``` @@ -72,7 +71,7 @@ git clone https://github.com/rust-analyzer/rust-analyzer cd rust-analyzer cargo xtask install --server -# usually as root: +# Usually as root: ln -s ~/.cargo/bin/rust-analyzer /usr/local/bin/rust-language-server ``` @@ -88,7 +87,7 @@ mkdir build cd build cmake .. make -# usually as root: +# Usually as root: make install echo '#!/bin/sh /usr/local/bin/glslls --stdin' > /usr/local/bin/glsl-language-server From c7da9c4977a16d883c984bf8aceb13885f618601 Mon Sep 17 00:00:00 2001 From: eidheim Date: Sat, 27 Mar 2021 14:03:38 +0100 Subject: [PATCH 074/375] Corrected typo in language server installation instructions --- src/notebook.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/notebook.cpp b/src/notebook.cpp index 8b81d7e..cdfd575 100644 --- a/src/notebook.cpp +++ b/src/notebook.cpp @@ -180,17 +180,17 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position std::string language_id = language->get_id(); if(shown.find(language_id) == shown.end()) { if(language_id == "js") { - Terminal::get().print("\e[33mWarning\e[m: Could not find JavaScript/TypeScript language server.\n"); + Terminal::get().print("\e[33mWarning\e[m: could not find JavaScript/TypeScript language server.\n"); Terminal::get().print("For installation instructions please visit: https://gitlab.com/cppit/jucipp/-/blob/master/docs/language_servers.md#javascripttypescript.\n"); shown.emplace(language_id); } else if(language_id == "python") { - Terminal::get().print("\e[33mWarning\e[m: Could not find Python language server.\n"); + Terminal::get().print("\e[33mWarning\e[m: could not find Python language server.\n"); Terminal::get().print("For installation instructions please visit: https://gitlab.com/cppit/jucipp/-/blob/master/docs/language_servers.md#python3.\n"); shown.emplace(language_id); } else if(language_id == "rust") { - Terminal::get().print("\e[33mWarning\e[m: Could not find Rust language server.\n"); + Terminal::get().print("\e[33mWarning\e[m: could not find Rust language server.\n"); Terminal::get().print("For installation instructions please visit: https://gitlab.com/cppit/jucipp/-/blob/master/docs/language_servers.md#rust.\n"); shown.emplace(language_id); } From 454fe03fb405922eea09d0ce33efaf5991399e66 Mon Sep 17 00:00:00 2001 From: eidheim Date: Sat, 27 Mar 2021 15:09:38 +0100 Subject: [PATCH 075/375] Cleanup for terminal messages --- src/cmake.cpp | 4 ++-- src/config.cpp | 2 +- src/directories.cpp | 20 ++++++++-------- src/meson.cpp | 4 ++-- src/notebook.cpp | 4 ++-- src/project.cpp | 4 ++-- src/source.cpp | 2 +- src/source_clang.cpp | 2 +- src/source_generic.cpp | 2 +- src/source_language_protocol.cpp | 2 +- src/window.cpp | 39 +++++++++++++++----------------- 11 files changed, 41 insertions(+), 44 deletions(-) diff --git a/src/cmake.cpp b/src/cmake.cpp index c6bac87..8cf8cb8 100644 --- a/src/cmake.cpp +++ b/src/cmake.cpp @@ -51,7 +51,7 @@ bool CMake::update_default_build(const boost::filesystem::path &default_build_pa boost::system::error_code ec; boost::filesystem::create_directories(default_build_path, ec); if(ec) { - Terminal::get().print("\e[31mError\e[m: could not create " + default_build_path.string() + ": " + ec.message() + "\n", true); + Terminal::get().print("\e[31mError\e[m: could not create " + filesystem::get_short_path(default_build_path).string() + ": " + ec.message() + "\n", true); return false; } } @@ -112,7 +112,7 @@ bool CMake::update_debug_build(const boost::filesystem::path &debug_build_path, boost::system::error_code ec; boost::filesystem::create_directories(debug_build_path, ec); if(ec) { - Terminal::get().print("\e[31mError\e[m: could not create " + debug_build_path.string() + ": " + ec.message() + "\n", true); + Terminal::get().print("\e[31mError\e[m: could not create " + filesystem::get_short_path(debug_build_path).string() + ": " + ec.message() + "\n", true); return false; } } diff --git a/src/config.cpp b/src/config.cpp index d12e4bb..5d725b5 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -44,7 +44,7 @@ void Config::load() { } catch(const std::exception &e) { dispatcher.post([config_json = std::move(config_json), e_what = std::string(e.what())] { - ::Terminal::get().print("\e[31mError\e[m: could not parse " + config_json.string() + ": " + e_what + "\n", true); + ::Terminal::get().print("\e[31mError\e[m: could not parse " + filesystem::get_short_path(config_json).string() + ": " + e_what + "\n", true); }); std::stringstream ss; ss << default_config_file; diff --git a/src/directories.cpp b/src/directories.cpp index fc34c42..3f2fec8 100644 --- a/src/directories.cpp +++ b/src/directories.cpp @@ -56,7 +56,7 @@ bool Directories::TreeStore::drag_data_received_vfunc(const TreeModel::Path &pat boost::system::error_code ec; if(boost::filesystem::exists(target_path, ec)) { - Terminal::get().print("\e[31mError\e[m: could not move file: " + target_path.string() + " already exists\n", true); + Terminal::get().print("\e[31mError\e[m: could not move file: " + filesystem::get_short_path(target_path).string() + " already exists\n", true); return false; } @@ -287,12 +287,12 @@ Directories::Directories() : Gtk::ListViewText(1) { on_save_file(target_path); } else { - Terminal::get().print("\e[31mError\e[m: could not create " + target_path.string() + '\n', true); + Terminal::get().print("\e[31mError\e[m: could not create " + filesystem::get_short_path(target_path).string() + '\n', true); return; } } else { - Terminal::get().print("\e[31mError\e[m: could not create " + target_path.string() + ": already exists\n", true); + Terminal::get().print("\e[31mError\e[m: could not create " + filesystem::get_short_path(target_path).string() + ": already exists\n", true); return; } @@ -331,12 +331,12 @@ Directories::Directories() : Gtk::ListViewText(1) { select(target_path); } else { - Terminal::get().print("\e[31mError\e[m: could not create " + target_path.string() + ": " + ec.message(), true); + Terminal::get().print("\e[31mError\e[m: could not create " + filesystem::get_short_path(target_path).string() + ": " + ec.message(), true); return; } } else { - Terminal::get().print("\e[31mError\e[m: could not create " + target_path.string() + ": already exists\n", true); + Terminal::get().print("\e[31mError\e[m: could not create " + filesystem::get_short_path(target_path).string() + ": already exists\n", true); return; } @@ -372,7 +372,7 @@ Directories::Directories() : Gtk::ListViewText(1) { auto target_path = source_path.parent_path() / content; if(boost::filesystem::exists(target_path, ec)) { - Terminal::get().print("\e[31mError\e[m: could not rename to " + target_path.string() + ": already exists\n", true); + Terminal::get().print("\e[31mError\e[m: could not rename to " + filesystem::get_short_path(target_path).string() + ": already exists\n", true); return; } @@ -381,7 +381,7 @@ Directories::Directories() : Gtk::ListViewText(1) { boost::filesystem::rename(source_path, target_path, ec); if(ec) { - Terminal::get().print("\e[31mError\e[m: could not rename " + source_path.string() + ": " + ec.message() + '\n', true); + Terminal::get().print("\e[31mError\e[m: could not rename " + filesystem::get_short_path(source_path).string() + ": " + ec.message() + '\n', true); return; } update(); @@ -412,7 +412,7 @@ Directories::Directories() : Gtk::ListViewText(1) { if(view->language) new_language_id = view->language->get_id(); if(new_language_id != old_language_id) - Terminal::get().print("\e[33mWarning\e[m: language for " + target_path.string() + " has changed. Please reopen the file\n"); + Terminal::get().print("\e[33mWarning\e[m: language for " + filesystem::get_short_path(target_path).string() + " has changed.\nPlease reopen the file.\n"); } } @@ -448,7 +448,7 @@ Directories::Directories() : Gtk::ListViewText(1) { boost::filesystem::remove_all(menu_popup_row_path, ec); if(ec) { - Terminal::get().print("\e[31mError\e[m: could not delete " + menu_popup_row_path.string() + ": " + ec.message() + "\n", true); + Terminal::get().print("\e[31mError\e[m: could not delete " + filesystem::get_short_path(menu_popup_row_path).string() + ": " + ec.message() + "\n", true); return; } @@ -517,7 +517,7 @@ Directories::~Directories() { void Directories::open(const boost::filesystem::path &dir_path) { boost::system::error_code ec; if(dir_path.empty() || !boost::filesystem::is_directory(dir_path, ec)) { - Terminal::get().print("\e[31mError\e[m: could not open " + dir_path.string() + '\n', true); + Terminal::get().print("\e[31mError\e[m: could not open " + filesystem::get_short_path(dir_path).string() + '\n', true); return; } diff --git a/src/meson.cpp b/src/meson.cpp index a2a6bfa..67f2a71 100644 --- a/src/meson.cpp +++ b/src/meson.cpp @@ -50,7 +50,7 @@ bool Meson::update_default_build(const boost::filesystem::path &default_build_pa boost::system::error_code ec; boost::filesystem::create_directories(default_build_path, ec); if(ec) { - Terminal::get().print("\e[31mError\e[m: could not create " + default_build_path.string() + ": " + ec.message() + "\n", true); + Terminal::get().print("\e[31mError\e[m: could not create " + filesystem::get_short_path(default_build_path).string() + ": " + ec.message() + "\n", true); return false; } } @@ -93,7 +93,7 @@ bool Meson::update_debug_build(const boost::filesystem::path &debug_build_path, boost::system::error_code ec; boost::filesystem::create_directories(debug_build_path, ec); if(ec) { - Terminal::get().print("\e[31mError\e[m: could not create " + debug_build_path.string() + ": " + ec.message() + "\n", true); + Terminal::get().print("\e[31mError\e[m: could not create " + filesystem::get_short_path(debug_build_path).string() + ": " + ec.message() + "\n", true); return false; } } diff --git a/src/notebook.cpp b/src/notebook.cpp index cdfd575..0094eef 100644 --- a/src/notebook.cpp +++ b/src/notebook.cpp @@ -117,7 +117,7 @@ bool Notebook::open(Source::View *view) { bool Notebook::open(const boost::filesystem::path &file_path_, Position position) { boost::system::error_code ec; if(file_path_.empty() || (boost::filesystem::exists(file_path_, ec) && !boost::filesystem::is_regular_file(file_path_, ec))) { - Terminal::get().print("\e[31mError\e[m: could not open " + file_path_.string() + "\n", true); + Terminal::get().print("\e[31mError\e[m: could not open " + filesystem::get_short_path(file_path_).string() + "\n", true); return false; } @@ -147,7 +147,7 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position if(boost::filesystem::exists(file_path, ec)) { std::ifstream can_read(file_path.string()); if(!can_read) { - Terminal::get().print("\e[31mError\e[m: could not open " + file_path.string() + "\n", true); + Terminal::get().print("\e[31mError\e[m: could not open " + filesystem::get_short_path(file_path).string() + "\n", true); return false; } can_read.close(); diff --git a/src/project.cpp b/src/project.cpp index e9753d3..7165a20 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -366,7 +366,7 @@ void Project::LLDB::debug_start() { Terminal::get().print("\e[31mError\e[m: build folder no longer valid, please rebuild project.\n", true); else { Terminal::get().print("\e[33mWarning\e[m: could not find executable.\n"); - Terminal::get().print("\e[32mSolution\e[m: either use Project Set Run Arguments, or open a source file within a directory where an executable is defined.\n"); + Terminal::get().print("Either use Project Set Run Arguments, or open a source file within a directory where an executable is defined.\n"); } return; } @@ -874,7 +874,7 @@ void Project::Clang::compile_and_run() { Terminal::get().print("\e[31mError\e[m: build folder no longer valid, please rebuild project.\n", true); else { Terminal::get().print("\e[33mWarning\e[m: could not find executable.\n"); - Terminal::get().print("\e[32mSolution\e[m: either use Project Set Run Arguments, or open a source file within a directory where an executable is defined.\n"); + Terminal::get().print("Either use Project Set Run Arguments, or open a source file within a directory where an executable is defined.\n"); } return; } diff --git a/src/source.cpp b/src/source.cpp index f4b4253..c413f82 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -421,7 +421,7 @@ bool Source::View::save() { } } catch(const Glib::Error &error) { - Terminal::get().print("\e[31mError\e[m: Could not save file " + filesystem::get_short_path(file_path).string() + ": " + error.what() + '\n', true); + Terminal::get().print("\e[31mError\e[m: could not save file " + filesystem::get_short_path(file_path).string() + ": " + error.what() + '\n', true); return false; } diff --git a/src/source_clang.cpp b/src/source_clang.cpp index 1332960..ee9739a 100644 --- a/src/source_clang.cpp +++ b/src/source_clang.cpp @@ -196,7 +196,7 @@ void Source::ClangViewParse::parse_initialize() { parse_state = ParseState::stop; parse_mutex.unlock(); dispatcher.post([this] { - Terminal::get().print("\e[31mError\e[m: failed to reparse " + filesystem::get_short_path(this->file_path).string() + ".\n", true); + Terminal::get().print("\e[31mError\e[m: failed to reparse " + filesystem::get_short_path(this->file_path).string() + "\n", true); status_state = ""; if(update_status_state) update_status_state(this); diff --git a/src/source_generic.cpp b/src/source_generic.cpp index a7d3f76..b56a22a 100644 --- a/src/source_generic.cpp +++ b/src/source_generic.cpp @@ -31,7 +31,7 @@ Source::GenericView::GenericView(const boost::filesystem::path &file_path, const boost::property_tree::xml_parser::read_xml(language_file.string(), pt); } catch(const std::exception &e) { - Terminal::get().print("\e[31mError\e[m: error parsing language file " + language_file.string() + ": " + e.what() + '\n', true); + Terminal::get().print("\e[31mError\e[m: error parsing language file " + filesystem::get_short_path(language_file).string() + ": " + e.what() + '\n', true); } bool has_context_class = false; parse_language_file(has_context_class, pt); diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index d54cc74..30e04cd 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -863,7 +863,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { changes_renamed.emplace_back(change); } else - Terminal::get().print("\e[31mError\e[m: could not write to file " + change->file + '\n', true); + Terminal::get().print("\e[31mError\e[m: could not write to file " + filesystem::get_short_path(change->file).string() + '\n', true); } for(auto &pair : changes_in_opened_files) { diff --git a/src/window.cpp b/src/window.cpp index 51f2526..39df5d2 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -293,7 +293,7 @@ void Window::set_menu_actions() { if(!path.empty()) { boost::system::error_code ec; if(boost::filesystem::exists(path, ec)) { - Terminal::get().print("\e[31mError\e[m: " + path.string() + " already exists.\n", true); + Terminal::get().print("\e[31mError\e[m: " + filesystem::get_short_path(path).string() + " already exists\n", true); } else { if(filesystem::write(path)) { @@ -302,10 +302,9 @@ void Window::set_menu_actions() { Notebook::get().open(path); if(!Directories::get().path.empty()) Directories::get().on_save_file(path); - Terminal::get().print("New file " + path.string() + " created.\n"); } else - Terminal::get().print("\e[31mError\e[m: could not create new file " + path.string() + ".\n", true); + Terminal::get().print("\e[31mError\e[m: could not create new file " + filesystem::get_short_path(path).string() + "\n", true); } } }); @@ -320,10 +319,9 @@ void Window::set_menu_actions() { Directories::get().update(); else Directories::get().open(path); - Terminal::get().print("New folder " + path.string() + " created.\n"); } else - Terminal::get().print("\e[31mError\e[m: " + path.string() + " already exists.\n", true); + Terminal::get().print("\e[31mError\e[m: " + filesystem::get_short_path(path).string() + " already exists\n", true); Directories::get().select(path); } }); @@ -347,22 +345,22 @@ void Window::set_menu_actions() { build_config = "project('" + project_name + "', 'c')\n\nadd_project_arguments('-std=c11', '-Wall', '-Wextra', language: 'c')\n\nexecutable('" + project_name + "', 'main.c')\n"; } else { - Terminal::get().print("\e[31mError\e[m: build management system " + Config::get().project.default_build_management_system + " not supported.\n", true); + Terminal::get().print("\e[31mError\e[m: build management system " + Config::get().project.default_build_management_system + " not supported\n", true); return; } auto c_main_path = project_path / "main.c"; auto clang_format_path = project_path / ".clang-format"; boost::system::error_code ec; if(boost::filesystem::exists(build_config_path, ec)) { - Terminal::get().print("\e[31mError\e[m: " + build_config_path.string() + " already exists.\n", true); + Terminal::get().print("\e[31mError\e[m: " + filesystem::get_short_path(build_config_path).string() + " already exists\n", true); return; } if(boost::filesystem::exists(c_main_path, ec)) { - Terminal::get().print("\e[31mError\e[m: " + c_main_path.string() + " already exists.\n", true); + Terminal::get().print("\e[31mError\e[m: " + filesystem::get_short_path(c_main_path).string() + " already exists\n", true); return; } if(boost::filesystem::exists(clang_format_path, ec)) { - Terminal::get().print("\e[31mError\e[m: " + clang_format_path.string() + " already exists.\n", true); + Terminal::get().print("\e[31mError\e[m: " + filesystem::get_short_path(clang_format_path).string() + " already exists\n", true); return; } std::string c_main = "#include \n\nint main() {\n printf(\"Hello World!\\n\");\n}\n"; @@ -371,10 +369,10 @@ void Window::set_menu_actions() { Directories::get().open(project_path); Notebook::get().open(c_main_path); Directories::get().update(); - Terminal::get().print("C project " + project_name + " \e[32mcreated\e[m.\n"); + Terminal::get().print("C project " + project_name + " \e[32mcreated\e[m\n"); } else - Terminal::get().print("\e[31mError\e[m: Could not create project " + project_path.string() + "\n", true); + Terminal::get().print("\e[31mError\e[m: Could not create project " + filesystem::get_short_path(project_path).string() + "\n", true); } }); menu.add_action("file_new_project_cpp", []() { @@ -397,22 +395,22 @@ void Window::set_menu_actions() { build_config = "project('" + project_name + "', 'cpp')\n\nadd_project_arguments('-std=c++1y', '-Wall', '-Wextra', language: 'cpp')\n\nexecutable('" + project_name + "', 'main.cpp')\n"; } else { - Terminal::get().print("\e[31mError\e[m: build management system " + Config::get().project.default_build_management_system + " not supported.\n", true); + Terminal::get().print("\e[31mError\e[m: build management system " + Config::get().project.default_build_management_system + " not supported\n", true); return; } auto cpp_main_path = project_path / "main.cpp"; auto clang_format_path = project_path / ".clang-format"; boost::system::error_code ec; if(boost::filesystem::exists(build_config_path, ec)) { - Terminal::get().print("\e[31mError\e[m: " + build_config_path.string() + " already exists.\n", true); + Terminal::get().print("\e[31mError\e[m: " + filesystem::get_short_path(build_config_path).string() + " already exists\n", true); return; } if(boost::filesystem::exists(cpp_main_path, ec)) { - Terminal::get().print("\e[31mError\e[m: " + cpp_main_path.string() + " already exists.\n", true); + Terminal::get().print("\e[31mError\e[m: " + filesystem::get_short_path(cpp_main_path).string() + " already exists\n", true); return; } if(boost::filesystem::exists(clang_format_path, ec)) { - Terminal::get().print("\e[31mError\e[m: " + clang_format_path.string() + " already exists.\n", true); + Terminal::get().print("\e[31mError\e[m: " + filesystem::get_short_path(clang_format_path).string() + " already exists\n", true); return; } std::string cpp_main = "#include \n\nint main() {\n std::cout << \"Hello World!\\n\";\n}\n"; @@ -421,10 +419,10 @@ void Window::set_menu_actions() { Directories::get().open(project_path); Notebook::get().open(cpp_main_path); Directories::get().update(); - Terminal::get().print("C++ project " + project_name + " \e[32mcreated\e[m.\n"); + Terminal::get().print("C++ project " + project_name + " \e[32mcreated\e[m\n"); } else - Terminal::get().print("\e[31mError\e[m: Could not create project " + project_path.string() + "\n", true); + Terminal::get().print("\e[31mError\e[m: Could not create project " + filesystem::get_short_path(project_path).string() + "\n", true); } }); @@ -448,13 +446,13 @@ void Window::set_menu_actions() { if(boost::filesystem::exists(view->file_path, ec)) { std::ifstream can_read(view->file_path.string()); if(!can_read) { - Terminal::get().print("\e[31mError\e[m: could not read " + view->file_path.string() + "\n", true); + Terminal::get().print("\e[31mError\e[m: could not read " + filesystem::get_short_path(view->file_path).string() + "\n", true); return; } can_read.close(); } else { - Terminal::get().print("\e[31mError\e[m: " + view->file_path.string() + " does not exist\n", true); + Terminal::get().print("\e[31mError\e[m: " + filesystem::get_short_path(view->file_path).string() + " does not exist\n", true); return; } @@ -487,10 +485,9 @@ void Window::set_menu_actions() { if(!Directories::get().path.empty()) Directories::get().update(); Notebook::get().open(path); - Terminal::get().print("File saved to: " + filesystem::get_short_path(filesystem::get_normal_path(path)).string() + "\n"); } else - Terminal::get().print("Error saving file\n", true); + Terminal::get().print("\e[31mError\e[m: could not save file " + filesystem::get_short_path(path).string() + '\n', true); } } }); From 61caf12e82a6085bae57a40b52bc43dac2727a12 Mon Sep 17 00:00:00 2001 From: eidheim Date: Mon, 29 Mar 2021 10:45:30 +0200 Subject: [PATCH 076/375] Improved and simplified folder exclusion for grep and ctags --- src/ctags.cpp | 7 +++-- src/grep.cpp | 12 ++++----- src/project_build.cpp | 60 ++++++++++++------------------------------- src/project_build.hpp | 11 +------- src/window.cpp | 7 ++--- 5 files changed, 29 insertions(+), 68 deletions(-) diff --git a/src/ctags.cpp b/src/ctags.cpp index 5550c3a..e144249 100644 --- a/src/ctags.cpp +++ b/src/ctags.cpp @@ -22,11 +22,10 @@ Ctags::Ctags(const boost::filesystem::path &path, bool enable_scope, bool enable if(boost::filesystem::is_directory(path, ec)) { auto build = Project::Build::create(path); std::string exclude; - if(!build->project_path.empty()) { + for(auto &exclude_folder : build->get_exclude_folders()) + exclude += " --exclude=\"" + exclude_folder + "/*\" --exclude=\"*/" + exclude_folder + "/*\""; + if(!build->project_path.empty()) project_path = build->project_path; - for(auto &exclude_path : build->get_exclude_paths()) - exclude += " --exclude=" + filesystem::escape_argument(filesystem::get_relative_path(exclude_path, project_path).string()); - } else project_path = path; command = Config::get().project.ctags_command + options + fields + exclude + " -R *"; diff --git a/src/grep.cpp b/src/grep.cpp index 8d3401d..f6216f3 100644 --- a/src/grep.cpp +++ b/src/grep.cpp @@ -10,16 +10,14 @@ Grep::Grep(const boost::filesystem::path &path, const std::string &pattern, bool return; auto build = Project::Build::create(path); std::string exclude; - if(!build->project_path.empty()) { - project_path = build->project_path; - for(auto &exclude_path : build->get_exclude_paths()) { + for(auto &exclude_folder : build->get_exclude_folders()) #ifdef JUCI_USE_GREP_EXCLUDE - exclude += " --exclude=" + filesystem::escape_argument(filesystem::get_relative_path(exclude_path, build->project_path).string()) + "/*"; + exclude += " --exclude=\"" + exclude_folder + "/*\" --exclude=\"*/" + exclude_folder + "/*\""; // BSD grep does not support --exclude-dir #else - exclude += " --exclude-dir=" + filesystem::escape_argument(filesystem::get_relative_path(exclude_path, build->project_path).string()); + exclude += " --exclude-dir=\"" + exclude_folder + '"'; // Need to use --exclude-dir on Linux for some reason (could not get --exclude to work) #endif - } - } + if(!build->project_path.empty()) + project_path = build->project_path; else project_path = path; diff --git a/src/project_build.cpp b/src/project_build.cpp index 09fba20..933a0d7 100644 --- a/src/project_build.cpp +++ b/src/project_build.cpp @@ -93,10 +93,22 @@ boost::filesystem::path Project::Build::get_debug_path() { return filesystem::get_normal_path(debug_build_path); } -std::vector Project::Build::get_exclude_paths() { - if(!project_path.empty()) - return {project_path / ".git"}; - return {}; +std::vector Project::Build::get_exclude_folders() { + auto default_build_path = Config::get().project.default_build_path; + boost::replace_all(default_build_path, "", ""); + + auto debug_build_path = Config::get().project.debug_build_path; + boost::replace_all(debug_build_path, "", Config::get().project.default_build_path); + boost::replace_all(debug_build_path, "", ""); + + return std::vector{ + ".git", "build", "debug", // Common exclude folders + boost::filesystem::path(default_build_path).filename().string(), boost::filesystem::path(debug_build_path).filename().string(), // C/C++ + "target", // Rust + "node_modules", "dist", "coverage", ".expo", // JavaScript + ".mypy_cache", + "__pycache__" // Python + }; } Project::CMakeBuild::CMakeBuild(const boost::filesystem::path &path) : Project::Build(), cmake(path) { @@ -146,13 +158,6 @@ bool Project::CMakeBuild::is_valid() { return true; } -std::vector Project::CMakeBuild::get_exclude_paths() { - auto exclude_paths = Project::Build::get_exclude_paths(); - exclude_paths.emplace_back(get_default_path()); - exclude_paths.emplace_back(get_debug_path()); - return exclude_paths; -} - Project::MesonBuild::MesonBuild(const boost::filesystem::path &path) : Project::Build(), meson(path) { project_path = meson.project_path; } @@ -197,39 +202,6 @@ bool Project::MesonBuild::is_valid() { return true; } -std::vector Project::MesonBuild::get_exclude_paths() { - auto exclude_paths = Project::Build::get_exclude_paths(); - exclude_paths.emplace_back(get_default_path()); - exclude_paths.emplace_back(get_debug_path()); - return exclude_paths; -} - -std::vector Project::CompileCommandsBuild::get_exclude_paths() { - auto exclude_paths = Project::Build::get_exclude_paths(); - exclude_paths.emplace_back(get_default_path()); - exclude_paths.emplace_back(get_debug_path()); - return exclude_paths; -} - std::string Project::CargoBuild::get_compile_command() { return Config::get().project.cargo_command + " build"; } - -std::vector Project::CargoBuild::get_exclude_paths() { - auto exclude_paths = Project::Build::get_exclude_paths(); - exclude_paths.emplace_back(project_path / "target"); - return exclude_paths; -} - -std::vector Project::NpmBuild::get_exclude_paths() { - auto exclude_paths = Project::Build::get_exclude_paths(); - exclude_paths.emplace_back(project_path / "node_modules"); - return exclude_paths; -} - -std::vector Project::PythonMain::get_exclude_paths() { - auto exclude_paths = Project::Build::get_exclude_paths(); - exclude_paths.emplace_back(project_path / ".mypy_cache"); - exclude_paths.emplace_back(project_path / "__pycache__"); - return exclude_paths; -} diff --git a/src/project_build.hpp b/src/project_build.hpp index 16bfb2a..f1677b6 100644 --- a/src/project_build.hpp +++ b/src/project_build.hpp @@ -21,7 +21,7 @@ namespace Project { /// Returns true if the project path reported by build system is correct virtual bool is_valid() { return true; } - virtual std::vector get_exclude_paths(); + std::vector get_exclude_folders(); static std::unique_ptr create(const boost::filesystem::path &path); }; @@ -39,8 +39,6 @@ namespace Project { boost::filesystem::path get_executable(const boost::filesystem::path &path) override; bool is_valid() override; - - std::vector get_exclude_paths() override; }; class MesonBuild : public Build { @@ -56,13 +54,10 @@ namespace Project { boost::filesystem::path get_executable(const boost::filesystem::path &path) override; bool is_valid() override; - - std::vector get_exclude_paths() override; }; class CompileCommandsBuild : public Build { public: - std::vector get_exclude_paths() override; }; class CargoBuild : public Build { @@ -74,17 +69,13 @@ namespace Project { std::string get_compile_command() override; boost::filesystem::path get_executable(const boost::filesystem::path &path) override { return get_debug_path() / project_path.filename(); } - - std::vector get_exclude_paths() override; }; class NpmBuild : public Build { public: - std::vector get_exclude_paths() override; }; class PythonMain : public Build { public: - std::vector get_exclude_paths() override; }; } // namespace Project diff --git a/src/window.cpp b/src/window.cpp index 39df5d2..6826cd8 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -924,7 +924,7 @@ void Window::set_menu_actions() { menu.add_action("source_find_file", []() { auto view_folder = Project::get_preferably_view_folder(); auto build = Project::Build::create(view_folder); - auto exclude_paths = build->get_exclude_paths(); + auto exclude_folders = build->get_exclude_folders(); if(!build->project_path.empty()) view_folder = build->project_path; @@ -945,8 +945,9 @@ void Window::set_menu_actions() { auto path = iter->path(); // ignore folders if(!boost::filesystem::is_regular_file(path, ec)) { - if(std::any_of(exclude_paths.begin(), exclude_paths.end(), [&path](const boost::filesystem::path &exclude_path) { - return path == exclude_path; + auto filename = path.filename(); + if(std::any_of(exclude_folders.begin(), exclude_folders.end(), [&filename](const std::string &exclude_folder) { + return filename == exclude_folder; })) iter.no_push(); continue; From cd6af96e0edc9e0f46eee491f1513a79e5657f7b Mon Sep 17 00:00:00 2001 From: eidheim Date: Mon, 29 Mar 2021 14:22:12 +0200 Subject: [PATCH 077/375] Find Symbol now always uses ctags --- src/project.cpp | 140 ------------------------------------------------ src/project.hpp | 3 +- 2 files changed, 1 insertion(+), 142 deletions(-) diff --git a/src/project.cpp b/src/project.cpp index 7165a20..ddc319f 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -15,7 +15,6 @@ #include "info.hpp" #include "snippets.hpp" #include "source_clang.hpp" -#include "source_language_protocol.hpp" #include "usages_clang.hpp" #include @@ -675,145 +674,6 @@ void Project::LLDB::debug_write(const std::string &buffer) { } #endif -void Project::LanguageProtocol::show_symbols() { - auto project_path = std::make_shared(build->project_path); - auto view = Notebook::get().get_current_view(); - auto language_protocol_view = dynamic_cast(view); - - if(project_path->empty()) { - if(language_protocol_view) - *project_path = language_protocol_view->file_path.parent_path(); - else { - 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()) - return Base::show_symbols(); - - auto client = ::LanguageProtocol::Client::get(language_protocol_view ? language_protocol_view->file_path : *project_path, language_id); - auto capabilities = client->initialize(language_protocol_view); - - if(!capabilities.workspace_symbol && !(capabilities.document_symbol && language_protocol_view)) - return Base::show_symbols(); - - if(view) - SelectionDialog::create(view, true, true); - else - SelectionDialog::create(true, true); - - SelectionDialog::get()->on_hide = [] { - SelectionDialog::get()->on_search_entry_changed = nullptr; // To delete client object - }; - - auto locations = std::make_shared>>(); - if(capabilities.workspace_symbol) { - SelectionDialog::get()->on_search_entry_changed = [client, project_path, locations](const std::string &text) { - if(text.size() > 1) - return; - else { - locations->clear(); - SelectionDialog::get()->erase_rows(); - if(text.empty()) - return; - } - std::promise result_processed; - client->write_request(nullptr, "workspace/symbol", R"("query":")" + text + '"', [&result_processed, locations, project_path](const boost::property_tree::ptree &result, bool error) { - if(!error) { - for(auto it = result.begin(); it != result.end(); ++it) { - try { - ::LanguageProtocol::Location location(it->second.get_child("location")); - if(filesystem::file_in_path(location.file, *project_path)) { - auto container = it->second.get("containerName", ""); - if(container == "null") - container.clear(); - - auto row = filesystem::get_relative_path(location.file, *project_path).string() + ':' + std::to_string(location.range.start.line + 1) + ": " + (!container.empty() ? Glib::Markup::escape_text(container) + "::" : "") + "" + Glib::Markup::escape_text(it->second.get("name")) + ""; - - locations->emplace_back(std::make_pair(std::move(location), std::move(row))); - } - } - catch(...) { - } - } - } - result_processed.set_value(); - }); - result_processed.get_future().get(); - - std::sort(locations->begin(), locations->end()); - for(auto &location : *locations) { - SelectionDialog::get()->add_row(location.second); - location.second.clear(); - } - }; - } - else { - std::promise result_processed; - client->write_request(language_protocol_view, "textDocument/documentSymbol", R"("textDocument":{"uri":")" + language_protocol_view->uri + "\"}", [&result_processed, locations, language_protocol_view](const boost::property_tree::ptree &result, bool error) { - if(!error) { - std::function parse_result = [locations, &parse_result, language_protocol_view](const boost::property_tree::ptree &pt, const std::string &container) { - for(auto it = pt.begin(); it != pt.end(); ++it) { - try { - std::unique_ptr<::LanguageProtocol::Location> location; - std::string prefix; - auto location_pt = it->second.get_child_optional("location"); - if(location_pt) { - location = std::make_unique<::LanguageProtocol::Location>(*location_pt); - std::string container = it->second.get("containerName", ""); - if(container == "null") - container.clear(); - if(!container.empty()) - prefix = container + "::"; - } - else { - location = std::make_unique<::LanguageProtocol::Location>(language_protocol_view->file_path.string(), ::LanguageProtocol::Range(it->second.get_child("range"))); - if(!container.empty()) - prefix = container + "::"; - } - auto row = std::to_string(location->range.start.line + 1) + ": " + Glib::Markup::escape_text(prefix) + "" + Glib::Markup::escape_text(it->second.get("name")) + ""; - locations->emplace_back(std::make_pair(std::move(*location), std::move(row))); - auto children = it->second.get_child_optional("children"); - if(children) - parse_result(*children, (!container.empty() ? container + "::" : "") + it->second.get("name")); - } - catch(...) { - } - } - }; - parse_result(result, ""); - } - result_processed.set_value(); - }); - result_processed.get_future().get(); - - std::sort(locations->begin(), locations->end()); - for(auto &location : *locations) { - SelectionDialog::get()->add_row(location.second); - location.second.clear(); - } - } - - SelectionDialog::get()->on_select = [locations](unsigned int index, const std::string &text, bool hide_window) { - auto &location = (*locations)[index].first; - boost::system::error_code ec; - if(!boost::filesystem::is_regular_file(location.file, ec)) - return; - if(Notebook::get().open(location.file)) { - auto view = Notebook::get().get_current_view(); - view->place_cursor_at_line_offset(location.range.start.line, location.range.start.character); - view->scroll_to_cursor_delayed(true, false); - } - }; - - if(view) - view->hide_tooltips(); - SelectionDialog::get()->show(); -} - std::pair Project::Clang::get_run_arguments() { auto build_path = build->get_default_path(); if(build_path.empty()) diff --git a/src/project.hpp b/src/project.hpp index 6503d1d..942c5a7 100644 --- a/src/project.hpp +++ b/src/project.hpp @@ -59,7 +59,7 @@ namespace Project { virtual void compile_and_run(); virtual void recreate_build(); - virtual void show_symbols(); + void show_symbols(); virtual std::pair debug_get_run_arguments(); virtual Project::DebugOptions *debug_get_options() { return nullptr; } @@ -105,7 +105,6 @@ namespace Project { class LanguageProtocol : public virtual Base { public: virtual std::string get_language_id() = 0; - void show_symbols() override; }; class Clang : public LLDB { From f709c31ec3cf96ddb69be5431d01a6ea34867097 Mon Sep 17 00:00:00 2001 From: eidheim Date: Mon, 29 Mar 2021 15:09:25 +0200 Subject: [PATCH 078/375] Language client, rename: now open, rename, save and close unopened buffers (as seems to be necessary for some language servers) --- src/notebook.cpp | 14 +- src/notebook.hpp | 1 + src/source_language_protocol.cpp | 217 ++++++++++++++++++------------- src/source_language_protocol.hpp | 1 + src/terminal.cpp | 24 ++-- 5 files changed, 146 insertions(+), 111 deletions(-) diff --git a/src/notebook.cpp b/src/notebook.cpp index 0094eef..76a4dfa 100644 --- a/src/notebook.cpp +++ b/src/notebook.cpp @@ -333,7 +333,7 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position //Set up tab label tab_labels.emplace_back(new TabLabel([this, view]() { - close(get_index(view)); + close(view); })); view->update_tab_label = [this](Source::BaseView *view) { std::string title = view->file_path.filename().string(); @@ -582,6 +582,14 @@ bool Notebook::close(size_t index) { return true; } +bool Notebook::close(Source::View *view) { + return close(get_index(view)); +} + +bool Notebook::close_current() { + return close(get_current_view()); +} + void Notebook::delete_cursor_locations(Source::View *view) { for(auto it = cursor_locations.begin(); it != cursor_locations.end();) { if(it->view == view) { @@ -607,10 +615,6 @@ void Notebook::delete_cursor_locations(Source::View *view) { } } -bool Notebook::close_current() { - return close(get_index(get_current_view())); -} - void Notebook::next() { if(auto view = get_current_view()) { auto notebook_page = get_notebook_page(view); diff --git a/src/notebook.hpp b/src/notebook.hpp index 0e4cf20..c51d363 100644 --- a/src/notebook.hpp +++ b/src/notebook.hpp @@ -50,6 +50,7 @@ public: bool save(size_t index); bool save_current(); bool close(size_t index); + bool close(Source::View *view); bool close_current(); void next(); void previous(); diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 30e04cd..9337a5f 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -121,6 +121,18 @@ LanguageProtocol::Client::~Client() { process->kill(); } +boost::optional LanguageProtocol::Client::get_capabilities(Source::LanguageProtocolView *view) { + if(view) { + LockGuard lock(views_mutex); + views.emplace(view); + } + + LockGuard lock(initialize_mutex); + if(initialized) + return capabilities; + return {}; +} + LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::LanguageProtocolView *view) { if(view) { LockGuard lock(views_mutex); @@ -452,35 +464,43 @@ void Source::LanguageProtocolView::initialize() { update_status_state(this); set_editable(false); - initialize_thread = std::thread([this] { - auto capabilities = client->initialize(this); - dispatcher.post([this, capabilities] { - this->capabilities = capabilities; - set_editable(true); + auto init = [this](const LanguageProtocol::Capabilities &capabilities) { + this->capabilities = capabilities; + set_editable(true); - 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 + "\"}"); + 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 + "\"}"); - if(!initialized) { - setup_signals(); - setup_autocomplete(); - setup_navigation_and_refactoring(); - Menu::get().toggle_menu_items(); - } + if(!initialized) { + setup_signals(); + 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); - } + if(status_state == "initializing...") { + status_state = ""; + if(update_status_state) + update_status_state(this); + } - update_type_coverage(); + update_type_coverage(); + + initialized = true; + }; - initialized = true; + if(auto capabilities = client->get_capabilities(this)) + init(*capabilities); + else { + initialize_thread = std::thread([this, init] { + auto capabilities = client->initialize(this); + dispatcher.post([init, capabilities] { + init(capabilities); + }); }); - }); + } } void Source::LanguageProtocolView::close() { @@ -743,8 +763,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { if(capabilities.rename || capabilities.document_highlight) { rename_similar_tokens = [this](const std::string &text) { - class Changes { - public: + struct Changes { std::string file; std::vector text_edits; }; @@ -754,10 +773,10 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { return; auto iter = get_buffer()->get_insert()->get_iter(); - std::vector changes; + std::vector changes_vec; std::promise result_processed; if(capabilities.rename) { - client->write_request(this, "textDocument/rename", R"("textDocument":{"uri":")" + uri + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "newName": ")" + text + "\"", [this, &changes, &result_processed](const boost::property_tree::ptree &result, bool error) { + client->write_request(this, "textDocument/rename", R"("textDocument":{"uri":")" + uri + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "newName": ")" + text + "\"", [this, &changes_vec, &result_processed](const boost::property_tree::ptree &result, bool error) { if(!error) { boost::filesystem::path project_path; auto build = Project::Build::create(file_path); @@ -774,7 +793,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { std::vector edits; for(auto edit_it = file_it->second.begin(); edit_it != file_it->second.end(); ++edit_it) edits.emplace_back(edit_it->second); - changes.emplace_back(Changes{std::move(file), std::move(edits)}); + changes_vec.emplace_back(Changes{std::move(file), std::move(edits)}); } } } @@ -782,28 +801,28 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { for(auto change_it = changes_pt->begin(); change_it != changes_pt->end(); ++change_it) { LanguageProtocol::TextDocumentEdit text_document_edit(change_it->second); if(filesystem::file_in_path(text_document_edit.file, project_path)) - changes.emplace_back(Changes{std::move(text_document_edit.file), std::move(text_document_edit.edits)}); + changes_vec.emplace_back(Changes{std::move(text_document_edit.file), std::move(text_document_edit.edits)}); } } } catch(...) { - changes.clear(); + changes_vec.clear(); } } result_processed.set_value(); }); } else { - client->write_request(this, "textDocument/documentHighlight", R"("textDocument":{"uri":")" + uri + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "context": {"includeDeclaration": true})", [this, &changes, &text, &result_processed](const boost::property_tree::ptree &result, bool error) { + client->write_request(this, "textDocument/documentHighlight", R"("textDocument":{"uri":")" + uri + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "context": {"includeDeclaration": true})", [this, &changes_vec, &text, &result_processed](const boost::property_tree::ptree &result, bool error) { if(!error) { try { std::vector edits; for(auto it = result.begin(); it != result.end(); ++it) edits.emplace_back(it->second, text); - changes.emplace_back(Changes{file_path.string(), std::move(edits)}); + changes_vec.emplace_back(Changes{file_path.string(), std::move(edits)}); } catch(...) { - changes.clear(); + changes_vec.clear(); } } result_processed.set_value(); @@ -811,95 +830,105 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { } result_processed.get_future().get(); - std::vector changes_renamed; + auto current_view = Notebook::get().get_current_view(); - std::vector changes_in_unopened_files; - std::vector> changes_in_opened_files; - for(auto &change : changes) { - auto view_it = views.end(); + struct ChangesAndView { + Changes *changes; + Source::View *view; + bool close; + }; + std::vector changes_and_views; + + for(auto &changes : changes_vec) { + Source::View *view = nullptr; for(auto it = views.begin(); it != views.end(); ++it) { - if((*it)->file_path == change.file) { - view_it = it; + if((*it)->file_path == changes.file) { + view = *it; break; } } - if(view_it != views.end()) - changes_in_opened_files.emplace_back(&change, *view_it); + if(!view) { + if(!Notebook::get().open(changes.file)) + return; + view = Notebook::get().get_current_view(); + changes_and_views.emplace_back(ChangesAndView{&changes, view, true}); + } else - changes_in_unopened_files.emplace_back(&change); + changes_and_views.emplace_back(ChangesAndView{&changes, view, false}); } - // Write changes to unopened files first, since this might improve server handling of opened files that will be changed after - for(auto &change : changes_in_unopened_files) { - Glib::ustring buffer; - { - std::ifstream stream(change->file, std::ifstream::binary); - if(stream) - buffer.assign(std::istreambuf_iterator(stream), std::istreambuf_iterator()); - } - std::ofstream stream(change->file, std::ifstream::binary); - if(!buffer.empty() && stream) { - std::vector 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 edit_it = change->text_edits.rbegin(); edit_it != change->text_edits.rend(); ++edit_it) { - auto start_line = edit_it->range.start.line; - auto end_line = edit_it->range.end.line; - if(static_cast(start_line) < lines_start_pos.size()) { - auto start = lines_start_pos[start_line] + edit_it->range.start.character; - size_t end; - if(static_cast(end_line) >= lines_start_pos.size()) - end = buffer.size(); - else - end = lines_start_pos[end_line] + edit_it->range.end.character; - if(start < buffer.size() && end <= buffer.size()) { - buffer.replace(start, end - start, edit_it->new_text); - } - } - } - stream.write(buffer.data(), buffer.bytes()); - changes_renamed.emplace_back(change); - } - else - Terminal::get().print("\e[31mError\e[m: could not write to file " + filesystem::get_short_path(change->file).string() + '\n', true); + if(current_view) + Notebook::get().open(current_view); + + if(!changes_and_views.empty()) { + Terminal::get().print("Renamed "); + Terminal::get().print(previous_text, true); + Terminal::get().print(" to "); + Terminal::get().print(text, true); + Terminal::get().print(" at:\n"); } - for(auto &pair : changes_in_opened_files) { - auto change = pair.first; - auto view = pair.second; + for(auto &changes_and_view : changes_and_views) { + auto changes = changes_and_view.changes; + auto view = changes_and_view.view; auto buffer = view->get_buffer(); buffer->begin_user_action(); auto end_iter = buffer->end(); // If entire buffer is replaced - if(change->text_edits.size() == 1 && - change->text_edits[0].range.start.line == 0 && change->text_edits[0].range.start.character == 0 && - (change->text_edits[0].range.end.line > end_iter.get_line() || - (change->text_edits[0].range.end.line == end_iter.get_line() && change->text_edits[0].range.end.character >= end_iter.get_line_offset()))) - view->replace_text(change->text_edits[0].new_text); + if(changes->text_edits.size() == 1 && + changes->text_edits[0].range.start.line == 0 && changes->text_edits[0].range.start.character == 0 && + (changes->text_edits[0].range.end.line > end_iter.get_line() || + (changes->text_edits[0].range.end.line == end_iter.get_line() && changes->text_edits[0].range.end.character >= end_iter.get_line_offset()))) { + view->replace_text(changes->text_edits[0].new_text); + + Terminal::get().print(filesystem::get_short_path(view->file_path).string() + ":1:1\n"); + } else { - for(auto edit_it = change->text_edits.rbegin(); edit_it != change->text_edits.rend(); ++edit_it) { + struct TerminalOutput { + std::string prefix; + std::string new_text; + std::string postfix; + }; + std::list terminal_output_list; + + for(auto edit_it = changes->text_edits.rbegin(); edit_it != changes->text_edits.rend(); ++edit_it) { auto start_iter = view->get_iter_at_line_pos(edit_it->range.start.line, edit_it->range.start.character); auto end_iter = view->get_iter_at_line_pos(edit_it->range.end.line, edit_it->range.end.character); + if(view != current_view) + view->get_buffer()->place_cursor(start_iter); buffer->erase(start_iter, end_iter); start_iter = view->get_iter_at_line_pos(edit_it->range.start.line, edit_it->range.start.character); + + auto start_line_iter = buffer->get_iter_at_line(start_iter.get_line()); + while((*start_line_iter == ' ' || *start_line_iter == '\t') && start_line_iter < start_iter && start_line_iter.forward_char()) { + } + auto end_line_iter = start_iter; + while(!end_line_iter.ends_line() && end_line_iter.forward_char()) { + } + terminal_output_list.emplace_front(TerminalOutput{filesystem::get_short_path(view->file_path).string() + ':' + std::to_string(edit_it->range.start.line + 1) + ':' + std::to_string(edit_it->range.start.character + 1) + ": " + buffer->get_text(start_line_iter, start_iter), + edit_it->new_text, + buffer->get_text(start_iter, end_line_iter) + '\n'}); + buffer->insert(start_iter, edit_it->new_text); } + + for(auto &output : terminal_output_list) { + Terminal::get().print(output.prefix); + Terminal::get().print(output.new_text, true); + Terminal::get().print(output.postfix); + } } buffer->end_user_action(); - view->save(); - changes_renamed.emplace_back(change); + if(!view->save()) + return; } - if(!changes_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 &changes_and_view : changes_and_views) { + if(changes_and_view.close) + Notebook::get().close(changes_and_view.view); + changes_and_view.close = false; } }; } diff --git a/src/source_language_protocol.hpp b/src/source_language_protocol.hpp index 0175017..05a6713 100644 --- a/src/source_language_protocol.hpp +++ b/src/source_language_protocol.hpp @@ -145,6 +145,7 @@ namespace LanguageProtocol { ~Client(); + boost::optional get_capabilities(Source::LanguageProtocolView *view); Capabilities initialize(Source::LanguageProtocolView *view); void close(Source::LanguageProtocolView *view); diff --git a/src/terminal.cpp b/src/terminal.cpp index 136ea28..5a99e3e 100644 --- a/src/terminal.cpp +++ b/src/terminal.cpp @@ -404,18 +404,18 @@ boost::optional Terminal::find_link(const std::string &line) { if(line.size() >= 1000) // Due to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86164 return {}; - const static std::regex link_regex("^([A-Z]:)?([^:]+):([0-9]+):([0-9]+): .*$|" // C/C++ compile warning/error/rename usages - "^In file included from ([A-Z]:)?([^:]+):([0-9]+)[:,]$|" // C/C++ extra compile warning/error info - "^ from ([A-Z]:)?([^:]+):([0-9]+)[:,]$|" // C/C++ extra compile warning/error info (gcc) - "^ +--> ([A-Z]:)?([^:]+):([0-9]+):([0-9]+)$|" // Rust - "^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) - "^([A-Z]:)?([\\\\/][^:]+):([0-9]+)$|" // Node.js - "^ +at .*?\\(([A-Z]:)?([^:]+):([0-9]+):([0-9]+)\\).*$|" // Node.js stack trace - "^ +at ([A-Z]:)?([^:]+):([0-9]+):([0-9]+).*$|" // Node.js stack trace - "^ File \"([A-Z]:)?([^\"]+)\", line ([0-9]+), in .*$|" // Python - "^.*?([A-Z]:)?([a-zA-Z0-9._\\\\/][a-zA-Z0-9._\\-\\\\/]*):([0-9]+):([0-9]+).*$", // Posix path:line:column + const static std::regex link_regex("^([A-Z]:)?([^:]+):([0-9]+):([0-9]+): .*$|" // C/C++ compile warning/error/rename usages + "^In file included from ([A-Z]:)?([^:]+):([0-9]+)[:,]$|" // C/C++ extra compile warning/error info + "^ from ([A-Z]:)?([^:]+):([0-9]+)[:,]$|" // C/C++ extra compile warning/error info (gcc) + "^ +--> ([A-Z]:)?([^:]+):([0-9]+):([0-9]+)$|" // Rust + "^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) + "^([A-Z]:)?([\\\\/][^:]+):([0-9]+)$|" // Node.js + "^ +at .*?\\(([A-Z]:)?([^:]+):([0-9]+):([0-9]+)\\).*$|" // Node.js stack trace + "^ +at ([A-Z]:)?([^:]+):([0-9]+):([0-9]+).*$|" // Node.js stack trace + "^ File \"([A-Z]:)?([^\"]+)\", line ([0-9]+), in .*$|" // Python + "^.*?([A-Z]:)?([a-zA-Z0-9._\\\\/~][a-zA-Z0-9._\\-\\\\/]*):([0-9]+):([0-9]+).*$", // Posix path:line:column std::regex::optimize); std::smatch sm; if(std::regex_match(line, sm, link_regex)) { From d2a5d81b590b89e8d0ad87e5fbfceacc2ae87f1a Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 30 Mar 2021 20:27:30 +0200 Subject: [PATCH 079/375] Made project name terminal output bold on creating new C/C++ project --- src/window.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/window.cpp b/src/window.cpp index 6826cd8..7685aea 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -369,7 +369,9 @@ void Window::set_menu_actions() { Directories::get().open(project_path); Notebook::get().open(c_main_path); Directories::get().update(); - Terminal::get().print("C project " + project_name + " \e[32mcreated\e[m\n"); + Terminal::get().print("C project "); + Terminal::get().print(project_name, true); + Terminal::get().print(" \e[32mcreated\e[m\n"); } else Terminal::get().print("\e[31mError\e[m: Could not create project " + filesystem::get_short_path(project_path).string() + "\n", true); @@ -419,7 +421,9 @@ void Window::set_menu_actions() { Directories::get().open(project_path); Notebook::get().open(cpp_main_path); Directories::get().update(); - Terminal::get().print("C++ project " + project_name + " \e[32mcreated\e[m\n"); + Terminal::get().print("C++ project "); + Terminal::get().print(project_name, true); + Terminal::get().print(" \e[32mcreated\e[m\n"); } else Terminal::get().print("\e[31mError\e[m: Could not create project " + filesystem::get_short_path(project_path).string() + "\n", true); From 076ee4d7cf42df9dd784ecc53fac329547c7101d Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 31 Mar 2021 10:45:20 +0200 Subject: [PATCH 080/375] No longer resolves symbolic links when finding current path --- src/dialogs.cpp | 6 +++--- src/filesystem.cpp | 28 ++++++++++++++++++++++++++++ src/filesystem.hpp | 3 +++ src/juci.cpp | 7 +++---- src/project.cpp | 14 ++++---------- 5 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/dialogs.cpp b/src/dialogs.cpp index f0ef662..6d1e90f 100644 --- a/src/dialogs.cpp +++ b/src/dialogs.cpp @@ -1,4 +1,5 @@ #include "dialogs.hpp" +#include "filesystem.hpp" #include Dialog::Message::Message(const std::string &text, std::function &&on_cancel, bool show_progress_bar) : Gtk::Window(Gtk::WindowType::WINDOW_POPUP) { @@ -84,9 +85,8 @@ std::string Dialog::gtk_dialog(const boost::filesystem::path &path, const std::s else if(!path.empty()) gtk_file_chooser_set_current_folder(reinterpret_cast(dialog.gobj()), path.string().c_str()); else { - boost::system::error_code ec; - auto current_path = boost::filesystem::current_path(ec); - if(!ec) + auto current_path = filesystem::get_current_path(); + if(!current_path.empty()) gtk_file_chooser_set_current_folder(reinterpret_cast(dialog.gobj()), current_path.string().c_str()); } diff --git a/src/filesystem.cpp b/src/filesystem.cpp index 9a6f07d..fdee257 100644 --- a/src/filesystem.cpp +++ b/src/filesystem.cpp @@ -1,4 +1,5 @@ #include "filesystem.hpp" +#include "process.hpp" #include "utility.hpp" #include #include @@ -81,6 +82,33 @@ std::string filesystem::unescape_argument(const std::string &argument) { return unescaped; } +boost::filesystem::path filesystem::get_current_path() noexcept { + static boost::filesystem::path current_path; + if(!current_path.empty()) + return current_path; +#ifdef _WIN32 + boost::system::error_code ec; + auto path = boost::filesystem::current_path(ec); + if(!ec) { + current_path = std::move(path); + return current_path; + } + return boost::filesystem::path(); +#else + std::string path; + TinyProcessLib::Process process("pwd", "", [&path](const char *buffer, size_t length) { + path += std::string(buffer, length); + }); + if(process.get_exit_status() == 0) { + if(!path.empty() && path.back() == '\n') + path.pop_back(); + current_path = boost::filesystem::path(path); + return current_path; + } + return boost::filesystem::path(); +#endif +} + boost::filesystem::path filesystem::get_home_path() noexcept { static boost::filesystem::path home_path; if(!home_path.empty()) diff --git a/src/filesystem.hpp b/src/filesystem.hpp index 3056315..a09710c 100644 --- a/src/filesystem.hpp +++ b/src/filesystem.hpp @@ -16,6 +16,9 @@ public: static std::string escape_argument(const std::string &argument); static std::string unescape_argument(const std::string &argument); + /// Does not resolve symbolic links. Returns empty path on failure + static boost::filesystem::path get_current_path() noexcept; + /// Returns empty path on failure static boost::filesystem::path get_home_path() noexcept; /// Replaces home path with ~ static boost::filesystem::path get_short_path(const boost::filesystem::path &path) noexcept; diff --git a/src/juci.cpp b/src/juci.cpp index 41d31f1..733a43b 100644 --- a/src/juci.cpp +++ b/src/juci.cpp @@ -24,13 +24,12 @@ int Application::on_command_line(const Glib::RefPtr 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) + auto current_path = filesystem::get_current_path(); + if(current_path.empty()) errors.emplace_back("\e[31mError\e[m: 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) + if(path.is_relative() && !current_path.empty()) path = current_path / path; boost::system::error_code ec; if(boost::filesystem::exists(path, ec)) { diff --git a/src/project.cpp b/src/project.cpp index ddc319f..799b89f 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -35,11 +35,8 @@ boost::filesystem::path Project::get_preferably_view_folder() { else if(!Directories::get().path.empty()) return Directories::get().path; else { - boost::system::error_code ec; - auto current_path = boost::filesystem::current_path(ec); - if(ec) - return boost::filesystem::path(); - return current_path; + auto current_path = filesystem::get_current_path(); + return !current_path.empty() ? current_path : boost::filesystem::path(); } } @@ -49,11 +46,8 @@ boost::filesystem::path Project::get_preferably_directory_folder() { else if(auto view = Notebook::get().get_current_view()) return view->file_path.parent_path(); else { - boost::system::error_code ec; - auto current_path = boost::filesystem::current_path(ec); - if(ec) - return boost::filesystem::path(); - return current_path; + auto current_path = filesystem::get_current_path(); + return !current_path.empty() ? current_path : boost::filesystem::path(); } } From eefe86f2b225e83f07f1f52f8875fb6429ce04ad Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 31 Mar 2021 10:53:18 +0200 Subject: [PATCH 081/375] Cleanup of dialog source/header files --- src/CMakeLists.txt | 3 +- src/cmake.cpp | 2 +- src/{dialogs.cpp => dialog.cpp} | 32 ++++- src/{dialogs.hpp => dialog.hpp} | 13 +- src/dialogs_unix.cpp | 31 ----- src/dialogs_win.cpp | 163 ------------------------ src/meson.cpp | 2 +- src/source_clang.cpp | 2 +- src/usages_clang.cpp | 2 +- src/window.cpp | 2 +- tests/CMakeLists.txt | 2 +- tests/stubs/{dialogs.cpp => dialog.cpp} | 2 +- 12 files changed, 46 insertions(+), 210 deletions(-) rename src/{dialogs.cpp => dialog.cpp} (72%) rename src/{dialogs.hpp => dialog.hpp} (99%) delete mode 100644 src/dialogs_unix.cpp delete mode 100644 src/dialogs_win.cpp rename tests/stubs/{dialogs.cpp => dialog.cpp} (92%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 86eadb9..14a0229 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -43,8 +43,7 @@ target_link_libraries(juci_shared set(JUCI_FILES config.cpp - dialogs.cpp - dialogs_unix.cpp + dialog.cpp directories.cpp entrybox.cpp info.cpp diff --git a/src/cmake.cpp b/src/cmake.cpp index 8cf8cb8..eb00c17 100644 --- a/src/cmake.cpp +++ b/src/cmake.cpp @@ -1,7 +1,7 @@ #include "cmake.hpp" #include "compile_commands.hpp" #include "config.hpp" -#include "dialogs.hpp" +#include "dialog.hpp" #include "filesystem.hpp" #include "terminal.hpp" #include "utility.hpp" diff --git a/src/dialogs.cpp b/src/dialog.cpp similarity index 72% rename from src/dialogs.cpp rename to src/dialog.cpp index 6d1e90f..f647098 100644 --- a/src/dialogs.cpp +++ b/src/dialog.cpp @@ -1,4 +1,4 @@ -#include "dialogs.hpp" +#include "dialog.hpp" #include "filesystem.hpp" #include @@ -94,3 +94,33 @@ std::string Dialog::gtk_dialog(const boost::filesystem::path &path, const std::s dialog.add_button(button.first, button.second); return dialog.run() == Gtk::RESPONSE_OK ? dialog.get_filename() : ""; } + +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); +} + +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); +} + +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); +} + +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); +} + +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); +} diff --git a/src/dialogs.hpp b/src/dialog.hpp similarity index 99% rename from src/dialogs.hpp rename to src/dialog.hpp index b4be549..5ea0230 100644 --- a/src/dialogs.hpp +++ b/src/dialog.hpp @@ -6,12 +6,6 @@ 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, std::function &&on_cancel = {}, bool show_progrss_bar = false); @@ -28,4 +22,11 @@ private: static std::string gtk_dialog(const boost::filesystem::path &path, const std::string &title, const std::vector> &buttons, Gtk::FileChooserAction gtk_options); + +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); }; diff --git a/src/dialogs_unix.cpp b/src/dialogs_unix.cpp deleted file mode 100644 index fe2dc94..0000000 --- a/src/dialogs_unix.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "dialogs.hpp" - -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); -} - -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); -} - -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); -} - -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); -} - -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); -} diff --git a/src/dialogs_win.cpp b/src/dialogs_win.cpp deleted file mode 100644 index dc33d79..0000000 --- a/src/dialogs_win.cpp +++ /dev/null @@ -1,163 +0,0 @@ -#include "dialogs.hpp" -#include "juci.hpp" -#include "singletons.hpp" - -#undef NTDDI_VERSION -#define NTDDI_VERSION NTDDI_VISTA -#undef _WIN32_WINNT -#define _WIN32_WINNT _WIN32_WINNT_VISTA - -#include -#include -#include - -#include //TODO: remove -using namespace std; //TODO: remove - -class Win32Dialog { -public: - Win32Dialog(){}; - - ~Win32Dialog() { - if(dialog) - 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 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(); - } - -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::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; - } -}; - -std::string Dialog::open_folder() { - return Win32Dialog().open(L"Open Folder", FOS_PICKFOLDERS); -} - -std::string Dialog::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); -} - -std::string Dialog::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); -} diff --git a/src/meson.cpp b/src/meson.cpp index 67f2a71..22de0cb 100644 --- a/src/meson.cpp +++ b/src/meson.cpp @@ -1,7 +1,7 @@ #include "meson.hpp" #include "compile_commands.hpp" #include "config.hpp" -#include "dialogs.hpp" +#include "dialog.hpp" #include "filesystem.hpp" #include "terminal.hpp" #include "utility.hpp" diff --git a/src/source_clang.cpp b/src/source_clang.cpp index ee9739a..c0bb4f7 100644 --- a/src/source_clang.cpp +++ b/src/source_clang.cpp @@ -7,7 +7,7 @@ #endif #include "compile_commands.hpp" #include "ctags.hpp" -#include "dialogs.hpp" +#include "dialog.hpp" #include "documentation.hpp" #include "filesystem.hpp" #include "info.hpp" diff --git a/src/usages_clang.cpp b/src/usages_clang.cpp index b6af6a7..11150d3 100644 --- a/src/usages_clang.cpp +++ b/src/usages_clang.cpp @@ -1,7 +1,7 @@ #include "usages_clang.hpp" #include "compile_commands.hpp" #include "config.hpp" -#include "dialogs.hpp" +#include "dialog.hpp" #include "filesystem.hpp" #include "utility.hpp" #include diff --git a/src/window.cpp b/src/window.cpp index 7685aea..33c79af 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -4,7 +4,7 @@ #include "debug_lldb.hpp" #endif #include "compile_commands.hpp" -#include "dialogs.hpp" +#include "dialog.hpp" #include "directories.hpp" #include "entrybox.hpp" #include "filesystem.hpp" diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5428c91..9d1c817 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -9,7 +9,7 @@ include_directories(${CMAKE_SOURCE_DIR}/src) add_library(test_stubs OBJECT stubs/config.cpp - stubs/dialogs.cpp + stubs/dialog.cpp stubs/directories.cpp stubs/info.cpp stubs/notebook.cpp diff --git a/tests/stubs/dialogs.cpp b/tests/stubs/dialog.cpp similarity index 92% rename from tests/stubs/dialogs.cpp rename to tests/stubs/dialog.cpp index 50255e4..404ef76 100644 --- a/tests/stubs/dialogs.cpp +++ b/tests/stubs/dialog.cpp @@ -1,4 +1,4 @@ -#include "dialogs.hpp" +#include "dialog.hpp" Dialog::Message::Message(const std::string &text, std::function &&on_cancel, bool show_progress_bar) : Gtk::Window(Gtk::WindowType::WINDOW_POPUP) {} From 98354766e77badbff388b9b41c6b44637619bf58 Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 31 Mar 2021 12:48:33 +0200 Subject: [PATCH 082/375] Fixed ctags_grep_test --- tests/ctags_grep_test.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/ctags_grep_test.cpp b/tests/ctags_grep_test.cpp index 0cf3d23..f73bd15 100644 --- a/tests/ctags_grep_test.cpp +++ b/tests/ctags_grep_test.cpp @@ -23,6 +23,7 @@ int main() { Config::get().project.ctags_command = "ctags"; #endif Config::get().project.grep_command = "grep"; + Config::get().project.default_build_path = "build"; Config::get().project.debug_build_path = "build"; auto tests_path = boost::filesystem::canonical(JUCI_TESTS_PATH); From 69065d25c04b7825097f20b09baad13351f0c9dd Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 31 Mar 2021 14:25:45 +0200 Subject: [PATCH 083/375] Added prettier installation instructions message when prettier executable is not found --- src/source.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/source.cpp b/src/source.cpp index c413f82..655d774 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -727,9 +727,18 @@ void Source::View::setup_signals() { 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" || language->get_id() == "html" || - language->get_id() == "markdown" || language->get_id() == "yaml")) { + auto prefer_prettier = language && (language->get_id() == "js" || language->get_id() == "json" || language->get_id() == "css" || language->get_id() == "html" || + language->get_id() == "markdown" || language->get_id() == "yaml"); + if(prettier.empty() && prefer_prettier) { + static bool shown = false; + if(!shown) { + Terminal::get().print("\e[33mWarning\e[m: could not find Prettier code formatter.\n"); + Terminal::get().print("To install Prettier, run the following command in a terminal: npm i -g prettier\n"); + } + shown = true; + } + + if(!prettier.empty() && prefer_prettier) { if(is_generic_view) { goto_next_diagnostic = [this] { place_cursor_at_next_diagnostic(); From 3a8385a9c68825b2108cf0288d23f8f171d78e29 Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 31 Mar 2021 14:49:10 +0200 Subject: [PATCH 084/375] Added test for filesystem::get_current_path() --- src/filesystem.hpp | 2 +- tests/filesystem_test.cpp | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/filesystem.hpp b/src/filesystem.hpp index a09710c..d8501b0 100644 --- a/src/filesystem.hpp +++ b/src/filesystem.hpp @@ -16,7 +16,7 @@ public: static std::string escape_argument(const std::string &argument); static std::string unescape_argument(const std::string &argument); - /// Does not resolve symbolic links. Returns empty path on failure + /// Does not resolve symbolic links. Returns empty path on failure. static boost::filesystem::path get_current_path() noexcept; /// Returns empty path on failure static boost::filesystem::path get_home_path() noexcept; diff --git a/tests/filesystem_test.cpp b/tests/filesystem_test.cpp index 166c852..c83c60f 100644 --- a/tests/filesystem_test.cpp +++ b/tests/filesystem_test.cpp @@ -6,6 +6,12 @@ int main() { auto home_path = filesystem::get_home_path(); g_assert(!home_path.empty()); } + { + auto home_path = filesystem::get_current_path(); + g_assert(!home_path.empty()); + g_assert(boost::filesystem::exists(home_path)); + g_assert(boost::filesystem::is_directory(home_path)); + } { auto original = "test () '\""; From a684e9995ec7aa894b8e165faa66881751d3b1d7 Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 31 Mar 2021 15:27:32 +0200 Subject: [PATCH 085/375] Added .prettierrc and applied style format on markdown files --- .prettierrc | 5 ++ README.md | 131 +++++++++++++++++++++++---------------- docs/api.md | 14 +++-- docs/custom_styling.md | 2 +- docs/install.md | 52 ++++++++++++---- docs/language_servers.md | 55 +++++++++------- 6 files changed, 164 insertions(+), 95 deletions(-) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..2c34d5c --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "printWidth": 100, + "proseWrap": "always", + "singleQuote": true +} diff --git a/README.md b/README.md index 3e33f87..88e5fa4 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,71 @@ juCi++ ## About -In 2015, juCi++ was one of the first IDEs to utilize libclang for improved C/C++ tooling. -The integrated C/C++ support has since then improved steadily, and support for other -languages has been made possible through the language server protocol. The main goals of juCi++ is -effective resource usage, stability, and ease of use. Instead of relying on 3rd party addons, -features expected in an IDE is instead integrated directly into juCi++. -For effective development, juCi++ is primarily written for Unix/Linux systems. However, Windows users -can use juCi++ through POSIX compatibility layers such as MSYS2. +In 2015, juCi++ was one of the first IDEs to utilize libclang for improved C/C++ tooling. The +integrated C/C++ support has since then improved steadily, and support for other languages has been +made possible through the language server protocol. The main goals of juCi++ is effective resource +usage, stability, and ease of use. Instead of relying on 3rd party addons, features expected in an +IDE is instead integrated directly into juCi++. + +For effective development, juCi++ is primarily written for Unix/Linux systems. However, Windows +users can use juCi++ through POSIX compatibility layers such as MSYS2. ## Features -* Platform independent -* Fast, responsive and stable (written extensively using C++11/14 features) -* Syntax highlighting for more than 100 different file types -* Warnings and errors on the fly -* Fix-its, as well as C/C++ standard header include suggestions -* Integrated Clang-Tidy checks can be enabled in preferences -* Debug integration, both local and remote, through lldb -* Supports the following C/C++ build systems directly (other build systems need manually generated compilation databases): - * CMake - * Meson -* Fast autocompletion -* Tooltips showing type information and documentation -* Rename refactoring across files -* Highlighting of similar types -* Automated documentation search for C/C++ identifiers -* Go to declaration, implementation, methods and usages -* OpenCL and CUDA files are supported and parsed as C++ -* Non-C/C++ files are supported through the Language Server Protocol, which is enabled if an `[language identifier]-language-server` executable is found. This executable can be a symbolic link to one of your installed language server binaries. - * For additional instructions, see: [setup of tested language servers](docs/language_servers.md) -* Non-C/C++ projects are also supported, such as Python, JavaScript, and Rust projects -* Git support through libgit2 -* Find symbol through Ctags ([Universal Ctags](https://github.com/universal-ctags/ctags) is recommended) -* Spell checking depending on file context -* Run shell commands within juCi++ -* ANSI colors are supported. Enable for instance by setting the environment variables `CLICOLOR=1 CLICOLOR_FORCE=1` before starting juCi++. Colored diagnostics from clang is enabled through the flag `-fcolor-diagnostics`, and gcc uses the flag `-fdiagnostics-color`. -* Regex search and replace -* Smart paste, keys and indentation -* Extend/shrink selection -* Multiple cursors -* Snippets can be added in ~/.juci/snippets.json using the [TextMate snippet syntax](https://macromates.com/manual/en/snippets). The language ids used in the regexes can be found here: https://gitlab.gnome.org/GNOME/gtksourceview/tree/master/data/language-specs. -* Auto-indentation through [clang-format](http://clang.llvm.org/docs/ClangFormat.html) or [Prettier](https://github.com/prettier/prettier) if installed -* Source minimap -* Split view -* Zen mode -* Full UTF-8 support -* Wayland supported with GTK+ 3.20 or newer - -See [enhancements](https://gitlab.com/cppit/jucipp/issues?scope=all&state=opened&label_name[]=enhancement) for planned features. + +- Platform independent +- Fast, responsive and stable (written extensively using C++11/14 features) +- Syntax highlighting for more than 100 different file types +- Warnings and errors on the fly +- Fix-its, as well as C/C++ standard header include suggestions +- Integrated Clang-Tidy checks can be enabled in preferences +- Debug integration, both local and remote, through lldb +- Supports the following C/C++ build systems directly (other build systems need manually generated + compilation databases): + - CMake + - Meson +- Fast autocompletion +- Tooltips showing type information and documentation +- Rename refactoring across files +- Highlighting of similar types +- Automated documentation search for C/C++ identifiers +- Go to declaration, implementation, methods and usages +- OpenCL and CUDA files are supported and parsed as C++ +- Non-C/C++ files are supported through the Language Server Protocol, which is enabled if an + `[language identifier]-language-server` executable is found. This executable can be a symbolic + link to one of your installed language server binaries. + - For additional instructions, see: [setup of tested language servers](docs/language_servers.md) +- Non-C/C++ projects are also supported, such as Python, JavaScript, and Rust projects +- Git support through libgit2 +- Find symbol through Ctags ([Universal Ctags](https://github.com/universal-ctags/ctags) is + recommended) +- Spell checking depending on file context +- Run shell commands within juCi++ +- ANSI colors are supported. Enable for instance by setting the environment variables + `CLICOLOR=1 CLICOLOR_FORCE=1` before starting juCi++. Colored diagnostics from clang is enabled + through the flag `-fcolor-diagnostics`, and gcc uses the flag `-fdiagnostics-color`. +- Regex search and replace +- Smart paste, keys and indentation +- Extend/shrink selection +- Multiple cursors +- Snippets can be added in ~/.juci/snippets.json using the + [TextMate snippet syntax](https://macromates.com/manual/en/snippets). The language ids used in the + regexes can be found here: + https://gitlab.gnome.org/GNOME/gtksourceview/tree/master/data/language-specs. +- Auto-indentation through [clang-format](http://clang.llvm.org/docs/ClangFormat.html) or + [Prettier](https://github.com/prettier/prettier) if installed +- Source minimap +- Split view +- Zen mode +- Full UTF-8 support +- Wayland supported with GTK+ 3.20 or newer + +See +[enhancements](https://gitlab.com/cppit/jucipp/issues?scope=all&state=opened&label_name[]=enhancement) +for planned features. ## Screenshots + @@ -62,22 +77,28 @@ See [enhancements](https://gitlab.com/cppit/jucipp/issues?scope=all&state=opened
## Installation + See [installation guide](docs/install.md). ## Custom styling + See [custom styling](docs/custom_styling.md). ## Dependencies -* boost-filesystem -* boost-serialization -* gtkmm-3.0 -* gtksourceviewmm-3.0 -* aspell -* libclang -* lldb -* libgit2 -* [libclangmm](http://gitlab.com/cppit/libclangmm/) (downloaded directly with git --recursive, no need to install) -* [tiny-process-library](http://gitlab.com/eidheim/tiny-process-library/) (downloaded directly with git --recursive, no need to install) + +- boost-filesystem +- boost-serialization +- gtkmm-3.0 +- gtksourceviewmm-3.0 +- aspell +- libclang +- lldb +- libgit2 +- [libclangmm](http://gitlab.com/cppit/libclangmm/) (downloaded directly with git --recursive, no + need to install) +- [tiny-process-library](http://gitlab.com/eidheim/tiny-process-library/) (downloaded directly with + git --recursive, no need to install) ## Documentation + See [how to build the API doc](docs/api.md). diff --git a/docs/api.md b/docs/api.md index 329e970..5d3c23b 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1,13 +1,16 @@ # juCi++ API doc ## Prerequisites: - * doxygen - * plantuml - * install via apt-get or download from http://plantuml.com/ - * see also http://plantuml.com/starting.html - * if downloaded either copy the jar file to /usr/bin or set the environment variable PLANTUML_PATH to point to the path containing the jar file) + +- doxygen +- plantuml + - install via apt-get or download from http://plantuml.com/ + - see also http://plantuml.com/starting.html + - if downloaded either copy the jar file to /usr/bin or set the environment variable PLANTUML_PATH + to point to the path containing the jar file) ## How to build the API doc: + ```sh mkdir jucipp/build cd jucipp/build @@ -16,4 +19,5 @@ make doc ``` ## Where is the generated API documentation + Open jucipp/build/src/html/index.html diff --git a/docs/custom_styling.md b/docs/custom_styling.md index 2dbee89..bf61a8c 100644 --- a/docs/custom_styling.md +++ b/docs/custom_styling.md @@ -10,7 +10,7 @@ was made with the following ~/.config/gtk-3.0/gtk.css: ```css .juci_window { - background: url("/home/eidheim/Pictures/juci_background.png"); + background: url('/home/eidheim/Pictures/juci_background.png'); background-size: 100% 100%; } diff --git a/docs/install.md b/docs/install.md index 45a2239..1141164 100644 --- a/docs/install.md +++ b/docs/install.md @@ -1,22 +1,24 @@ # juCi++ Installation Guide - Installation - - Linux - - [Debian/Linux Mint/Ubuntu](#debianlinux-mintubuntu) - - [Arch Linux/Manjaro Linux](#arch-linuxmanjaro-linux) - - [Fedora](#fedora) - - [Mageia](#mageia) - - [OpenSUSE Tumbleweed](#opensuse-tumbleweed) - - [GNU Guix/GuixSD](#gnu-guixguixsd) - - [FreeBSD](#freebsd) - - MacOS - - [Homebrew](#macos-with-homebrew-httpbrewsh) - - Windows - - [MSYS2](#windows-with-msys2-httpsmsys2githubio) + - Linux + - [Debian/Linux Mint/Ubuntu](#debianlinux-mintubuntu) + - [Arch Linux/Manjaro Linux](#arch-linuxmanjaro-linux) + - [Fedora](#fedora) + - [Mageia](#mageia) + - [OpenSUSE Tumbleweed](#opensuse-tumbleweed) + - [GNU Guix/GuixSD](#gnu-guixguixsd) + - [FreeBSD](#freebsd) + - MacOS + - [Homebrew](#macos-with-homebrew-httpbrewsh) + - Windows + - [MSYS2](#windows-with-msys2-httpsmsys2githubio) - [Run](#run) ## Debian/Linux Mint/Ubuntu + Install dependencies: + ```sh sudo apt-get install libclang-dev liblldb-dev || sudo apt-get install libclang-6.0-dev liblldb-6.0-dev || sudo apt-get install libclang-4.0-dev liblldb-4.0-dev || sudo apt-get install libclang-3.8-dev liblldb-3.8-dev sudo apt-get install universal-ctags || sudo apt-get install exuberant-ctags @@ -24,6 +26,7 @@ sudo apt-get install git cmake make g++ clang-format pkg-config libboost-filesys ``` Get juCi++ source, compile and install: + ```sh git clone --recursive https://gitlab.com/cppit/jucipp mkdir jucipp/build @@ -34,12 +37,15 @@ sudo make install ``` ## Arch Linux/Manjaro Linux + Install dependencies: + ```sh sudo pacman -S git cmake pkg-config make clang lldb gtksourceviewmm boost aspell aspell-en libgit2 ctags ``` Get juCi++ source, compile and install: + ```sh git clone --recursive https://gitlab.com/cppit/jucipp mkdir jucipp/build @@ -50,12 +56,15 @@ sudo make install ``` ## Fedora + Install dependencies: + ```sh sudo dnf install git cmake make gcc-c++ clang-devel clang lldb-devel boost-devel gtksourceviewmm3-devel gtkmm30-devel aspell-devel aspell-en libgit2-devel ctags ``` Get juCi++ source, compile and install: + ```sh git clone --recursive https://gitlab.com/cppit/jucipp mkdir jucipp/build @@ -66,6 +75,7 @@ sudo make install ``` ## Mageia + **Mageia might not yet support LLDB, but you can compile without debug support.** Install dependencies: @@ -77,11 +87,13 @@ sudo urpmi git cmake make gcc-c++ clang libclang-devel libboost-devel libgtkmm3. ``` 64-bit: + ```sh sudo urpmi git cmake make gcc-c++ clang lib64clang-devel lib64boost-devel lib64gtkmm3.0-devel lib64gtksourceviewmm3.0-devel lib64aspell-devel aspell-en libgit2-devel ``` Get juCi++ source, compile and install: + ```sh git clone --recursive https://gitlab.com/cppit/jucipp mkdir jucipp/build @@ -92,12 +104,15 @@ sudo make install ``` ## OpenSUSE Tumbleweed + Install dependencies: + ```sh sudo zypper install git-core cmake gcc-c++ boost-devel libboost_filesystem-devel libboost_serialization-devel clang-devel lldb-devel lldb gtksourceviewmm3_0-devel aspell-devel aspell-en libgit2-devel ctags ``` Get juCi++ source, compile and install: + ```sh git clone --recursive https://gitlab.com/cppit/jucipp mkdir jucipp/build @@ -108,27 +123,34 @@ sudo make install ``` ## GNU Guix/GuixSD + Simply install juCi++ from the official package definition + ```sh guix install jucipp ``` ## FreeBSD + On FreeBSD, latest release of juCi++ is available through the port: jucipp. ## MacOS with Homebrew (http://brew.sh/) + Install dependencies: + ```sh brew install cmake pkg-config boost gtksourceviewmm3 gnome-icon-theme aspell llvm clang-format libgit2 zlib libxml2 brew install --HEAD universal-ctags/universal-ctags/universal-ctags # Recommended Ctags package ``` Mojave users might need to install headers: + ```sh open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg ``` Get juCi++ source, compile and install: + ```sh git clone --recursive https://gitlab.com/cppit/jucipp mkdir jucipp/build @@ -139,9 +161,11 @@ make install ``` ## Windows with MSYS2 (https://msys2.github.io/) + **See https://gitlab.com/cppit/jucipp/issues/190 for details on adding debug support in MSYS2** Install dependencies (replace `x86_64` with `i686` for 32-bit MSYS2 installs): + ```sh pacman -S git mingw-w64-x86_64-cmake make mingw-w64-x86_64-toolchain mingw-w64-x86_64-clang mingw-w64-x86_64-gtkmm3 mingw-w64-x86_64-gtksourceviewmm3 mingw-w64-x86_64-boost mingw-w64-x86_64-aspell mingw-w64-x86_64-aspell-en mingw-w64-x86_64-libgit2 mingw-w64-x86_64-universal-ctags-git ``` @@ -149,6 +173,7 @@ pacman -S git mingw-w64-x86_64-cmake make mingw-w64-x86_64-toolchain mingw-w64-x Note that juCi++ must be built and run in a MinGW Shell (for instance MinGW-w64 Win64 Shell). Get juCi++ source, compile and install (replace `mingw64` with `mingw32` for 32-bit MSYS2 installs): + ```sh git clone --recursive https://gitlab.com/cppit/jucipp mkdir jucipp/build @@ -159,10 +184,13 @@ make install ``` ## Run + ```sh juci ``` + Alternatively, you can also include directories and files: + ```sh juci [directory] [file1 file2 ...] ``` diff --git a/docs/language_servers.md b/docs/language_servers.md index 12cd3e8..2ce6596 100644 --- a/docs/language_servers.md +++ b/docs/language_servers.md @@ -3,12 +3,14 @@ ## JavaScript/TypeScript ### JavaScript with Flow static type checker -* Prerequisites: - * Node.js -* Recommended: - * [Prettier](https://github.com/prettier/prettier) + +- Prerequisites: + - Node.js +- Recommended: + - [Prettier](https://github.com/prettier/prettier) (installed globally: `install i -g prettier`) Install language server, and create executable to enable server in juCi++: + ```sh npm install -g flow-bin @@ -18,16 +20,18 @@ flow lsp' > /usr/local/bin/javascript-language-server chmod 755 /usr/local/bin/javascript-language-server ``` -* Additional setup within a JavaScript project: - * Add a `.prettierrc` file to enable style format on save +- Additional setup within a JavaScript project: + - Add a `.prettierrc` file to enable style format on save ### TypeScript or JavaScript without Flow -* Prerequisites: - * Node.js -* Recommended: - * [Prettier](https://github.com/prettier/prettier) + +- Prerequisites: + - Node.js +- Recommended: + - [Prettier](https://github.com/prettier/prettier) (installed globally: `install i -g prettier`) Install language server, and create executable to enable server in juCi++: + ```sh npm install -g typescript-language-server typescript @@ -40,14 +44,16 @@ cp /usr/local/bin/javascript-language-server /usr/local/bin/typescript-language- cp /usr/local/bin/javascript-language-server /usr/local/bin/typescriptreact-language-server ``` -* Additional setup within a JavaScript project: - * Add a `.prettierrc` file to enable style format on save +- Additional setup within a JavaScript project: + - Add a `.prettierrc` file to enable style format on save ## Python3 -* Prerequisites: - * Python3 + +- Prerequisites: + - Python3 Install language server, and create symbolic link to enable server in juCi++: + ```sh pip3 install python-language-server[rope,pycodestyle,yapf] @@ -55,15 +61,18 @@ pip3 install python-language-server[rope,pycodestyle,yapf] ln -s `which pyls` /usr/local/bin/python-language-server ``` -* Additional setup within a Python project: - * Add a setup file, for instance: `printf '[pycodestyle]\nmax-line-length = 120\n\n[yapf]\nCOLUMN_LIMIT = 120\n' > setup.cfg` - * Add an empty `.python-format` file to enable style format on save +- Additional setup within a Python project: + - Add a setup file, for instance: + `printf '[pycodestyle]\nmax-line-length = 120\n\n[yapf]\nCOLUMN_LIMIT = 120\n' > setup.cfg` + - Add an empty `.python-format` file to enable style format on save ## Rust -* Prerequisites: - * Rust - + +- Prerequisites: + - Rust + Install language server, and create symbolic link to enable server in juCi++: + ```sh rustup component add rust-src @@ -75,11 +84,13 @@ cargo xtask install --server ln -s ~/.cargo/bin/rust-analyzer /usr/local/bin/rust-language-server ``` -* Additional setup within a Rust project: - * Add an empty `.rust-format` file to enable style format on save +- Additional setup within a Rust project: + - Add an empty `.rust-format` file to enable style format on save ## GLSL + Install language server, and create a script to enable server in juCi++: + ```sh git clone https://github.com/svenstaro/glsl-language-server --recursive cd glsl-language-server From 38b92a54aff40f2931812ef9c3b060cf2ef3cea4 Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 31 Mar 2021 17:54:41 +0200 Subject: [PATCH 086/375] Added Window menu items Toggle Directories, Toggle Terminal and Toggle Menu --- CMakeLists.txt | 2 +- src/files.hpp | 3 +++ src/menu.cpp | 14 +++++++++++++- src/window.cpp | 9 +++++++++ 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 87163ff..926f55f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.1) project(juci) -set(JUCI_VERSION "1.6.2.1") +set(JUCI_VERSION "1.6.2.2") set(CPACK_PACKAGE_NAME "jucipp") set(CPACK_PACKAGE_CONTACT "Ole Christian Eidheim ") diff --git a/src/files.hpp b/src/files.hpp index 74a42b9..ffd6aaf 100644 --- a/src/files.hpp +++ b/src/files.hpp @@ -217,6 +217,9 @@ const std::string default_config_file = "window_toggle_full_screen": "F11",)RAW" #endif R"RAW( + "window_toggle_directories": "", + "window_toggle_terminal": "", + "window_toggle_menu": "", "window_toggle_tabs": "", "window_toggle_zen_mode": "", "window_clear_terminal": "" diff --git a/src/menu.cpp b/src/menu.cpp index 5760594..575466d 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -518,7 +518,19 @@ const Glib::ustring menu_xml = R"RAW( app.window_toggle_full_screen - _Toggle _Tabs _Visibility + _Toggle _Directories + app.window_toggle_directories + + + _Toggle _Terminal + app.window_toggle_terminal + + + _Toggle _Menu + app.window_toggle_menu + + + _Toggle _Tabs app.window_toggle_tabs diff --git a/src/window.cpp b/src/window.cpp index 33c79af..a0869c6 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -1676,6 +1676,15 @@ void Window::set_menu_actions() { else fullscreen(); }); + menu.add_action("window_toggle_directories", [this] { + directories_scrolled_window.set_visible(!directories_scrolled_window.get_visible()); + }); + menu.add_action("window_toggle_terminal", [this] { + terminal_scrolled_window.set_visible(!terminal_scrolled_window.get_visible()); + }); + menu.add_action("window_toggle_menu", [this] { + set_show_menubar(!get_show_menubar()); + }); menu.add_action("window_toggle_tabs", [] { for(auto ¬ebook : Notebook::get().notebooks) notebook.set_show_tabs(!notebook.get_show_tabs()); From 0a76fab5b237684a5a25448cbd5fbe7fc1f44259 Mon Sep 17 00:00:00 2001 From: eidheim Date: Thu, 1 Apr 2021 10:30:04 +0200 Subject: [PATCH 087/375] Added platform specific instructions for installing prettier --- src/source.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/source.cpp b/src/source.cpp index 655d774..0d9bdb9 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -733,7 +733,18 @@ void Source::View::setup_format_style(bool is_generic_view) { static bool shown = false; if(!shown) { Terminal::get().print("\e[33mWarning\e[m: could not find Prettier code formatter.\n"); - Terminal::get().print("To install Prettier, run the following command in a terminal: npm i -g prettier\n"); + Terminal::get().print("To install Prettier, run the following command in a terminal: "); +#if defined(__APPLE__) + Terminal::get().print("brew install prettier"); +#elif defined(__linux) + if(!filesystem::find_executable("pacman").empty()) + Terminal::get().print("sudo pacman -S prettier"); + else + Terminal::get().print("npm i -g prettier"); +#else + Terminal::get().print("npm i -g prettier"); +#endif + Terminal::get().print("\n"); } shown = true; } From dc9177e97e62e0b8a04145895b0332ffa2131aac Mon Sep 17 00:00:00 2001 From: eidheim Date: Thu, 1 Apr 2021 16:52:36 +0200 Subject: [PATCH 088/375] Fixed a thread sanitizer warning in LanguageProtocol::Client destructor --- src/source_language_protocol.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 9337a5f..7609037 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -115,10 +115,12 @@ LanguageProtocol::Client::~Client() { if(process->try_get_exit_status(exit_status)) break; } + if(exit_status == -1) { + process->kill(); + exit_status = process->get_exit_status(); + } if(Config::get().log.language_server) std::cout << "Language server exit status: " << exit_status << std::endl; - if(exit_status == -1) - process->kill(); } boost::optional LanguageProtocol::Client::get_capabilities(Source::LanguageProtocolView *view) { From 18aca81cf61cabf605453cb77589315d6e9ea1ab Mon Sep 17 00:00:00 2001 From: eidheim Date: Thu, 1 Apr 2021 20:47:51 +0200 Subject: [PATCH 089/375] Added undefined-sanitizer job to CI --- .gitlab-ci.yml | 9 +++++++++ tests/stubs/project.cpp | 16 ++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 864c033..2ea6a46 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -55,6 +55,15 @@ address-sanitizer: - make -j$(nproc) - broadwayd & CTEST_OUTPUT_ON_FAILURE=1 LSAN_OPTIONS=detect_leaks=0 make test +undefined-sanitizer: + image: cppit/jucipp:arch + stage: test + script: + - mkdir build && cd build + - CXXFLAGS="-fsanitize=undefined" cmake -DBUILD_TESTING=1 .. + - make -j$(nproc) + - broadwayd & CTEST_OUTPUT_ON_FAILURE=1 make test + check-format: image: cppit/jucipp:arch stage: lint diff --git a/tests/stubs/project.cpp b/tests/stubs/project.cpp index 243a31f..42079da 100644 --- a/tests/stubs/project.cpp +++ b/tests/stubs/project.cpp @@ -3,3 +3,19 @@ std::shared_ptr Project::current; std::shared_ptr Project::create() { return nullptr; } + +std::pair Project::Base::get_run_arguments() { + return std::make_pair("", ""); +} + +void Project::Base::compile() {} + +void Project::Base::compile_and_run() {} + +void Project::Base::recreate_build() {} + +std::pair Project::Base::debug_get_run_arguments() { + return std::make_pair("", ""); +} + +void Project::Base::debug_start() {} From 7808ec67462421a4022293c743ef5120a99c35ea Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 2 Apr 2021 09:10:56 +0200 Subject: [PATCH 090/375] Updated libclangmm submodule --- lib/libclangmm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/libclangmm b/lib/libclangmm index a80c808..11fc6bb 160000 --- a/lib/libclangmm +++ b/lib/libclangmm @@ -1 +1 @@ -Subproject commit a80c8081f599847ce0ea507d25a59565fda99ea0 +Subproject commit 11fc6bb436fea00abc23fa2464781880642de5e5 From e59cb70467a085cb00fba75a748fe8b7b5431624 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 2 Apr 2021 09:30:44 +0200 Subject: [PATCH 091/375] v1.6.3 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 926f55f..3fe3c00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.1) project(juci) -set(JUCI_VERSION "1.6.2.2") +set(JUCI_VERSION "1.6.3") set(CPACK_PACKAGE_NAME "jucipp") set(CPACK_PACKAGE_CONTACT "Ole Christian Eidheim ") From 9a463dc9d750752b817307ff6153f33f4e7d3c07 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 2 Apr 2021 14:16:50 +0200 Subject: [PATCH 092/375] Added cancel dialog if grep or ctags processes exceed 10 seconds --- src/ctags.cpp | 40 ++++++++++++++++++++++++++++++++++++++-- src/grep.cpp | 40 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/src/ctags.cpp b/src/ctags.cpp index e144249..7150adc 100644 --- a/src/ctags.cpp +++ b/src/ctags.cpp @@ -1,5 +1,6 @@ #include "ctags.hpp" #include "config.hpp" +#include "dialog.hpp" #include "filesystem.hpp" #include "project_build.hpp" #include "terminal.hpp" @@ -35,8 +36,43 @@ Ctags::Ctags(const boost::filesystem::path &path, bool enable_scope, bool enable command = Config::get().project.ctags_command + options + fields + " --c-kinds=+p --c++-kinds=+p " + filesystem::escape_argument(path.string()); } - std::stringstream stdin_stream; - Terminal::get().process(stdin_stream, output, command, project_path); + TinyProcessLib::Process process( + command, project_path.string(), + [this](const char *output, size_t length) { + this->output.write(output, length); + }, + [](const char *bytes, size_t n) { + Terminal::get().async_print(std::string(bytes, n), true); + }); + + int exit_status; + size_t count = 0; + while(!process.try_get_exit_status(exit_status)) { + ++count; + if(count > 1000) + break; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + + if(!process.try_get_exit_status(exit_status)) { + bool canceled = false; + Dialog::Message message("Please wait until ctags command completes", [&canceled] { + canceled = true; + }); + bool killed = false; + while(!process.try_get_exit_status(exit_status)) { + if(canceled && !killed) { + process.kill(); + killed = true; + } + while(Gtk::Main::events_pending()) + Gtk::Main::iteration(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + message.hide(); + if(killed) + output = std::stringstream(); + } } Ctags::operator bool() { diff --git a/src/grep.cpp b/src/grep.cpp index f6216f3..877b7b8 100644 --- a/src/grep.cpp +++ b/src/grep.cpp @@ -1,5 +1,6 @@ #include "grep.hpp" #include "config.hpp" +#include "dialog.hpp" #include "filesystem.hpp" #include "project_build.hpp" #include "terminal.hpp" @@ -37,8 +38,43 @@ Grep::Grep(const boost::filesystem::path &path, const std::string &pattern, bool std::string command = Config::get().project.grep_command + " -RHn --color=always --binary-files=without-match" + flags + exclude + escaped_pattern + " *"; - std::stringstream stdin_stream; - Terminal::get().process(stdin_stream, output, command, project_path); + TinyProcessLib::Process process( + command, project_path.string(), + [this](const char *output, size_t length) { + this->output.write(output, length); + }, + [](const char *bytes, size_t n) { + Terminal::get().async_print(std::string(bytes, n), true); + }); + + int exit_status; + size_t count = 0; + while(!process.try_get_exit_status(exit_status)) { + ++count; + if(count > 1000) + break; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + + if(!process.try_get_exit_status(exit_status)) { + bool canceled = false; + Dialog::Message message("Please wait until grep command completes", [&canceled] { + canceled = true; + }); + bool killed = false; + while(!process.try_get_exit_status(exit_status)) { + if(canceled && !killed) { + process.kill(); + killed = true; + } + while(Gtk::Main::events_pending()) + Gtk::Main::iteration(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + message.hide(); + if(killed) + output = std::stringstream(); + } } Grep::operator bool() { From 955e1558f293955d96af4b13d34a46fe498ce469 Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 7 Apr 2021 08:27:52 +0200 Subject: [PATCH 093/375] Let theme decide if tree lines should be enabled in directories view --- src/directories.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/directories.cpp b/src/directories.cpp index 3f2fec8..e2ef20c 100644 --- a/src/directories.cpp +++ b/src/directories.cpp @@ -103,8 +103,6 @@ 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); From ddaca98476984f0e3467036a57476eb81e935281 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 9 Apr 2021 09:53:44 +0200 Subject: [PATCH 094/375] Made filesystem methods thread safe --- src/filesystem.cpp | 100 +++++++++++++++++++------------------- tests/filesystem_test.cpp | 24 ++++++--- 2 files changed, 69 insertions(+), 55 deletions(-) diff --git a/src/filesystem.cpp b/src/filesystem.cpp index fdee257..ee10937 100644 --- a/src/filesystem.cpp +++ b/src/filesystem.cpp @@ -83,48 +83,47 @@ std::string filesystem::unescape_argument(const std::string &argument) { } boost::filesystem::path filesystem::get_current_path() noexcept { - static boost::filesystem::path current_path; - if(!current_path.empty()) - return current_path; + auto current_path = [] { #ifdef _WIN32 - boost::system::error_code ec; - auto path = boost::filesystem::current_path(ec); - if(!ec) { - current_path = std::move(path); - return current_path; - } - return boost::filesystem::path(); + boost::system::error_code ec; + auto path = boost::filesystem::current_path(ec); + if(!ec) + return path; + return boost::filesystem::path(); #else - std::string path; - TinyProcessLib::Process process("pwd", "", [&path](const char *buffer, size_t length) { - path += std::string(buffer, length); - }); - if(process.get_exit_status() == 0) { - if(!path.empty() && path.back() == '\n') - path.pop_back(); - current_path = boost::filesystem::path(path); - return current_path; - } - return boost::filesystem::path(); + std::string path; + TinyProcessLib::Process process("pwd", "", [&path](const char *buffer, size_t length) { + path += std::string(buffer, length); + }); + if(process.get_exit_status() == 0) { + if(!path.empty() && path.back() == '\n') + path.pop_back(); + return boost::filesystem::path(path); + } + return boost::filesystem::path(); #endif + }; + + static boost::filesystem::path path = current_path(); + return path; } boost::filesystem::path filesystem::get_home_path() noexcept { - static boost::filesystem::path home_path; - if(!home_path.empty()) - return home_path; - std::vector environment_variables = {"HOME", "AppData"}; - for(auto &variable : environment_variables) { - if(auto ptr = std::getenv(variable.c_str())) { - boost::system::error_code ec; - boost::filesystem::path path(ptr); - if(boost::filesystem::exists(path, ec)) { - home_path = std::move(path); - return home_path; + auto home_path = [] { + std::vector environment_variables = {"HOME", "AppData"}; + for(auto &variable : environment_variables) { + if(auto ptr = std::getenv(variable.c_str())) { + boost::system::error_code ec; + boost::filesystem::path path(ptr); + if(boost::filesystem::exists(path, ec)) + return path; } } - } - return boost::filesystem::path(); + return boost::filesystem::path(); + }; + + static boost::filesystem::path path = home_path(); + return path; } boost::filesystem::path filesystem::get_short_path(const boost::filesystem::path &path) noexcept { @@ -260,22 +259,25 @@ boost::filesystem::path filesystem::get_executable(const boost::filesystem::path // Based on https://stackoverflow.com/a/11295568 const std::vector &filesystem::get_executable_search_paths() { - static std::vector 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)); + auto executable_search_paths = [] { + std::vector paths; + + 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) { + paths.emplace_back(env.substr(previous, pos - previous)); + previous = pos + 1; + } + paths.emplace_back(env.substr(previous)); + + return paths; + }; - return result; + static std::vector paths = executable_search_paths(); + return paths; } boost::filesystem::path filesystem::find_executable(const std::string &executable_name) { diff --git a/tests/filesystem_test.cpp b/tests/filesystem_test.cpp index c83c60f..e22c7c7 100644 --- a/tests/filesystem_test.cpp +++ b/tests/filesystem_test.cpp @@ -3,14 +3,26 @@ int main() { { - auto home_path = filesystem::get_home_path(); - g_assert(!home_path.empty()); + auto path = filesystem::get_home_path(); + g_assert(!path.empty()); + g_assert(boost::filesystem::exists(path)); + g_assert(boost::filesystem::is_directory(path)); } { - auto home_path = filesystem::get_current_path(); - g_assert(!home_path.empty()); - g_assert(boost::filesystem::exists(home_path)); - g_assert(boost::filesystem::is_directory(home_path)); + auto path = filesystem::get_current_path(); + g_assert(!path.empty()); + g_assert(boost::filesystem::exists(path)); + g_assert(boost::filesystem::is_directory(path)); + } + + { + auto paths = filesystem::get_executable_search_paths(); + g_assert(!paths.empty()); + for(auto &path : paths) { + g_assert(!path.empty()); + g_assert(boost::filesystem::exists(path)); + g_assert(boost::filesystem::is_directory(path)); + } } { From 59aa74afb32bdbc62360b8cf297215cefedb969a Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 9 Apr 2021 13:26:32 +0200 Subject: [PATCH 095/375] Fixed test on MSYS2 --- tests/filesystem_test.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/filesystem_test.cpp b/tests/filesystem_test.cpp index e22c7c7..16e4059 100644 --- a/tests/filesystem_test.cpp +++ b/tests/filesystem_test.cpp @@ -20,8 +20,10 @@ int main() { g_assert(!paths.empty()); for(auto &path : paths) { g_assert(!path.empty()); +#ifndef _WIN32 g_assert(boost::filesystem::exists(path)); g_assert(boost::filesystem::is_directory(path)); +#endif } } From 3b07c7b66ba3199de39b38a7293feb340cc9c3d1 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 23 Apr 2021 09:32:24 +0200 Subject: [PATCH 096/375] Updated libclangmm --- lib/libclangmm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/libclangmm b/lib/libclangmm index 11fc6bb..663e548 160000 --- a/lib/libclangmm +++ b/lib/libclangmm @@ -1 +1 @@ -Subproject commit 11fc6bb436fea00abc23fa2464781880642de5e5 +Subproject commit 663e54847f091b9e143db48361538a1e9ea540e9 From b3b4cf71d980f027000e928f6462d149d5869b9d Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 5 May 2021 15:20:59 +0200 Subject: [PATCH 097/375] Fixed potential crash on directory update when trying to colorize a deleted folder --- src/directories.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/directories.cpp b/src/directories.cpp index e2ef20c..132784f 100644 --- a/src/directories.cpp +++ b/src/directories.cpp @@ -695,7 +695,7 @@ void Directories::add_or_update_path(const boost::filesystem::path &dir_path, co repository->clear_saved_status(); connection->disconnect(); *connection = Glib::signal_timeout().connect( - [path_and_row, this]() { + [this, path_and_row]() { 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; @@ -748,8 +748,12 @@ void Directories::add_or_update_path(const boost::filesystem::path &dir_path, co already_added.emplace(filename); ++it; } - else + else { + auto path_it = directories.find(it->get_value(column_record.path).string()); + if(path_it != directories.end()) + directories.erase(path_it); it = tree_store->erase(it); + } } for(auto &filename : filenames) { @@ -817,7 +821,13 @@ void Directories::colorize_path(boost::filesystem::path dir_path_, bool include_ if(it == directories.end()) return; - if(it != directories.end() && it->second.repository) { + boost::system::error_code ec; + if(!boost::filesystem::exists(*dir_path, ec)) { + directories.erase(it); + return; + } + + if(it->second.repository) { auto repository = it->second.repository; thread_pool.push([this, dir_path, repository, include_parent_paths] { Git::Repository::Status status; From 485c68f6ea792969a40d68065a40d3f584ec3efb Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 12 May 2021 09:50:50 +0200 Subject: [PATCH 098/375] Fixed Open With Default Application menu item in directory view for file paths with special characters --- src/directories.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/directories.cpp b/src/directories.cpp index 132784f..3f3a915 100644 --- a/src/directories.cpp +++ b/src/directories.cpp @@ -474,9 +474,9 @@ Directories::Directories() : Gtk::ListViewText(1) { if(menu_popup_row_path.empty()) return; #ifdef __APPLE__ - Terminal::get().async_process("open " + menu_popup_row_path.string(), "", nullptr, true); + Terminal::get().async_process("open " + filesystem::escape_argument(menu_popup_row_path.string()), "", nullptr, true); #else - Terminal::get().async_process("xdg-open " + menu_popup_row_path.string(), "", nullptr, true); + Terminal::get().async_process("xdg-open " + filesystem::escape_argument(menu_popup_row_path.string()), "", nullptr, true); #endif }; From 218f98f9c29f9dde8b8df1fa693d17d5446d81bc Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 21 May 2021 11:16:38 +0200 Subject: [PATCH 099/375] Now makes sure build directory is created before building and debugging rust programs --- src/project_build.cpp | 22 ++++++++++++++++++++++ src/project_build.hpp | 4 ++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/project_build.cpp b/src/project_build.cpp index 933a0d7..d767121 100644 --- a/src/project_build.cpp +++ b/src/project_build.cpp @@ -1,6 +1,7 @@ #include "project_build.hpp" #include "config.hpp" #include "filesystem.hpp" +#include "terminal.hpp" #include #include #include @@ -202,6 +203,27 @@ bool Project::MesonBuild::is_valid() { return true; } +bool Project::CargoBuild::update_default(bool force) { + auto default_build_path = get_default_path(); + if(default_build_path.empty()) + return false; + + boost::system::error_code ec; + if(!boost::filesystem::exists(default_build_path, ec)) { + boost::system::error_code ec; + boost::filesystem::create_directories(default_build_path, ec); + if(ec) { + Terminal::get().print("\e[31mError\e[m: could not create " + filesystem::get_short_path(default_build_path).string() + ": " + ec.message() + "\n", true); + return false; + } + } + return true; +} + +bool Project::CargoBuild::update_debug(bool force) { + return update_default(force); +} + std::string Project::CargoBuild::get_compile_command() { return Config::get().project.cargo_command + " build"; } diff --git a/src/project_build.hpp b/src/project_build.hpp index f1677b6..0ddacf1 100644 --- a/src/project_build.hpp +++ b/src/project_build.hpp @@ -63,9 +63,9 @@ namespace Project { 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; 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; std::string get_compile_command() override; boost::filesystem::path get_executable(const boost::filesystem::path &path) override { return get_debug_path() / project_path.filename(); } From 17a3fb4e2f39f87d7b81fefc22025d1d54975e7d Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 21 May 2021 11:55:36 +0200 Subject: [PATCH 100/375] Fixed rust debug value formatter --- src/project.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/project.cpp b/src/project.cpp index 799b89f..db3c335 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -474,9 +474,13 @@ void Project::LLDB::debug_start() { auto sysroot = ostream.str(); while(!sysroot.empty() && (sysroot.back() == '\n' || sysroot.back() == '\r')) sysroot.pop_back(); - startup_commands.emplace_back("command script import \"" + sysroot + "/lib/rustlib/etc/lldb_rust_formatters.py\""); - startup_commands.emplace_back("type summary add --no-value --python-function lldb_rust_formatters.print_val -x \".*\" --category Rust"); - startup_commands.emplace_back("type category enable Rust"); + std::string line; + std::ifstream input(sysroot + "/lib/rustlib/etc/lldb_commands", std::ofstream::binary); + if(input) { + startup_commands.emplace_back("command script import \"" + sysroot + "/lib/rustlib/etc/lldb_lookup.py\""); + while(std::getline(input, line)) + startup_commands.emplace_back(line); + } } } Debug::LLDB::get().start(*run_arguments, *project_path, breakpoints, startup_commands, remote_host); From 4cd3ee6de290f9df0276d2e405251efd5dcbfda1 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 21 May 2021 15:12:18 +0200 Subject: [PATCH 101/375] Added extra lookup for rust-analyzer --- src/filesystem.cpp | 18 ++++++++++++++++++ src/filesystem.hpp | 2 ++ src/notebook.cpp | 10 +++++++++- src/project.cpp | 7 ++----- src/source_language_protocol.cpp | 14 +++++++------- src/source_language_protocol.hpp | 7 ++++--- 6 files changed, 42 insertions(+), 16 deletions(-) diff --git a/src/filesystem.cpp b/src/filesystem.cpp index ee10937..7a6792f 100644 --- a/src/filesystem.cpp +++ b/src/filesystem.cpp @@ -126,6 +126,24 @@ boost::filesystem::path filesystem::get_home_path() noexcept { return path; } +boost::filesystem::path filesystem::get_rust_sysroot_path() noexcept { + auto rust_sysroot_path = [] { + std::string path; + TinyProcessLib::Process process("rustc --print sysroot", "", [&path](const char *buffer, size_t length) { + path += std::string(buffer, length); + }); + if(process.get_exit_status() == 0) { + while(!path.empty() && (path.back() == '\n' || path.back() == '\r')) + path.pop_back(); + return boost::filesystem::path(path); + } + return boost::filesystem::path(); + }; + + static boost::filesystem::path path = rust_sysroot_path(); + return path; +} + boost::filesystem::path filesystem::get_short_path(const boost::filesystem::path &path) noexcept { #ifdef _WIN32 return path; diff --git a/src/filesystem.hpp b/src/filesystem.hpp index d8501b0..173faa3 100644 --- a/src/filesystem.hpp +++ b/src/filesystem.hpp @@ -20,6 +20,8 @@ public: static boost::filesystem::path get_current_path() noexcept; /// Returns empty path on failure static boost::filesystem::path get_home_path() noexcept; + /// Returns empty path on failure + static boost::filesystem::path get_rust_sysroot_path() noexcept; /// Replaces home path with ~ static boost::filesystem::path get_short_path(const boost::filesystem::path &path) noexcept; /// Replaces ~ with home path (boost::filesystem does not recognize ~) diff --git a/src/notebook.cpp b/src/notebook.cpp index 76a4dfa..c3b4678 100644 --- a/src/notebook.cpp +++ b/src/notebook.cpp @@ -173,7 +173,15 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position 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)); + source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, language_protocol_language_id + "-language-server")); + else if(language && language_protocol_language_id == "rust" && !filesystem::get_rust_sysroot_path().empty()) { + auto rust_analyzer = filesystem::get_rust_sysroot_path().string() + "/bin/rust-analyzer"; + boost::system::error_code ec; + if(boost::filesystem::exists(rust_analyzer, ec)) + source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, rust_analyzer)); + else + source_views.emplace_back(new Source::GenericView(file_path, language)); + } else { if(language) { static std::set shown; diff --git a/src/project.cpp b/src/project.cpp index db3c335..0a03cdf 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -469,11 +469,8 @@ void Project::LLDB::debug_start() { std::vector startup_commands; if(dynamic_cast(self->build.get())) { - std::stringstream istream, ostream; - if(Terminal::get().process(istream, ostream, "rustc --print sysroot") == 0) { - auto sysroot = ostream.str(); - while(!sysroot.empty() && (sysroot.back() == '\n' || sysroot.back() == '\r')) - sysroot.pop_back(); + auto sysroot = filesystem::get_rust_sysroot_path().string(); + if(!sysroot.empty()) { std::string line; std::ifstream input(sysroot + "/lib/rustlib/etc/lldb_commands", std::ofstream::binary); if(input) { diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 7609037..f76131e 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -55,9 +55,9 @@ LanguageProtocol::TextDocumentEdit::TextDocumentEdit(const boost::property_tree: edits.emplace_back(it->second); } -LanguageProtocol::Client::Client(boost::filesystem::path root_path_, std::string language_id_) : root_path(std::move(root_path_)), language_id(std::move(language_id_)) { +LanguageProtocol::Client::Client(boost::filesystem::path root_path_, std::string language_id_, const std::string &language_server) : root_path(std::move(root_path_)), language_id(std::move(language_id_)) { process = std::make_unique( - filesystem::escape_argument(language_id + "-language-server"), root_path.string(), + filesystem::escape_argument(language_server), root_path.string(), [this](const char *bytes, size_t n) { server_message_stream.write(bytes, n); parse_server_message(); @@ -68,7 +68,7 @@ LanguageProtocol::Client::Client(boost::filesystem::path root_path_, std::string true, TinyProcessLib::Config{1048576}); } -std::shared_ptr LanguageProtocol::Client::get(const boost::filesystem::path &file_path, const std::string &language_id) { +std::shared_ptr LanguageProtocol::Client::get(const boost::filesystem::path &file_path, const std::string &language_id, const std::string &language_server) { boost::filesystem::path root_path; auto build = Project::Build::create(file_path); if(!build->project_path.empty()) @@ -87,7 +87,7 @@ std::shared_ptr LanguageProtocol::Client::get(const bo it = cache.emplace(cache_id, std::weak_ptr()).first; auto instance = it->second.lock(); if(!instance) - it->second = instance = std::shared_ptr(new Client(root_path, language_id), [](Client *client_ptr) { + it->second = instance = std::shared_ptr(new Client(root_path, language_id, language_server), [](Client *client_ptr) { std::thread delete_thread([client_ptr] { // Delete client in the background delete client_ptr; }); @@ -451,8 +451,8 @@ void LanguageProtocol::Client::handle_server_request(size_t id, const std::strin // write_response(*id, ""); } -Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path &file_path, const Glib::RefPtr &language, std::string language_id_) - : Source::BaseView(file_path, language), Source::View(file_path, language), uri(filesystem::get_uri_from_path(file_path)), language_id(std::move(language_id_)), client(LanguageProtocol::Client::get(file_path, language_id)) { +Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path &file_path, const Glib::RefPtr &language, std::string language_id_, std::string language_server_) + : Source::BaseView(file_path, language), Source::View(file_path, language), uri(filesystem::get_uri_from_path(file_path)), language_id(std::move(language_id_)), language_server(std::move(language_server_)), client(LanguageProtocol::Client::get(file_path, language_id, language_server)) { initialize(); } @@ -534,7 +534,7 @@ void Source::LanguageProtocolView::rename(const boost::filesystem::path &path) { dispatcher.reset(); Source::DiffView::rename(path); uri = filesystem::get_uri_from_path(path); - client = LanguageProtocol::Client::get(file_path, language_id); + client = LanguageProtocol::Client::get(file_path, language_id, language_server); initialize(); } diff --git a/src/source_language_protocol.hpp b/src/source_language_protocol.hpp index 05a6713..b4638c9 100644 --- a/src/source_language_protocol.hpp +++ b/src/source_language_protocol.hpp @@ -113,7 +113,7 @@ namespace LanguageProtocol { }; class Client { - Client(boost::filesystem::path root_path, std::string language_id); + Client(boost::filesystem::path root_path, std::string language_id, const std::string &language_server); boost::filesystem::path root_path; std::string language_id; @@ -141,7 +141,7 @@ namespace LanguageProtocol { std::vector timeout_threads GUARDED_BY(timeout_threads_mutex); public: - static std::shared_ptr get(const boost::filesystem::path &file_path, const std::string &language_id); + static std::shared_ptr get(const boost::filesystem::path &file_path, const std::string &language_id, const std::string &language_server); ~Client(); @@ -161,7 +161,7 @@ namespace LanguageProtocol { namespace Source { class LanguageProtocolView : public View { public: - LanguageProtocolView(const boost::filesystem::path &file_path, const Glib::RefPtr &language, std::string language_id_); + LanguageProtocolView(const boost::filesystem::path &file_path, const Glib::RefPtr &language, std::string language_id_, std::string language_server_); void initialize(); void close(); ~LanguageProtocolView() override; @@ -190,6 +190,7 @@ namespace Source { bool initialized = false; std::string language_id; + std::string language_server; LanguageProtocol::Capabilities capabilities; std::shared_ptr client; From 9579c16193d86a5beed6d5ffc84f2086e3bbcfc1 Mon Sep 17 00:00:00 2001 From: eidheim Date: Mon, 24 May 2021 09:52:16 +0200 Subject: [PATCH 102/375] Added menu item File, New Project, Rust --- docs/language_servers.md | 2 +- src/menu.cpp | 4 ++++ src/notebook.cpp | 4 ++-- src/project.cpp | 5 ++--- src/project_build.cpp | 9 +++++++++ src/project_build.hpp | 2 +- src/source_base.cpp | 7 ++++--- src/source_language_protocol.cpp | 14 ++++++++++++++ src/window.cpp | 27 +++++++++++++++++++++++++++ 9 files changed, 64 insertions(+), 10 deletions(-) diff --git a/docs/language_servers.md b/docs/language_servers.md index 2ce6596..a3294f4 100644 --- a/docs/language_servers.md +++ b/docs/language_servers.md @@ -85,7 +85,7 @@ ln -s ~/.cargo/bin/rust-analyzer /usr/local/bin/rust-language-server ``` - Additional setup within a Rust project: - - Add an empty `.rust-format` file to enable style format on save + - Add an empty `.rustfmt.toml` file to enable style format on save ## GLSL diff --git a/src/menu.cpp b/src/menu.cpp index 575466d..19aea24 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -135,6 +135,10 @@ const Glib::ustring menu_xml = R"RAW( C++ app.file_new_project_cpp + + Rust + app.file_new_project_rust +
diff --git a/src/notebook.cpp b/src/notebook.cpp index c3b4678..19b432e 100644 --- a/src/notebook.cpp +++ b/src/notebook.cpp @@ -175,10 +175,10 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position 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, language_protocol_language_id + "-language-server")); else if(language && language_protocol_language_id == "rust" && !filesystem::get_rust_sysroot_path().empty()) { - auto rust_analyzer = filesystem::get_rust_sysroot_path().string() + "/bin/rust-analyzer"; + auto rust_analyzer = filesystem::get_rust_sysroot_path() / "bin" / "rust-analyzer"; boost::system::error_code ec; if(boost::filesystem::exists(rust_analyzer, ec)) - source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, rust_analyzer)); + source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, rust_analyzer.string())); else source_views.emplace_back(new Source::GenericView(file_path, language)); } diff --git a/src/project.cpp b/src/project.cpp index 0a03cdf..8a89889 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -908,7 +908,7 @@ std::pair Project::Rust::get_run_arguments() { arguments = run_arguments_it->second; if(arguments.empty()) - arguments = filesystem::get_short_path(build->get_executable(project_path)).string(); + arguments = filesystem::escape_argument(filesystem::get_short_path(build->get_executable(project_path)).string()); return {project_path, arguments}; } @@ -921,8 +921,7 @@ void Project::Rust::compile() { Terminal::get().print("\e[2mCompiling project " + filesystem::get_short_path(build->project_path).string() + "\e[m\n"); - auto command = build->get_compile_command(); - Terminal::get().async_process(command, build->project_path, [](int exit_status) { + Terminal::get().async_process(build->get_compile_command(), build->project_path, [](int exit_status) { compiling = false; }); } diff --git a/src/project_build.cpp b/src/project_build.cpp index d767121..a73cccb 100644 --- a/src/project_build.cpp +++ b/src/project_build.cpp @@ -227,3 +227,12 @@ bool Project::CargoBuild::update_debug(bool force) { std::string Project::CargoBuild::get_compile_command() { return Config::get().project.cargo_command + " build"; } + +boost::filesystem::path Project::CargoBuild::get_executable(const boost::filesystem::path &path) { + auto project_name = project_path.filename().string(); + for(auto &chr : project_name) { + if(chr == ' ') + chr = '_'; + } + return get_debug_path() / project_name; +} diff --git a/src/project_build.hpp b/src/project_build.hpp index 0ddacf1..a69add0 100644 --- a/src/project_build.hpp +++ b/src/project_build.hpp @@ -68,7 +68,7 @@ namespace Project { bool update_debug(bool force = false) override; std::string get_compile_command() override; - 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; }; class NpmBuild : public Build { diff --git a/src/source_base.cpp b/src/source_base.cpp index 075712b..0fd282c 100644 --- a/src/source_base.cpp +++ b/src/source_base.cpp @@ -169,6 +169,10 @@ Source::BaseView::BaseView(const boost::filesystem::path &file_path, const Glib: #endif tab_char = Config::get().source.default_tab_char; tab_size = Config::get().source.default_tab_size; + if(language && (language->get_id() == "python" || language->get_id() == "rust")) { + tab_char = ' '; + tab_size = 4; + } 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) { @@ -408,9 +412,6 @@ void Source::BaseView::check_last_write_time(boost::optional last_w } std::pair Source::BaseView::find_tab_char_and_size() { - if(language && language->get_id() == "python") - return {' ', 4}; - std::map tab_chars; std::map tab_sizes; auto iter = get_buffer()->begin(); diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index f76131e..cc4e588 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -568,6 +568,20 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { style_file_search_path = style_file_search_path.parent_path(); } + if(!has_style_file && language && language->get_id() == "rust") { + auto style_file_search_path = file_path.parent_path(); + while(true) { + if(boost::filesystem::exists(style_file_search_path / "rustfmt.toml", ec) || + boost::filesystem::exists(style_file_search_path / ".rustfmt.toml", ec)) { + 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; } diff --git a/src/window.cpp b/src/window.cpp index a0869c6..f6a8463 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -429,6 +429,33 @@ void Window::set_menu_actions() { Terminal::get().print("\e[31mError\e[m: Could not create project " + filesystem::get_short_path(project_path).string() + "\n", true); } }); + menu.add_action("file_new_project_rust", []() { + auto sysroot = filesystem::get_rust_sysroot_path(); + if(sysroot.empty()) { + Terminal::get().print("\e[33mWarning\e[m: could not find Rust.\n"); + Terminal::get().print("For installation instructions please visit: https://gitlab.com/cppit/jucipp/-/blob/master/docs/language_servers.md#rust.\n"); + return; + } + boost::filesystem::path project_path = Dialog::new_folder(Project::get_preferably_directory_folder()); + if(!project_path.empty()) { + auto project_name = project_path.filename().string(); + for(auto &chr : project_name) { + if(chr == ' ') + chr = '_'; + } + if(Terminal::get().process("cargo init " + filesystem::escape_argument(project_path.string()) + " --bin --vcs none --name " + project_name) == 0) { + filesystem::write(project_path / ".rustfmt.toml", ""); + Directories::get().open(project_path); + Notebook::get().open(project_path / "src" / "main.rs"); + Directories::get().update(); + Terminal::get().print("Rust project "); + Terminal::get().print(project_path.filename().string(), true); + Terminal::get().print(" \e[32mcreated\e[m\n"); + } + else + Terminal::get().print("\e[31mError\e[m: Could not create project " + filesystem::get_short_path(project_path).string() + "\n", true); + } + }); menu.add_action("file_open_file", []() { auto path = Dialog::open_file(Project::get_preferably_view_folder()); From 4d1f725a0fa8dc5fd4e516f7eabe5f9fc0e4c1e9 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 25 May 2021 08:44:30 +0200 Subject: [PATCH 103/375] New Project menu items now also add .gitignore files --- src/window.cpp | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/window.cpp b/src/window.cpp index f6a8463..95d332c 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -350,6 +350,7 @@ void Window::set_menu_actions() { } auto c_main_path = project_path / "main.c"; auto clang_format_path = project_path / ".clang-format"; + auto gitignore_path = project_path / ".gitignore"; boost::system::error_code ec; if(boost::filesystem::exists(build_config_path, ec)) { Terminal::get().print("\e[31mError\e[m: " + filesystem::get_short_path(build_config_path).string() + " already exists\n", true); @@ -363,9 +364,13 @@ void Window::set_menu_actions() { Terminal::get().print("\e[31mError\e[m: " + filesystem::get_short_path(clang_format_path).string() + " already exists\n", true); return; } - std::string c_main = "#include \n\nint main() {\n printf(\"Hello World!\\n\");\n}\n"; - std::string clang_format = "IndentWidth: 2\nAccessModifierOffset: -2\nUseTab: Never\nColumnLimit: 0\n"; - if(filesystem::write(build_config_path, build_config) && filesystem::write(c_main_path, c_main) && filesystem::write(clang_format_path, clang_format)) { + if(boost::filesystem::exists(gitignore_path, ec)) { + Terminal::get().print("\e[31mError\e[m: " + filesystem::get_short_path(gitignore_path).string() + " already exists\n", true); + return; + } + if(filesystem::write(build_config_path, build_config) && filesystem::write(c_main_path, "#include \n\nint main() {\n printf(\"Hello World!\\n\");\n}\n")) { + filesystem::write(clang_format_path, "IndentWidth: 2\nAccessModifierOffset: -2\nUseTab: Never\nColumnLimit: 0\n"); + filesystem::write(gitignore_path, "/build\n"); Directories::get().open(project_path); Notebook::get().open(c_main_path); Directories::get().update(); @@ -402,6 +407,7 @@ void Window::set_menu_actions() { } auto cpp_main_path = project_path / "main.cpp"; auto clang_format_path = project_path / ".clang-format"; + auto gitignore_path = project_path / ".gitignore"; boost::system::error_code ec; if(boost::filesystem::exists(build_config_path, ec)) { Terminal::get().print("\e[31mError\e[m: " + filesystem::get_short_path(build_config_path).string() + " already exists\n", true); @@ -415,9 +421,13 @@ void Window::set_menu_actions() { Terminal::get().print("\e[31mError\e[m: " + filesystem::get_short_path(clang_format_path).string() + " already exists\n", true); return; } - std::string cpp_main = "#include \n\nint main() {\n std::cout << \"Hello World!\\n\";\n}\n"; - std::string clang_format = "IndentWidth: 2\nAccessModifierOffset: -2\nUseTab: Never\nColumnLimit: 0\nNamespaceIndentation: All\n"; - if(filesystem::write(build_config_path, build_config) && filesystem::write(cpp_main_path, cpp_main) && filesystem::write(clang_format_path, clang_format)) { + if(boost::filesystem::exists(gitignore_path, ec)) { + Terminal::get().print("\e[31mError\e[m: " + filesystem::get_short_path(gitignore_path).string() + " already exists\n", true); + return; + } + if(filesystem::write(build_config_path, build_config) && filesystem::write(cpp_main_path, "#include \n\nint main() {\n std::cout << \"Hello World!\\n\";\n}\n")) { + filesystem::write(clang_format_path, "IndentWidth: 2\nAccessModifierOffset: -2\nUseTab: Never\nColumnLimit: 0\nNamespaceIndentation: All\n"); + filesystem::write(gitignore_path, "/build\n"); Directories::get().open(project_path); Notebook::get().open(cpp_main_path); Directories::get().update(); @@ -444,6 +454,7 @@ void Window::set_menu_actions() { chr = '_'; } if(Terminal::get().process("cargo init " + filesystem::escape_argument(project_path.string()) + " --bin --vcs none --name " + project_name) == 0) { + filesystem::write(project_path / ".gitignore", "/target\n"); filesystem::write(project_path / ".rustfmt.toml", ""); Directories::get().open(project_path); Notebook::get().open(project_path / "src" / "main.rs"); From 36a8515d051fa401c20e268566daa7673647aaf2 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 25 May 2021 11:34:49 +0200 Subject: [PATCH 104/375] Now also searches for rust-analyzer executable in $PATH --- src/notebook.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/notebook.cpp b/src/notebook.cpp index 19b432e..123809e 100644 --- a/src/notebook.cpp +++ b/src/notebook.cpp @@ -170,19 +170,25 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position } } + size_t source_views_previous_size = source_views.size(); 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, language_protocol_language_id + "-language-server")); - else if(language && language_protocol_language_id == "rust" && !filesystem::get_rust_sysroot_path().empty()) { - auto rust_analyzer = filesystem::get_rust_sysroot_path() / "bin" / "rust-analyzer"; - boost::system::error_code ec; - if(boost::filesystem::exists(rust_analyzer, ec)) - source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, rust_analyzer.string())); - else - source_views.emplace_back(new Source::GenericView(file_path, language)); + else if(language && language_protocol_language_id == "rust") { + if(!filesystem::find_executable("rust-analyzer").empty()) + source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, "rust-analyzer")); + else { + auto sysroot = filesystem::get_rust_sysroot_path(); + if(!sysroot.empty()) { + auto rust_analyzer = sysroot / "bin" / "rust-analyzer"; + boost::system::error_code ec; + if(boost::filesystem::exists(rust_analyzer, ec)) + source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, rust_analyzer.string())); + } + } } - else { + if(source_views_previous_size == source_views.size()) { if(language) { static std::set shown; std::string language_id = language->get_id(); From 81b3b670dce2687a0c2be2e4886d18e5c18359bb Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 26 May 2021 08:31:56 +0200 Subject: [PATCH 105/375] Improved instructions when rust installation is not found --- docs/language_servers.md | 2 +- src/notebook.cpp | 5 ++++- src/window.cpp | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/language_servers.md b/docs/language_servers.md index a3294f4..fbbae20 100644 --- a/docs/language_servers.md +++ b/docs/language_servers.md @@ -69,7 +69,7 @@ ln -s `which pyls` /usr/local/bin/python-language-server ## Rust - Prerequisites: - - Rust + - [Rust](https://www.rust-lang.org/tools/install) Install language server, and create symbolic link to enable server in juCi++: diff --git a/src/notebook.cpp b/src/notebook.cpp index 123809e..bef2af1 100644 --- a/src/notebook.cpp +++ b/src/notebook.cpp @@ -204,8 +204,11 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position shown.emplace(language_id); } else if(language_id == "rust") { - Terminal::get().print("\e[33mWarning\e[m: could not find Rust language server.\n"); + auto rust_installed = !filesystem::get_rust_sysroot_path().empty(); + Terminal::get().print(std::string("\e[33mWarning\e[m: could not find Rust ") + (rust_installed ? "language server" : "installation") + ".\n"); Terminal::get().print("For installation instructions please visit: https://gitlab.com/cppit/jucipp/-/blob/master/docs/language_servers.md#rust.\n"); + if(!rust_installed) + Terminal::get().print("You will need to restart juCi++ after installing Rust.\n"); shown.emplace(language_id); } } diff --git a/src/window.cpp b/src/window.cpp index 95d332c..2850448 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -442,8 +442,9 @@ void Window::set_menu_actions() { menu.add_action("file_new_project_rust", []() { auto sysroot = filesystem::get_rust_sysroot_path(); if(sysroot.empty()) { - Terminal::get().print("\e[33mWarning\e[m: could not find Rust.\n"); + Terminal::get().print("\e[33mWarning\e[m: could not find Rust installation.\n"); Terminal::get().print("For installation instructions please visit: https://gitlab.com/cppit/jucipp/-/blob/master/docs/language_servers.md#rust.\n"); + Terminal::get().print("You will need to restart juCi++ after installing Rust.\n"); return; } boost::filesystem::path project_path = Dialog::new_folder(Project::get_preferably_directory_folder()); From 11a5368ea480bbef5d9121eead2881cf099008c1 Mon Sep 17 00:00:00 2001 From: eidheim Date: Thu, 27 May 2021 14:33:00 +0200 Subject: [PATCH 106/375] Renamed instances in get() functions to instance --- src/config.hpp | 4 ++-- src/debug_lldb.hpp | 4 ++-- src/directories.hpp | 4 ++-- src/entrybox.hpp | 4 ++-- src/menu.hpp | 4 ++-- src/notebook.hpp | 4 ++-- src/snippets.hpp | 4 ++-- src/terminal.hpp | 4 ++-- src/window.hpp | 4 ++-- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/config.hpp b/src/config.hpp index ec44076..36999d7 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -119,8 +119,8 @@ private: public: static Config &get() { - static Config singleton; - return singleton; + static Config instance; + return instance; } void load(); diff --git a/src/debug_lldb.hpp b/src/debug_lldb.hpp index 5609b15..3c11938 100644 --- a/src/debug_lldb.hpp +++ b/src/debug_lldb.hpp @@ -38,8 +38,8 @@ namespace Debug { public: static LLDB &get() { - static LLDB singleton; - return singleton; + static LLDB instance; + return instance; } /// Must be called before application terminates (cannot be placed in destructor sadly) diff --git a/src/directories.hpp b/src/directories.hpp index bfbe0ce..f38497c 100644 --- a/src/directories.hpp +++ b/src/directories.hpp @@ -55,8 +55,8 @@ class Directories : public Gtk::ListViewText { public: static Directories &get() { - static Directories singleton; - return singleton; + static Directories instance; + return instance; } ~Directories() override; diff --git a/src/entrybox.hpp b/src/entrybox.hpp index 893e077..a118e85 100644 --- a/src/entrybox.hpp +++ b/src/entrybox.hpp @@ -39,8 +39,8 @@ private: public: static EntryBox &get() { - static EntryBox singleton; - return singleton; + static EntryBox instance; + return instance; } Gtk::Box upper_box; diff --git a/src/menu.hpp b/src/menu.hpp index a6f56f8..be2336c 100644 --- a/src/menu.hpp +++ b/src/menu.hpp @@ -9,8 +9,8 @@ class Menu { public: static Menu &get() { - static Menu singleton; - return singleton; + static Menu instance; + return instance; } void add_action(const std::string &name, const std::function &action); diff --git a/src/notebook.hpp b/src/notebook.hpp index c51d363..6b30171 100644 --- a/src/notebook.hpp +++ b/src/notebook.hpp @@ -27,8 +27,8 @@ class Notebook : public Gtk::Paned { public: static Notebook &get() { - static Notebook singleton; - return singleton; + static Notebook instance; + return instance; } std::vector notebooks; diff --git a/src/snippets.hpp b/src/snippets.hpp index 5f3c924..c32f625 100644 --- a/src/snippets.hpp +++ b/src/snippets.hpp @@ -17,8 +17,8 @@ public: }; static Snippets &get() { - static Snippets singleton; - return singleton; + static Snippets instance; + return instance; } std::vector>> snippets; diff --git a/src/terminal.hpp b/src/terminal.hpp index 409cb5f..726ca05 100644 --- a/src/terminal.hpp +++ b/src/terminal.hpp @@ -15,8 +15,8 @@ class Terminal : public Source::SearchView { public: static Terminal &get() { - static Terminal singleton; - return singleton; + static Terminal instance; + return instance; } int process(const std::string &command, const boost::filesystem::path &path = "", bool use_pipes = true); diff --git a/src/window.hpp b/src/window.hpp index 20b37a0..dea21bd 100644 --- a/src/window.hpp +++ b/src/window.hpp @@ -9,8 +9,8 @@ class Window : public Gtk::ApplicationWindow { public: static Window &get() { - static Window singleton; - return singleton; + static Window instance; + return instance; } void add_widgets(); void save_session(); From c2eff0a40ce538d90ce874e8b0c850bc3fad01d3 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 28 May 2021 12:48:34 +0200 Subject: [PATCH 107/375] Fixes #434 : added possibility to add custom commands in menu item juCi++, Commands --- CMakeLists.txt | 2 +- README.md | 8 +- src/CMakeLists.txt | 1 + src/commands.cpp | 53 ++++++++ src/commands.hpp | 29 +++++ src/compile_commands.cpp | 3 +- src/debug_lldb.cpp | 1 + src/directories.cpp | 2 +- src/files.hpp | 1 + src/filesystem.cpp | 10 ++ src/filesystem.hpp | 2 + src/menu.cpp | 4 + src/project.cpp | 252 ++++++++++++++++++++------------------ src/project.hpp | 6 +- src/source.cpp | 4 +- src/window.cpp | 97 ++++++++++++++- tests/filesystem_test.cpp | 8 ++ tests/stubs/project.cpp | 4 +- 18 files changed, 351 insertions(+), 136 deletions(-) create mode 100644 src/commands.cpp create mode 100644 src/commands.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3fe3c00..782d6af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.1) project(juci) -set(JUCI_VERSION "1.6.3") +set(JUCI_VERSION "1.6.3.1") set(CPACK_PACKAGE_NAME "jucipp") set(CPACK_PACKAGE_CONTACT "Ole Christian Eidheim ") diff --git a/README.md b/README.md index 88e5fa4..04855da 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,10 @@ IDE is instead integrated directly into juCi++. For effective development, juCi++ is primarily written for Unix/Linux systems. However, Windows users can use juCi++ through POSIX compatibility layers such as MSYS2. +## Installation + +See [installation guide](docs/install.md). + ## Features - Platform independent @@ -76,10 +80,6 @@ for planned features. -## Installation - -See [installation guide](docs/install.md). - ## Custom styling See [custom styling](docs/custom_styling.md). diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 14a0229..3585c7f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,6 +2,7 @@ set(JUCI_SHARED_FILES autocomplete.cpp cmake.cpp + commands.cpp compile_commands.cpp ctags.cpp dispatcher.cpp diff --git a/src/commands.cpp b/src/commands.cpp new file mode 100644 index 0000000..c58932f --- /dev/null +++ b/src/commands.cpp @@ -0,0 +1,53 @@ +#include "commands.hpp" +#include "config.hpp" +#include "filesystem.hpp" +#include "terminal.hpp" +#include + +void Commands::load() { + auto commands_file = Config::get().home_juci_path / "commands.json"; + + boost::system::error_code ec; + if(!boost::filesystem::exists(commands_file, ec)) + filesystem::write(commands_file, R"([ + { + "key": "1", + "path_comment": "Regular expression for which paths this command should apply", + "path": "^.*\\.json$", + "compile_comment": "Add compile command if a compilation step is needed prior to the run command. is set to the matching file or directory, and is set to the project directory if found or the matching file's directory.", + "compile": "", + "run_comment": " is set to the matching file or directory, and is set to the project directory if found or the matching file's directory", + "run": "echo && echo ", + "debug_comment": "Whether or not this command should run through debugger", + "debug": false, + "debug_remote_host": "" + } +] +)"); + + commands.clear(); + try { + boost::property_tree::ptree pt; + boost::property_tree::json_parser::read_json(commands_file.string(), pt); + for(auto command_it = pt.begin(); command_it != pt.end(); ++command_it) { + auto key_string = command_it->second.get("key"); + guint key = 0; + GdkModifierType modifier = static_cast(0); + if(!key_string.empty()) { + gtk_accelerator_parse(key_string.c_str(), &key, &modifier); + if(key == 0 && modifier == 0) + Terminal::get().async_print("\e[31mError\e[m: could not parse key string: " + key_string + "\n", true); + } + auto path = command_it->second.get("path", ""); + boost::optional regex; + if(!path.empty()) + regex = std::regex(path, std::regex::optimize); + commands.emplace_back(Command{key, modifier, std::move(regex), + command_it->second.get("compile", ""), command_it->second.get("run"), + command_it->second.get("debug", false), command_it->second.get("debug_remote_host", "")}); + } + } + catch(const std::exception &e) { + Terminal::get().async_print(std::string("\e[31mError\e[m: ") + e.what() + "\n", true); + } +} diff --git a/src/commands.hpp b/src/commands.hpp new file mode 100644 index 0000000..86847b7 --- /dev/null +++ b/src/commands.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include +#include +#include + +class Commands { +public: + class Command { + public: + guint key; + GdkModifierType modifier; + boost::optional path; + std::string compile; + std::string run; + bool debug; + std::string debug_remote_host; + }; + + static Commands &get() { + static Commands instance; + return instance; + } + + std::vector commands; + void load(); +}; diff --git a/src/compile_commands.cpp b/src/compile_commands.cpp index e5912b9..88e598b 100644 --- a/src/compile_commands.cpp +++ b/src/compile_commands.cpp @@ -1,6 +1,7 @@ #include "compile_commands.hpp" #include "clangmm.hpp" #include "config.hpp" +#include "filesystem.hpp" #include "terminal.hpp" #include "utility.hpp" #include @@ -107,7 +108,7 @@ CompileCommands::CompileCommands(const boost::filesystem::path &build_path) { if(parameter_start_pos != std::string::npos) add_parameter(); - commands.emplace_back(Command{directory, parameters, boost::filesystem::absolute(file, build_path)}); + commands.emplace_back(Command{directory, parameters, filesystem::get_absolute_path(file, build_path)}); } } catch(...) { diff --git a/src/debug_lldb.cpp b/src/debug_lldb.cpp index cb83b47..327f6c3 100644 --- a/src/debug_lldb.cpp +++ b/src/debug_lldb.cpp @@ -129,6 +129,7 @@ void Debug::LLDB::start(const std::string &command, const boost::filesystem::pat argv.emplace_back(argument.c_str()); argv.emplace_back(nullptr); + executable = filesystem::get_absolute_path(executable, path).string(); auto target = debugger->CreateTarget(executable.c_str()); if(!target.IsValid()) { Terminal::get().async_print("\e[31mError (debug)\e[m: Could not create debug target to: " + executable + '\n', true); diff --git a/src/directories.cpp b/src/directories.cpp index 3f3a915..fd154d7 100644 --- a/src/directories.cpp +++ b/src/directories.cpp @@ -438,7 +438,7 @@ Directories::Directories() : Gtk::ListViewText(1) { return; Gtk::MessageDialog dialog(*static_cast(get_toplevel()), "Delete!", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO); dialog.set_default_response(Gtk::RESPONSE_NO); - dialog.set_secondary_text("Are you sure you want to delete " + menu_popup_row_path.string() + "?"); + dialog.set_secondary_text("Are you sure you want to delete " + filesystem::get_short_path(menu_popup_row_path).string() + "?"); int result = dialog.run(); if(result == Gtk::RESPONSE_YES) { boost::system::error_code ec; diff --git a/src/files.hpp b/src/files.hpp index ffd6aaf..92cb638 100644 --- a/src/files.hpp +++ b/src/files.hpp @@ -126,6 +126,7 @@ const std::string default_config_file = "keybindings": { "preferences": "comma", "snippets": "", + "commands": "", "quit": "q", "file_new_file": "n", "file_new_folder": "n", diff --git a/src/filesystem.cpp b/src/filesystem.cpp index 7a6792f..76acacf 100644 --- a/src/filesystem.cpp +++ b/src/filesystem.cpp @@ -233,6 +233,16 @@ boost::filesystem::path filesystem::get_relative_path(const boost::filesystem::p return relative_path; } +boost::filesystem::path filesystem::get_absolute_path(const boost::filesystem::path &path, const boost::filesystem::path &base) noexcept { + boost::filesystem::path absolute_path; + for(auto path_it = path.begin(); path_it != path.end(); ++path_it) { + if(path_it == path.begin() && (!path.has_root_path() && *path_it != "~")) + absolute_path /= base; + absolute_path /= *path_it; + } + return absolute_path; +} + boost::filesystem::path filesystem::get_executable(const boost::filesystem::path &executable_name) noexcept { #if defined(__APPLE__) || defined(_WIN32) return executable_name; diff --git a/src/filesystem.hpp b/src/filesystem.hpp index 173faa3..4cd2135 100644 --- a/src/filesystem.hpp +++ b/src/filesystem.hpp @@ -35,6 +35,8 @@ public: static boost::filesystem::path get_relative_path(const boost::filesystem::path &path, const boost::filesystem::path &base) noexcept; + static boost::filesystem::path get_absolute_path(const boost::filesystem::path &path, const boost::filesystem::path &base) noexcept; + /// Return executable with latest version in filename on systems that is lacking executable_name symbolic link static boost::filesystem::path get_executable(const boost::filesystem::path &executable_name) noexcept; diff --git a/src/menu.cpp b/src/menu.cpp index 19aea24..24f335e 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -104,6 +104,10 @@ const Glib::ustring menu_xml = R"RAW( _Snippets app.snippets + + _Commands + app.commands +
diff --git a/src/project.cpp b/src/project.cpp index 8a89889..bcac224 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1,4 +1,5 @@ #include "project.hpp" +#include "commands.hpp" #include "config.hpp" #include "directories.hpp" #include "filesystem.hpp" @@ -72,6 +73,9 @@ void Project::on_save(size_t index) { view->set_snippets(); } + if(view->file_path == Config::get().home_juci_path / "commands.json") + Commands::get().load(); + boost::filesystem::path build_path; if(view->language && view->language->get_id() == "cmake") { if(view->file_path.filename() == "CMakeLists.txt") @@ -259,10 +263,15 @@ std::pair Project::Base::debug_get_run_arguments() { return {"", ""}; } -void Project::Base::debug_start() { +void Project::Base::debug_compile_and_start() { Info::get().print("Could not find a supported project"); } +void Project::Base::debug_start(const std::string &command, const boost::filesystem::path &path, const std::string &remote_host) { + Info::get().print("Could not find a supported project"); + Project::debugging = false; +} + #ifdef JUCI_ENABLE_DEBUG std::pair Project::LLDB::debug_get_run_arguments() { auto debug_build_path = build->get_debug_path(); @@ -336,7 +345,7 @@ Project::DebugOptions *Project::LLDB::debug_get_options() { return debug_options.get(); } -void Project::LLDB::debug_start() { +void Project::LLDB::debug_compile_and_start() { auto default_build_path = build->get_default_path(); if(default_build_path.empty() || !build->update_default()) return; @@ -344,17 +353,19 @@ void Project::LLDB::debug_start() { if(debug_build_path.empty() || !build->update_debug()) return; - auto project_path = std::make_shared(build->project_path); - - auto run_arguments_it = debug_run_arguments.find(project_path->string()); - auto run_arguments = std::make_shared(); - if(run_arguments_it != debug_run_arguments.end()) - *run_arguments = run_arguments_it->second.arguments; + auto run_arguments_it = debug_run_arguments.find(build->project_path.string()); + std::string run_arguments; + std::string remote_host; + if(run_arguments_it != debug_run_arguments.end()) { + run_arguments = run_arguments_it->second.arguments; + if(run_arguments_it->second.remote_enabled) + remote_host = run_arguments_it->second.remote_host_port; + } - if(run_arguments->empty()) { + if(run_arguments.empty()) { auto view = Notebook::get().get_current_view(); - *run_arguments = build->get_executable(view ? view->file_path : Directories::get().path).string(); - if(run_arguments->empty()) { + run_arguments = build->get_executable(view ? view->file_path : Directories::get().path).string(); + if(run_arguments.empty()) { if(!build->is_valid()) Terminal::get().print("\e[31mError\e[m: build folder no longer valid, please rebuild project.\n", true); else { @@ -363,10 +374,10 @@ void Project::LLDB::debug_start() { } return; } - size_t pos = run_arguments->find(default_build_path.string()); + size_t pos = run_arguments.find(default_build_path.string()); if(pos != std::string::npos) - run_arguments->replace(pos, default_build_path.string().size(), debug_build_path.string()); - *run_arguments = filesystem::escape_argument(filesystem::get_short_path(*run_arguments).string()); + run_arguments.replace(pos, default_build_path.string().size(), debug_build_path.string()); + run_arguments = filesystem::escape_argument(filesystem::get_short_path(run_arguments).string()); } debugging = true; @@ -374,115 +385,114 @@ void Project::LLDB::debug_start() { if(Config::get().terminal.clear_on_compile) Terminal::get().clear(); - Terminal::get().print("\e[2mCompiling and debugging " + *run_arguments + "\e[m\n"); - Terminal::get().async_process(build->get_compile_command(), debug_build_path, [self = this->shared_from_this(), run_arguments, project_path](int exit_status) { + Terminal::get().print("\e[2mCompiling and debugging: " + run_arguments + "\e[m\n"); + Terminal::get().async_process(build->get_compile_command(), debug_build_path, [self = this->shared_from_this(), run_arguments, project_path = build->project_path, remote_host](int exit_status) { if(exit_status != EXIT_SUCCESS) debugging = false; else { - std::vector> breakpoints; - for(size_t c = 0; c < Notebook::get().size(); c++) { - auto view = Notebook::get().get_view(c); - if(filesystem::file_in_path(view->file_path, *project_path)) { - auto iter = view->get_buffer()->begin(); - if(view->get_source_buffer()->get_source_marks_at_iter(iter, "debug_breakpoint").size() > 0) - breakpoints.emplace_back(view->file_path, iter.get_line() + 1); - while(view->get_source_buffer()->forward_iter_to_source_mark(iter, "debug_breakpoint")) - breakpoints.emplace_back(view->file_path, iter.get_line() + 1); - } - } + self->debug_start(run_arguments, project_path, remote_host); + } + }); +} - std::string remote_host; - auto debug_run_arguments_it = debug_run_arguments.find(project_path->string()); - if(debug_run_arguments_it != debug_run_arguments.end() && debug_run_arguments_it->second.remote_enabled) - remote_host = debug_run_arguments_it->second.remote_host_port; - - static auto on_exit_it = Debug::LLDB::get().on_exit.end(); - if(on_exit_it != Debug::LLDB::get().on_exit.end()) - Debug::LLDB::get().on_exit.erase(on_exit_it); - Debug::LLDB::get().on_exit.emplace_back([self, run_arguments](int exit_status) { - debugging = false; - Terminal::get().async_print("\e[2m" + *run_arguments + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); - self->dispatcher.post([] { - debug_update_status(""); - }); - }); - on_exit_it = std::prev(Debug::LLDB::get().on_exit.end()); - - static auto on_event_it = Debug::LLDB::get().on_event.end(); - if(on_event_it != Debug::LLDB::get().on_event.end()) - Debug::LLDB::get().on_event.erase(on_event_it); - Debug::LLDB::get().on_event.emplace_back([self](const lldb::SBEvent &event) { - std::string status; - boost::filesystem::path stop_path; - unsigned stop_line = 0, stop_column = 0; - - LockGuard lock(Debug::LLDB::get().mutex); - auto process = lldb::SBProcess::GetProcessFromEvent(event); - auto state = lldb::SBProcess::GetStateFromEvent(event); +void Project::LLDB::debug_start(const std::string &command, const boost::filesystem::path &path, const std::string &remote_host) { + std::vector> breakpoints; + for(size_t c = 0; c < Notebook::get().size(); c++) { + auto view = Notebook::get().get_view(c); + if(filesystem::file_in_path(view->file_path, path)) { + auto iter = view->get_buffer()->begin(); + if(view->get_source_buffer()->get_source_marks_at_iter(iter, "debug_breakpoint").size() > 0) + breakpoints.emplace_back(view->file_path, iter.get_line() + 1); + while(view->get_source_buffer()->forward_iter_to_source_mark(iter, "debug_breakpoint")) + breakpoints.emplace_back(view->file_path, iter.get_line() + 1); + } + } + + static auto on_exit_it = Debug::LLDB::get().on_exit.end(); + if(on_exit_it != Debug::LLDB::get().on_exit.end()) + Debug::LLDB::get().on_exit.erase(on_exit_it); + Debug::LLDB::get().on_exit.emplace_back([self = shared_from_this(), command](int exit_status) { + debugging = false; + Terminal::get().async_print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); + self->dispatcher.post([] { + debug_update_status(""); + }); + }); + on_exit_it = std::prev(Debug::LLDB::get().on_exit.end()); + + static auto on_event_it = Debug::LLDB::get().on_event.end(); + if(on_event_it != Debug::LLDB::get().on_event.end()) + Debug::LLDB::get().on_event.erase(on_event_it); + Debug::LLDB::get().on_event.emplace_back([self = shared_from_this()](const lldb::SBEvent &event) { + std::string status; + boost::filesystem::path stop_path; + unsigned stop_line = 0, stop_column = 0; + + LockGuard lock(Debug::LLDB::get().mutex); + auto process = lldb::SBProcess::GetProcessFromEvent(event); + auto state = lldb::SBProcess::GetStateFromEvent(event); + lldb::SBStream stream; + event.GetDescription(stream); + std::string event_desc = stream.GetData(); + event_desc.pop_back(); + auto pos = event_desc.rfind(" = "); + if(pos != std::string::npos && pos + 3 < event_desc.size()) + status = event_desc.substr(pos + 3); + if(state == lldb::StateType::eStateStopped) { + char buffer[100]; + auto thread = process.GetSelectedThread(); + auto n = thread.GetStopDescription(buffer, 100); // Returns number of bytes read. Might include null termination... Although maybe on newer versions only. + if(n > 0) + status += " (" + std::string(buffer, n <= 100 ? (buffer[n - 1] == '\0' ? n - 1 : n) : 100) + ")"; + auto line_entry = thread.GetSelectedFrame().GetLineEntry(); + if(line_entry.IsValid()) { lldb::SBStream stream; - event.GetDescription(stream); - std::string event_desc = stream.GetData(); - event_desc.pop_back(); - auto pos = event_desc.rfind(" = "); - if(pos != std::string::npos && pos + 3 < event_desc.size()) - status = event_desc.substr(pos + 3); - if(state == lldb::StateType::eStateStopped) { - char buffer[100]; - auto thread = process.GetSelectedThread(); - auto n = thread.GetStopDescription(buffer, 100); // Returns number of bytes read. Might include null termination... Although maybe on newer versions only. - if(n > 0) - status += " (" + std::string(buffer, n <= 100 ? (buffer[n - 1] == '\0' ? n - 1 : n) : 100) + ")"; - auto line_entry = thread.GetSelectedFrame().GetLineEntry(); - if(line_entry.IsValid()) { - lldb::SBStream stream; - line_entry.GetFileSpec().GetDescription(stream); - auto line = line_entry.GetLine(); - status += " " + boost::filesystem::path(stream.GetData()).filename().string() + ":" + std::to_string(line); - auto column = line_entry.GetColumn(); - if(column == 0) - column = 1; - stop_path = filesystem::get_normal_path(stream.GetData()); - stop_line = line - 1; - stop_column = column - 1; - } - } + line_entry.GetFileSpec().GetDescription(stream); + auto line = line_entry.GetLine(); + status += " " + boost::filesystem::path(stream.GetData()).filename().string() + ":" + std::to_string(line); + auto column = line_entry.GetColumn(); + if(column == 0) + column = 1; + stop_path = filesystem::get_normal_path(stream.GetData()); + stop_line = line - 1; + stop_column = column - 1; + } + } - self->dispatcher.post([status = std::move(status), stop_path = std::move(stop_path), stop_line, stop_column] { - debug_update_status(status); - Project::debug_stop.first = stop_path; - Project::debug_stop.second.first = stop_line; - Project::debug_stop.second.second = stop_column; - debug_update_stop(); - - if(Config::get().source.debug_place_cursor_at_stop && !stop_path.empty()) { - if(Notebook::get().open(stop_path)) { - auto view = Notebook::get().get_current_view(); - view->place_cursor_at_line_index(stop_line, stop_column); - view->scroll_to_cursor_delayed(true, false); - } - } - else if(auto view = Notebook::get().get_current_view()) - view->get_buffer()->place_cursor(view->get_buffer()->get_insert()->get_iter()); - }); - }); - on_event_it = std::prev(Debug::LLDB::get().on_event.end()); - - std::vector startup_commands; - if(dynamic_cast(self->build.get())) { - auto sysroot = filesystem::get_rust_sysroot_path().string(); - if(!sysroot.empty()) { - std::string line; - std::ifstream input(sysroot + "/lib/rustlib/etc/lldb_commands", std::ofstream::binary); - if(input) { - startup_commands.emplace_back("command script import \"" + sysroot + "/lib/rustlib/etc/lldb_lookup.py\""); - while(std::getline(input, line)) - startup_commands.emplace_back(line); - } + self->dispatcher.post([status = std::move(status), stop_path = std::move(stop_path), stop_line, stop_column] { + debug_update_status(status); + Project::debug_stop.first = stop_path; + Project::debug_stop.second.first = stop_line; + Project::debug_stop.second.second = stop_column; + debug_update_stop(); + + if(Config::get().source.debug_place_cursor_at_stop && !stop_path.empty()) { + if(Notebook::get().open(stop_path)) { + auto view = Notebook::get().get_current_view(); + view->place_cursor_at_line_index(stop_line, stop_column); + view->scroll_to_cursor_delayed(true, false); } } - Debug::LLDB::get().start(*run_arguments, *project_path, breakpoints, startup_commands, remote_host); - } + else if(auto view = Notebook::get().get_current_view()) + view->get_buffer()->place_cursor(view->get_buffer()->get_insert()->get_iter()); + }); }); + on_event_it = std::prev(Debug::LLDB::get().on_event.end()); + + std::vector startup_commands; + if(dynamic_cast(build.get())) { + auto sysroot = filesystem::get_rust_sysroot_path().string(); + if(!sysroot.empty()) { + std::string line; + std::ifstream input(sysroot + "/lib/rustlib/etc/lldb_commands", std::ofstream::binary); + if(input) { + startup_commands.emplace_back("command script import \"" + sysroot + "/lib/rustlib/etc/lldb_lookup.py\""); + while(std::getline(input, line)) + startup_commands.emplace_back(line); + } + } + } + Debug::LLDB::get().start(command, path, breakpoints, startup_commands, remote_host); } void Project::LLDB::debug_continue() { @@ -703,7 +713,7 @@ void Project::Clang::compile() { if(Config::get().terminal.clear_on_compile) Terminal::get().clear(); - Terminal::get().print("\e[2mCompiling project " + filesystem::get_short_path(build->project_path).string() + "\e[m\n"); + Terminal::get().print("\e[2mCompiling project: " + filesystem::get_short_path(build->project_path).string() + "\e[m\n"); Terminal::get().async_process(build->get_compile_command(), default_build_path, [](int exit_status) { compiling = false; }); @@ -741,7 +751,7 @@ void Project::Clang::compile_and_run() { if(Config::get().terminal.clear_on_compile) Terminal::get().clear(); - Terminal::get().print("\e[2mCompiling and running " + arguments + "\e[m\n"); + Terminal::get().print("\e[2mCompiling and running: " + arguments + "\e[m\n"); Terminal::get().async_process(build->get_compile_command(), default_build_path, [arguments, project_path](int exit_status) { compiling = false; if(exit_status == 0) { @@ -852,7 +862,7 @@ void Project::Python::compile_and_run() { if(Config::get().terminal.clear_on_compile) Terminal::get().clear(); - Terminal::get().print("\e[2mRunning " + command + "\e[m\n"); + Terminal::get().print("\e[2mRunning: " + command + "\e[m\n"); Terminal::get().async_process(command, path, [command](int exit_status) { Terminal::get().print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); }); @@ -878,7 +888,7 @@ void Project::JavaScript::compile_and_run() { if(Config::get().terminal.clear_on_compile) Terminal::get().clear(); - Terminal::get().print("\e[2mRunning " + command + "\e[m\n"); + Terminal::get().print("\e[2mRunning: " + command + "\e[m\n"); Terminal::get().async_process(command, path, [command](int exit_status) { Terminal::get().print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); }); @@ -891,7 +901,7 @@ void Project::HTML::compile_and_run() { if(Config::get().terminal.clear_on_compile) Terminal::get().clear(); - Terminal::get().print("\e[2mRunning " + command + "\e[m\n"); + Terminal::get().print("\e[2mRunning: " + command + "\e[m\n"); Terminal::get().async_process(command, build->project_path, [command](int exit_status) { Terminal::get().print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); }); @@ -919,7 +929,7 @@ void Project::Rust::compile() { if(Config::get().terminal.clear_on_compile) Terminal::get().clear(); - Terminal::get().print("\e[2mCompiling project " + filesystem::get_short_path(build->project_path).string() + "\e[m\n"); + Terminal::get().print("\e[2mCompiling project: " + filesystem::get_short_path(build->project_path).string() + "\e[m\n"); Terminal::get().async_process(build->get_compile_command(), build->project_path, [](int exit_status) { compiling = false; @@ -933,7 +943,7 @@ void Project::Rust::compile_and_run() { Terminal::get().clear(); auto arguments = get_run_arguments().second; - Terminal::get().print("\e[2mCompiling and running " + arguments + "\e[m\n"); + Terminal::get().print("\e[2mCompiling and running: " + arguments + "\e[m\n"); auto self = this->shared_from_this(); Terminal::get().async_process(build->get_compile_command(), build->project_path, [self, arguments = std::move(arguments)](int exit_status) { diff --git a/src/project.hpp b/src/project.hpp index 942c5a7..e5e3f94 100644 --- a/src/project.hpp +++ b/src/project.hpp @@ -64,7 +64,8 @@ namespace Project { virtual std::pair debug_get_run_arguments(); virtual Project::DebugOptions *debug_get_options() { return nullptr; } Tooltips debug_variable_tooltips; - virtual void debug_start(); + virtual void debug_compile_and_start(); + virtual void debug_start(const std::string &command, const boost::filesystem::path &path, const std::string &remote_host); virtual void debug_continue() {} virtual void debug_stop() {} virtual void debug_kill() {} @@ -85,7 +86,8 @@ namespace Project { #ifdef JUCI_ENABLE_DEBUG std::pair debug_get_run_arguments() override; Project::DebugOptions *debug_get_options() override; - void debug_start() override; + void debug_compile_and_start() override; + void debug_start(const std::string &command, const boost::filesystem::path &path, const std::string &remote_host) override; void debug_continue() override; void debug_stop() override; void debug_kill() override; diff --git a/src/source.cpp b/src/source.cpp index 0d9bdb9..96dacb5 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -2104,8 +2104,10 @@ bool Source::View::on_key_press_event(GdkEventKey *event) { { LockGuard lock(snippets_mutex); if(snippets) { + guint keyval_without_state; + gdk_keymap_translate_keyboard_state(gdk_keymap_get_default(), event->hardware_keycode, (GdkModifierType)0, 0, &keyval_without_state, nullptr, nullptr, nullptr); for(auto &snippet : *snippets) { - if(snippet.key == event->keyval && (snippet.modifier & event->state) == snippet.modifier) { + if((snippet.key == event->keyval || snippet.key == keyval_without_state) && (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK | GDK_MOD1_MASK | GDK_META_MASK)) == snippet.modifier) { insert_snippet(get_buffer()->get_insert()->get_iter(), snippet.body); return true; } diff --git a/src/window.cpp b/src/window.cpp index 2850448..dc468fb 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -3,6 +3,7 @@ #ifdef JUCI_ENABLE_DEBUG #include "debug_lldb.hpp" #endif +#include "commands.hpp" #include "compile_commands.hpp" #include "dialog.hpp" #include "directories.hpp" @@ -15,6 +16,7 @@ #include "project.hpp" #include "selection_dialog.hpp" #include "terminal.hpp" +#include Window::Window() { Gsv::init(); @@ -166,6 +168,7 @@ Window::Window() { void Window::configure() { Config::get().load(); Snippets::get().load(); + Commands::get().load(); auto screen = get_screen(); static Glib::RefPtr css_provider_theme; @@ -284,6 +287,9 @@ void Window::set_menu_actions() { menu.add_action("snippets", []() { Notebook::get().open(Config::get().home_juci_path / "snippets.json"); }); + menu.add_action("commands", []() { + Notebook::get().open(Config::get().home_juci_path / "commands.json"); + }); menu.add_action("quit", [this]() { close(); }); @@ -1386,7 +1392,7 @@ void Window::set_menu_actions() { Project::current = Project::create(); if(Config::get().project.save_on_compile_or_run) - Project::save_files(Project::current->build->project_path); + Project::save_files(!Project::current->build->project_path.empty() ? Project::current->build->project_path : Project::get_preferably_view_folder()); Project::current->compile_and_run(); }); @@ -1399,7 +1405,7 @@ void Window::set_menu_actions() { Project::current = Project::create(); if(Config::get().project.save_on_compile_or_run) - Project::save_files(Project::current->build->project_path); + Project::save_files(!Project::current->build->project_path.empty() ? Project::current->build->project_path : Project::get_preferably_view_folder()); Project::current->compile(); }); @@ -1507,9 +1513,9 @@ void Window::set_menu_actions() { Project::current = Project::create(); if(Config::get().project.save_on_compile_or_run) - Project::save_files(Project::current->build->project_path); + Project::save_files(!Project::current->build->project_path.empty() ? Project::current->build->project_path : Project::get_preferably_view_folder()); - Project::current->debug_start(); + Project::current->debug_compile_and_start(); }); menu.add_action("debug_stop", []() { if(Project::current) @@ -1893,6 +1899,89 @@ void Window::add_widgets() { } bool Window::on_key_press_event(GdkEventKey *event) { + guint keyval_without_state; + gdk_keymap_translate_keyboard_state(gdk_keymap_get_default(), event->hardware_keycode, (GdkModifierType)0, 0, &keyval_without_state, nullptr, nullptr, nullptr); + for(auto &command : Commands::get().commands) { + if((command.key == event->keyval || command.key == keyval_without_state) && (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK | GDK_MOD1_MASK | GDK_META_MASK)) == command.modifier) { + auto view = Notebook::get().get_current_view(); + auto view_folder = Project::get_preferably_view_folder(); + auto path = view ? view->file_path : view_folder; + if(command.path) { + std::smatch sm; + if(!std::regex_match(path.string(), sm, *command.path) && !std::regex_match(filesystem::get_short_path(path).string(), sm, *command.path)) + continue; + } + + auto project = Project::create(); + auto run_path = !project->build->project_path.empty() ? project->build->project_path : view_folder; + + auto compile = command.compile; + boost::replace_all(compile, "", filesystem::escape_argument(path.string())); + boost::replace_all(compile, "", filesystem::escape_argument(run_path.string())); + auto run = command.run; + boost::replace_all(run, "", filesystem::escape_argument(path.string())); + boost::replace_all(run, "", filesystem::escape_argument(run_path.string())); + + if(!compile.empty() || command.debug) { + if(Project::debugging && command.debug) // Possibly continue current debugging + break; + if(Project::compiling || Project::debugging) { + Info::get().print("Compile or debug in progress"); + break; + } + Project::current = project; + } + + if(Config::get().project.save_on_compile_or_run) + Project::save_files(run_path); + + if(Config::get().terminal.clear_on_run_command || (!compile.empty() && Config::get().terminal.clear_on_compile)) + Terminal::get().clear(); + + if(!compile.empty()) { + if(!project->build->get_default_path().empty()) + project->build->update_default(); + if(command.debug && !project->build->get_debug_path().empty()) + project->build->update_debug(); + + if(!command.debug) { + Project::compiling = true; + Terminal::get().print("\e[2mCompiling and running: " + run + "\e[m\n"); + Terminal::get().async_process(compile, run_path, [run, run_path](int exit_status) { + Project::compiling = false; + if(exit_status == 0) { + Terminal::get().async_process(run, run_path, [run](int exit_status) { + Terminal::get().print("\e[2m" + run + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); + }); + } + }); + } + else { // Debug + Project::debugging = true; + Terminal::get().print("\e[2mCompiling and debugging: " + run + "\e[m\n"); + Terminal::get().async_process(compile, run_path, [project = project->shared_from_this(), run, run_path, debug_remote_host = command.debug_remote_host](int exit_status) { + if(exit_status != EXIT_SUCCESS) + Project::debugging = false; + else + project->debug_start(run, run_path, debug_remote_host); + }); + } + } + else if(!command.debug) { + Terminal::get().async_print("\e[2mRunning: " + run + "\e[m\n"); + Terminal::get().async_process(run, run_path, [run](int exit_status) { + Terminal::get().print("\e[2m" + run + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); + }); + } + else { // Debug + Project::debugging = true; + Terminal::get().async_print("\e[2mDebugging: " + run + "\e[m\n"); + project->debug_start(run, run_path, command.debug_remote_host); + } + return true; + } + } + if(event->keyval == GDK_KEY_Escape) { EntryBox::get().hide(); } diff --git a/tests/filesystem_test.cpp b/tests/filesystem_test.cpp index 16e4059..ee9835e 100644 --- a/tests/filesystem_test.cpp +++ b/tests/filesystem_test.cpp @@ -105,6 +105,14 @@ int main() { g_assert(filesystem::get_relative_path("/test2/test.cc", "/test/base") == boost::filesystem::path("..") / ".." / "test2" / "test.cc"); } + { + g_assert(filesystem::get_absolute_path("./test1/test2", "/home") == boost::filesystem::path("/") / "home" / "." / "test1" / "test2"); + g_assert(filesystem::get_absolute_path("../test1/test2", "/home") == boost::filesystem::path("/") / "home" / ".." / "test1" / "test2"); + g_assert(filesystem::get_absolute_path("test1/test2", "/home") == boost::filesystem::path("/") / "home" / "test1" / "test2"); + g_assert(filesystem::get_absolute_path("/test1/test2", "/home") == boost::filesystem::path("/") / "test1" / "test2"); + g_assert(filesystem::get_absolute_path("~/test1/test2", "/home") == boost::filesystem::path("~") / "test1" / "test2"); + } + { boost::filesystem::path path = "/ro ot/te stæøå.txt"; auto uri = filesystem::get_uri_from_path(path); diff --git a/tests/stubs/project.cpp b/tests/stubs/project.cpp index 42079da..ac2183f 100644 --- a/tests/stubs/project.cpp +++ b/tests/stubs/project.cpp @@ -18,4 +18,6 @@ std::pair Project::Base::debug_get_run_arguments() { return std::make_pair("", ""); } -void Project::Base::debug_start() {} +void Project::Base::debug_compile_and_start() {} + +void Project::Base::debug_start(const std::string &command, const boost::filesystem::path &path, const std::string &remote_host) {} From 3b4ef42bd5682800dfda831416ba01a2cbe418ae Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 1 Jun 2021 11:02:34 +0200 Subject: [PATCH 108/375] Fixed compilation on MSYS2 --- src/window.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/window.cpp b/src/window.cpp index dc468fb..e80c66b 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -1908,7 +1908,9 @@ bool Window::on_key_press_event(GdkEventKey *event) { auto path = view ? view->file_path : view_folder; if(command.path) { std::smatch sm; - if(!std::regex_match(path.string(), sm, *command.path) && !std::regex_match(filesystem::get_short_path(path).string(), sm, *command.path)) + std::string path_str = path.string(); // Workaround for MSYS2 (g++ 9.1.0) + std::string short_path_str = filesystem::get_short_path(path).string(); // Workaround for MSYS2 (g++ 9.1.0) + if(!std::regex_match(path_str, sm, *command.path) && !std::regex_match(short_path_str, sm, *command.path)) continue; } From 51c619f4bd1f348c7b392e0268f399372db327fe Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 1 Jun 2021 16:36:13 +0200 Subject: [PATCH 109/375] Copying from diff buffer now omits + or - at start of lines --- src/source_base.cpp | 157 ++++++++++++++++++++++++++++++++++++-------- src/source_base.hpp | 23 ++++--- src/window.cpp | 49 +++----------- 3 files changed, 152 insertions(+), 77 deletions(-) diff --git a/src/source_base.cpp b/src/source_base.cpp index 0fd282c..97d5abb 100644 --- a/src/source_base.cpp +++ b/src/source_base.cpp @@ -10,7 +10,7 @@ #include #include -Source::SearchView::SearchView() : Gsv::View() { +Source::SearchView::SearchView(const Glib::RefPtr &language) : Gsv::View(), language(language) { 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); @@ -136,7 +136,130 @@ bool Source::SearchView::on_key_press_event(GdkEventKey *event) { return Gsv::View::on_key_press_event(event); } -Source::BaseView::BaseView(const boost::filesystem::path &file_path, const Glib::RefPtr &language) : SearchView(), file_path(file_path), language(language), status_diagnostics(0, 0, 0) { +void Source::SearchView::cut() { + if(!get_editable()) + return copy(); + + disable_spellcheck = true; + ScopeGuard guard{[this] { + disable_spellcheck = false; + }}; + + if(!get_buffer()->get_has_selection()) + cut_lines(); + else if(language && language->get_id() == "diff") { + Gtk::TextIter start, end; + get_buffer()->get_selection_bounds(start, end); + Glib::ustring selection; + selection.reserve(end.get_offset() - start.get_offset()); + for(auto iter = start; iter < end; ++iter) { + if(iter.starts_line() && *iter == '@') { + selection = get_buffer()->get_text(start, end); + break; + } + if(!(iter.starts_line() && (*iter == '-' || *iter == '+'))) + selection += *iter; + } + Gtk::Clipboard::get()->set_text(selection); + get_buffer()->erase(start, end); + } + else + get_buffer()->cut_clipboard(Gtk::Clipboard::get()); + keep_clipboard = true; +} + +void Source::SearchView::cut_lines() { + if(!get_editable()) + return copy_lines(); + + disable_spellcheck = true; + ScopeGuard guard{[this] { + disable_spellcheck = false; + }}; + + Gtk::TextIter start, end; + get_buffer()->get_selection_bounds(start, end); + start = get_buffer()->get_iter_at_line(start.get_line()); + + if(!end.ends_line()) + end.forward_to_line_end(); + end.forward_char(); + + if(language && language->get_id() == "diff") { + Glib::ustring selection; + selection.reserve(end.get_offset() - start.get_offset()); + for(auto iter = start; iter < end; ++iter) { + if(iter.starts_line() && *iter == '@') { + selection = get_buffer()->get_text(start, end); + break; + } + if(!(iter.starts_line() && (*iter == '-' || *iter == '+'))) + selection += *iter; + } + if(keep_clipboard) + Gtk::Clipboard::get()->set_text(Gtk::Clipboard::get()->wait_for_text() + selection); + else + Gtk::Clipboard::get()->set_text(selection); + } + else { + if(keep_clipboard) + Gtk::Clipboard::get()->set_text(Gtk::Clipboard::get()->wait_for_text() + get_buffer()->get_text(start, end)); + else + Gtk::Clipboard::get()->set_text(get_buffer()->get_text(start, end)); + } + get_buffer()->erase(start, end); + keep_clipboard = true; +} + +void Source::SearchView::copy() { + if(!get_buffer()->get_has_selection()) + copy_lines(); + else if(language && language->get_id() == "diff") { + Gtk::TextIter start, end; + get_buffer()->get_selection_bounds(start, end); + Glib::ustring selection; + selection.reserve(end.get_offset() - start.get_offset()); + for(auto iter = start; iter < end; ++iter) { + if(iter.starts_line() && *iter == '@') { + selection = get_buffer()->get_text(start, end); + break; + } + if(!(iter.starts_line() && (*iter == '-' || *iter == '+'))) + selection += *iter; + } + Gtk::Clipboard::get()->set_text(selection); + } + else + get_buffer()->copy_clipboard(Gtk::Clipboard::get()); +} + +void Source::SearchView::copy_lines() { + Gtk::TextIter start, end; + get_buffer()->get_selection_bounds(start, end); + start = get_buffer()->get_iter_at_line(start.get_line()); + + if(!end.ends_line()) + end.forward_to_line_end(); + end.forward_char(); + + if(language && language->get_id() == "diff") { + Glib::ustring selection; + selection.reserve(end.get_offset() - start.get_offset()); + for(auto iter = start; iter < end; ++iter) { + if(iter.starts_line() && *iter == '@') { + selection = get_buffer()->get_text(start, end); + break; + } + if(!(iter.starts_line() && (*iter == '-' || *iter == '+'))) + selection += *iter; + } + Gtk::Clipboard::get()->set_text(selection); + } + else + Gtk::Clipboard::get()->set_text(get_buffer()->get_text(start, end)); +} + +Source::BaseView::BaseView(const boost::filesystem::path &file_path, const Glib::RefPtr &language) : SearchView(language), file_path(file_path), status_diagnostics(0, 0, 0) { get_style_context()->add_class("juci_source_view"); load(true); @@ -766,35 +889,17 @@ void Source::BaseView::cleanup_whitespace_characters(const Gtk::TextIter &iter) get_buffer()->erase(start_blank_iter, end_blank_iter); } -void Source::BaseView::cut() { - if(!get_buffer()->get_has_selection()) - cut_line(); - else - get_buffer()->cut_clipboard(Gtk::Clipboard::get()); - keep_clipboard = true; -} - -void Source::BaseView::cut_line() { - Gtk::TextIter start, end; - get_buffer()->get_selection_bounds(start, end); - start = get_buffer()->get_iter_at_line(start.get_line()); - if(!end.ends_line()) - end.forward_to_line_end(); - end.forward_char(); - if(keep_clipboard) - Gtk::Clipboard::get()->set_text(Gtk::Clipboard::get()->wait_for_text() + get_buffer()->get_text(start, end)); - else - Gtk::Clipboard::get()->set_text(get_buffer()->get_text(start, end)); - get_buffer()->erase(start, end); - keep_clipboard = true; -} - void Source::BaseView::paste() { + disable_spellcheck = true; + ScopeGuard spellcheck_guard{[this] { + disable_spellcheck = false; + }}; + if(CompletionDialog::get()) CompletionDialog::get()->hide(); enable_multiple_cursors = true; - ScopeGuard guard{[this] { + ScopeGuard multiple_cursors_guard{[this] { enable_multiple_cursors = false; }}; diff --git a/src/source_base.hpp b/src/source_base.hpp index 5dfa9d5..0d24a03 100644 --- a/src/source_base.hpp +++ b/src/source_base.hpp @@ -29,7 +29,7 @@ namespace Source { class SearchView : public Gsv::View { public: - SearchView(); + SearchView(const Glib::RefPtr &language = {}); ~SearchView() override; void search_highlight(const std::string &text, bool case_sensitive, bool regex); void search_forward(); @@ -38,6 +38,18 @@ namespace Source { void replace_backward(const std::string &replacement); void replace_all(const std::string &replacement); + Glib::RefPtr language; + + protected: + bool keep_clipboard = false; + + public: + void cut(); + void cut_lines(); + void copy(); + void copy_lines(); + bool disable_spellcheck = false; + std::function update_search_occurrences; protected: @@ -55,8 +67,6 @@ namespace Source { ~BaseView() override; boost::filesystem::path file_path; - Glib::RefPtr 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); @@ -102,19 +112,12 @@ namespace Source { std::function update_status_branch; std::string status_branch; - void cut(); - void cut_line(); void paste(); std::string get_selected_text(); - bool disable_spellcheck = false; - void set_snippets(); - private: - bool keep_clipboard = false; - protected: boost::optional last_write_time; void monitor_file(); diff --git a/src/window.cpp b/src/window.cpp index e80c66b..f805405 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -632,15 +632,8 @@ void Window::set_menu_actions() { else entry->cut_clipboard(); } - else if(auto view = dynamic_cast(widget)) { - if(!view->get_editable()) - return; - if(auto source_view = dynamic_cast(view)) { - source_view->disable_spellcheck = true; - source_view->cut(); - source_view->disable_spellcheck = false; - } - } + else if(auto view = dynamic_cast(widget)) + view->cut(); }); menu.add_action("edit_cut_lines", [this]() { // Return if a shown tooltip has selected text @@ -655,15 +648,8 @@ void Window::set_menu_actions() { Gtk::Clipboard::get()->set_text(entry->get_text()); entry->set_text(""); } - else if(auto view = dynamic_cast(widget)) { - if(!view->get_editable()) - return; - if(auto source_view = dynamic_cast(view)) { - source_view->disable_spellcheck = true; - source_view->cut_line(); - source_view->disable_spellcheck = false; - } - } + else if(auto view = dynamic_cast(widget)) + view->cut_lines(); }); menu.add_action("edit_copy", [this]() { // Copy from a tooltip if it has selected text @@ -683,18 +669,8 @@ void Window::set_menu_actions() { else entry->copy_clipboard(); } - else if(auto view = dynamic_cast(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(); - Gtk::Clipboard::get()->set_text(view->get_buffer()->get_text(start, end)); - } - else - view->get_buffer()->copy_clipboard(Gtk::Clipboard::get()); - } + else if(auto view = dynamic_cast(widget)) + view->copy(); }); menu.add_action("edit_copy_lines", [this]() { // Copy from a tooltip if it has selected text @@ -715,15 +691,8 @@ void Window::set_menu_actions() { auto widget = get_focus(); if(auto entry = dynamic_cast(widget)) Gtk::Clipboard::get()->set_text(entry->get_text()); - else if(auto view = dynamic_cast(widget)) { - Gtk::TextIter start, end; - view->get_buffer()->get_selection_bounds(start, end); - start = view->get_buffer()->get_iter_at_line(start.get_line()); - if(!end.ends_line()) - end.forward_to_line_end(); - end.forward_char(); - Gtk::Clipboard::get()->set_text(view->get_buffer()->get_text(start, end)); - } + else if(auto view = dynamic_cast(widget)) + view->copy_lines(); }); menu.add_action("edit_paste", [this]() { auto widget = get_focus(); @@ -731,9 +700,7 @@ void Window::set_menu_actions() { entry->paste_clipboard(); else if(auto view = dynamic_cast(widget)) { if(auto source_view = dynamic_cast(view)) { - source_view->disable_spellcheck = true; source_view->paste(); - source_view->disable_spellcheck = false; source_view->hide_tooltips(); } else if(auto terminal = dynamic_cast(view)) { From cb2bc83044ff6ac2c0f0b5406b972a9515cd6669 Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 2 Jun 2021 11:18:56 +0200 Subject: [PATCH 110/375] Extend selection now works on HTML and JSX --- src/source.cpp | 248 +++++++++++++++++++++++++++++++++++++++--- src/source.hpp | 2 + tests/source_test.cpp | 128 ++++++++++++++++++++++ 3 files changed, 362 insertions(+), 16 deletions(-) diff --git a/src/source.cpp b/src/source.cpp index 96dacb5..54d99c4 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -176,6 +176,8 @@ Source::View::View(const boost::filesystem::path &file_path, const Glib::RefPtr< is_c = true; else if(language_id == "cpphdr" || language_id == "cpp") is_cpp = true; + else if(language_id == "js" || language_id == "html") + is_js = true; if(is_c || is_cpp) { use_fixed_continuation_indenting = false; @@ -1254,7 +1256,8 @@ void Source::View::extend_selection() { (*before_start == '<' && *end == '>') || (*before_start == '{' && *end == '}')) { // Select expression from selected brackets - if(extend_expression(start, end)) { + if(!(*before_start == '<' && *end == '>' && is_js) && + extend_expression(start, end)) { get_buffer()->select_range(start, end); return; } @@ -1282,9 +1285,11 @@ void Source::View::extend_selection() { int para_count = 0; int square_count = 0; + int angle_count = 0; int curly_count = 0; auto start_comma_iter = get_buffer()->end(); auto start_angle_iter = get_buffer()->end(); + auto start_angle_reversed_iter = get_buffer()->end(); while(start.backward_char()) { if(*start == '(' && is_code_iter(start)) para_count++; @@ -1294,6 +1299,19 @@ void Source::View::extend_selection() { square_count++; else if(*start == ']' && is_code_iter(start)) square_count--; + else if(*start == '<' && is_code_iter(start)) { + if(!start_angle_iter && para_count == 0 && square_count == 0 && angle_count == 0 && curly_count == 0) + start_angle_iter = start; + angle_count++; + } + else if(*start == '>' && is_code_iter(start)) { + auto prev = start; + if(!(prev.backward_char() && (*prev == '=' || *prev == '-'))) { + if(!start_angle_reversed_iter && para_count == 0 && square_count == 0 && angle_count == 0 && curly_count == 0) + start_angle_reversed_iter = start; + angle_count--; + } + } else if(*start == '{' && is_code_iter(start)) { if(!start_sentence_iter && para_count == 0 && square_count == 0 && curly_count == 0) { @@ -1318,10 +1336,6 @@ void Source::View::extend_selection() { para_count == 0 && square_count == 0 && curly_count == 0 && *start == ';' && is_code_iter(start)) start_sentence_iter = start; - else if(!start_angle_iter && - para_count == 0 && square_count == 0 && curly_count == 0 && - *start == '<' && is_code_iter(start)) - start_angle_iter = start; if(*start == ';' && is_code_iter(start)) { ignore_comma = true; start_comma_iter = get_buffer()->end(); @@ -1332,9 +1346,11 @@ void Source::View::extend_selection() { para_count = 0; square_count = 0; + angle_count = 0; curly_count = 0; auto end_comma_iter = get_buffer()->end(); auto end_angle_iter = get_buffer()->end(); + auto end_angle_reversed_iter = get_buffer()->end(); do { if(*end == '(' && is_code_iter(end)) para_count++; @@ -1344,6 +1360,19 @@ void Source::View::extend_selection() { square_count++; else if(*end == ']' && is_code_iter(end)) square_count--; + else if(*end == '<' && is_code_iter(end)) { + if(!end_angle_reversed_iter && para_count == 0 && square_count == 0 && angle_count == 0 && curly_count == 0) + end_angle_reversed_iter = end; + angle_count++; + } + else if(*end == '>' && is_code_iter(end)) { + auto prev = end; + if(!(prev.backward_char() && (*prev == '=' || *prev == '-'))) { + if(!end_angle_iter && para_count == 0 && square_count == 0 && angle_count == 0 && curly_count == 0) + end_angle_iter = end; + angle_count--; + } + } else if(*end == '{' && is_code_iter(end)) curly_count++; else if(*end == '}' && is_code_iter(end)) { @@ -1353,6 +1382,8 @@ void Source::View::extend_selection() { auto next = end_sentence_iter = end; if(next.forward_char() && forward_to_code(next) && *next == ';') end_sentence_iter = next; + else if(is_js && *next == '>') + end_sentence_iter = get_buffer()->end(); } } else if(!ignore_comma && !end_comma_iter && @@ -1363,10 +1394,6 @@ void Source::View::extend_selection() { para_count == 0 && square_count == 0 && curly_count == 0 && *end == ';' && is_code_iter(end)) end_sentence_iter = end; - else if(!end_angle_iter && - para_count == 0 && square_count == 0 && curly_count == 0 && - *end == '>' && is_code_iter(end)) - end_angle_iter = end; if(*end == ';' && is_code_iter(end)) { ignore_comma = true; start_comma_iter = get_buffer()->end(); @@ -1376,10 +1403,156 @@ void Source::View::extend_selection() { break; } while(end.forward_char()); + // Extend HTML/JSX + if(is_js) { + static std::vector void_elements = {"area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"}; + auto get_element = [this](Gtk::TextIter iter) { + auto start = iter; + auto end = iter; + while(iter.backward_char() && (is_token_char(*iter) || *iter == '.')) + start = iter; + while((is_token_char(*end) || *end == '.') && end.forward_char()) { + } + return get_buffer()->get_text(start, end); + }; + + Gtk::TextIter start = get_buffer()->end(), end = get_buffer()->end(); + auto start_stored_prev = start_stored; + if(start_angle_iter && end_angle_iter) { // If inside angle brackets in for instance + auto next = start_angle_iter; + next.forward_char(); + auto prev = end_angle_iter; + prev.backward_char(); + auto element = get_element(next); + if(*next == '/') { + start = end = start_angle_iter; + start.backward_char(); + } + else if(*prev == '/' || std::any_of(void_elements.begin(), void_elements.end(), [&element](const std::string &e) { return e == element; })) { + end_angle_iter.forward_char(); + get_buffer()->select_range(start_angle_iter, end_angle_iter); + return; + } + else { + start = end = end_angle_iter; + end.forward_char(); + } + } + else if(start_stored_prev.backward_char() && *start_stored_prev == '<' && *end_stored == '>') { // Matches for instance
, where div is selected + auto element = get_element(start_stored); + if(std::any_of(void_elements.begin(), void_elements.end(), [&element](const std::string &e) { return e == element; })) { + auto next = end_stored; + next.forward_char(); + get_buffer()->select_range(start_stored_prev, next); + return; + } + start = end = end_stored; + end.forward_char(); + } + else if(start_angle_reversed_iter && end_angle_reversed_iter) { // If not inside angle brackets, for instance <>selection here + start = start_angle_reversed_iter; + end = end_angle_reversed_iter; + } + + if(start && end) { + Gtk::TextIter start_children, end_children; + auto iter = start; + int depth = 0; + // Search backward for opening element + while(find_open_symbol_backward(iter, iter, '>', '<') && iter.backward_char()) { // Backward to > (end of opening element) + start_children = iter; + start_children.forward_chars(2); + auto prev = iter; + prev.backward_char(); + bool no_child_element = *iter == '/' && *prev != '<'; // Excludes as it is always closing element of <> + if(find_open_symbol_backward(iter, iter, '<', '>')) { // Backward to < + if(!no_child_element) { + iter.forward_char(); + if(*iter == '/') { + --depth; + if(!iter.backward_chars(2)) + break; + continue; + } + auto element = get_element(iter); + if(std::any_of(void_elements.begin(), void_elements.end(), [&element](const std::string &e) { return e == element; })) { + if(!iter.backward_chars(2)) + break; + continue; + } + else if(depth == 0) { + iter.backward_char(); + auto start = iter; + iter = end; + // Search forward for closing element + int depth = 0; + while(find_close_symbol_forward(iter, iter, '>', '<') && iter.forward_char()) { // Forward to < (start of closing element) + end_children = iter; + end_children.backward_char(); + if(*iter == '/') { + if(iter.forward_char() && find_close_symbol_forward(iter, iter, '<', '>') && iter.forward_char()) { // Forward to > + if(depth == 0) { + if(start_children <= start_stored && end_children >= end_stored && (start_children != start_stored || end_children != end_stored)) // Select children + get_buffer()->select_range(start_children, end_children); + else + get_buffer()->select_range(start, iter); + return; + } + else { + --depth; + continue; + } + } + else + break; + } + else { + auto element = get_element(iter); + if(std::any_of(void_elements.begin(), void_elements.end(), [&element](const std::string &e) { return e == element; })) { + if(!(find_close_symbol_forward(iter, iter, '<', '>') && iter.forward_char())) // Forward to > + break; + continue; + } + else if(find_close_symbol_forward(iter, iter, '<', '>') && iter.backward_char()) { // Forward to > + if(*iter == '/') { + iter.forward_chars(2); + continue; + } + else { + ++depth; + iter.forward_chars(2); + continue; + } + } + else + break; + } + } + break; + } + else { + ++depth; + if(!iter.backward_chars(2)) + break; + continue; + } + } + else { + if(!iter.backward_char()) + break; + } + } + else + break; + } + } + } + // Test for <> used for template arguments if(start_angle_iter && end_angle_iter && is_template_arguments(start_angle_iter, end_angle_iter)) { - start = start_angle_iter; - end = end_angle_iter; + start_angle_iter.forward_char(); + get_buffer()->select_range(start_angle_iter, end_angle_iter); + return; } // Test for matching brackets and try select regions within brackets separated by ',' @@ -1564,8 +1737,9 @@ void Source::View::extend_selection() { if(start == start_stored && end == end_stored) { // In case of no change due to inbalanced brackets previous_extended_selections.pop_back(); - if(!start.backward_char() && !end.forward_char()) + if(!start.backward_char() && !end.forward_char()) { return; + } get_buffer()->select_range(start, end); extend_selection(); return; @@ -1865,9 +2039,30 @@ bool Source::View::find_close_symbol_forward(Gtk::TextIter iter, Gtk::TextIter & else { long curly_count = 0; do { - if(curly_count == 0 && *iter == positive_char && is_code_iter(iter)) + if(curly_count == 0 && *iter == positive_char && is_code_iter(iter)) { + if((is_c || is_cpp) && positive_char == '>') { + auto prev = iter; + if(prev.backward_char() && *prev == '-') + continue; + } + else if(is_js && positive_char == '>') { + auto prev = iter; + if(prev.backward_char() && *prev == '=') + continue; + } count++; + } else if(curly_count == 0 && *iter == negative_char && is_code_iter(iter)) { + if((is_c || is_cpp) && negative_char == '>') { + auto prev = iter; + if(prev.backward_char() && *prev == '-') + continue; + } + else if(is_js && negative_char == '>') { + auto prev = iter; + if(prev.backward_char() && *prev == '=') + continue; + } if(count == 0) { found_iter = iter; return true; @@ -1906,14 +2101,35 @@ bool Source::View::find_open_symbol_backward(Gtk::TextIter iter, Gtk::TextIter & long curly_count = 0; do { if(curly_count == 0 && *iter == positive_char && is_code_iter(iter)) { + if((is_c || is_cpp) && positive_char == '>') { + auto prev = iter; + if(prev.backward_char() && *prev == '-') + continue; + } + else if(is_js && positive_char == '>') { + auto prev = iter; + if(prev.backward_char() && *prev == '=') + continue; + } if(count == 0) { found_iter = iter; return true; } count++; } - else if(curly_count == 0 && *iter == negative_char && is_code_iter(iter)) + else if(curly_count == 0 && *iter == negative_char && is_code_iter(iter)) { + if((is_c || is_cpp) && negative_char == '>') { + auto prev = iter; + if(prev.backward_char() && *prev == '-') + continue; + } + else if(is_js && negative_char == '>') { + auto prev = iter; + if(prev.backward_char() && *prev == '=') + continue; + } count--; + } else if(*iter == '{' && is_code_iter(iter)) { if(curly_count == 0) return false; @@ -2435,7 +2651,7 @@ bool Source::View::on_key_press_event_bracket_language(GdkEventKey *event) { // to //
// CURSORtest - if(*condition_iter == '>' && language && (language->get_id() == "js" || language->get_id() == "html")) { + if(*condition_iter == '>' && is_js) { auto prev = condition_iter; Gtk::TextIter open_element_iter; if(prev.backward_char() && backward_to_code(prev) && *prev != '/' && @@ -2451,7 +2667,7 @@ bool Source::View::on_key_press_event_bracket_language(GdkEventKey *event) { return get_buffer()->get_text(start, end); }; auto open_element_token = get_element(open_element_iter); - std::vector void_elements = {"area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"}; + static std::vector void_elements = {"area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"}; if(std::none_of(void_elements.begin(), void_elements.end(), [&open_element_token](const std::string &e) { return e == open_element_token; })) { auto close_element_iter = iter; // If cursor is placed between open and close tag diff --git a/src/source.hpp b/src/source.hpp index 6496847..a9d2ab9 100644 --- a/src/source.hpp +++ b/src/source.hpp @@ -176,6 +176,8 @@ namespace Source { bool is_c = false; bool is_cpp = false; + /// Set to true if language is html or js (including typescript) + bool is_js = false; private: void setup_signals(); diff --git a/tests/source_test.cpp b/tests/source_test.cpp index 10356a6..d8b2be0 100644 --- a/tests/source_test.cpp +++ b/tests/source_test.cpp @@ -360,6 +360,134 @@ int main() { view.shrink_selection(); g_assert(view.get_selected_text() == "{\n test;\n }"); } + + { + auto buffer = view.get_buffer(); + view.is_js = true; + Gtk::TextIter start, end; + + buffer->set_text(R"(render( +
+);)"); + view.place_cursor_at_line_offset(1, 7); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 1 && start.get_line_offset() == 2 && end.get_line() == 1 && end.get_line_offset() == 13); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 0 && start.get_line_offset() == 7 && end.get_line() == 2 && end.get_line_offset() == 0); + + view.place_cursor_at_line_offset(1, 4); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 1 && start.get_line_offset() == 3 && end.get_line() == 1 && end.get_line_offset() == 6); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 1 && start.get_line_offset() == 2 && end.get_line() == 1 && end.get_line_offset() == 13); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 0 && start.get_line_offset() == 7 && end.get_line() == 2 && end.get_line_offset() == 0); + + buffer->set_text(R"(render( +
test test
+);)"); + view.place_cursor_at_line_offset(1, 18); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 1 && start.get_line_offset() == 17 && end.get_line() == 1 && end.get_line_offset() == 21); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 1 && start.get_line_offset() == 17 && end.get_line() == 1 && end.get_line_offset() == 26); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 1 && start.get_line_offset() == 2 && end.get_line() == 1 && end.get_line_offset() == 32); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 0 && start.get_line_offset() == 7 && end.get_line() == 2 && end.get_line_offset() == 0); + + view.place_cursor_at_line_offset(1, 4); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 1 && start.get_line_offset() == 3 && end.get_line() == 1 && end.get_line_offset() == 6); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 1 && start.get_line_offset() == 2 && end.get_line() == 1 && end.get_line_offset() == 32); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 0 && start.get_line_offset() == 7 && end.get_line() == 2 && end.get_line_offset() == 0); + + buffer->set_text(R"(render( +
{}}>
+);)"); + view.place_cursor_at_line_offset(1, 6); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 1 && start.get_line_offset() == 2 && end.get_line() == 1 && end.get_line_offset() == 32); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 0 && start.get_line_offset() == 7 && end.get_line() == 2 && end.get_line_offset() == 0); + + buffer->set_text(R"(render( +
+
+
+
+);)"); + view.place_cursor_at_line_offset(2, 8); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 2 && start.get_line_offset() == 4 && end.get_line() == 2 && end.get_line_offset() == 15); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 1 && start.get_line_offset() == 7 && end.get_line() == 4 && end.get_line_offset() == 2); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 1 && start.get_line_offset() == 2 && end.get_line() == 4 && end.get_line_offset() == 8); + + view.place_cursor_at_line_offset(3, 8); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 3 && start.get_line_offset() == 4 && end.get_line() == 3 && end.get_line_offset() == 15); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 1 && start.get_line_offset() == 7 && end.get_line() == 4 && end.get_line_offset() == 2); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 1 && start.get_line_offset() == 2 && end.get_line() == 4 && end.get_line_offset() == 8); + + buffer->set_text(R"(render( +
+
+ + +
+
+
+);)"); + view.place_cursor_at_line_offset(2, 8); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 2 && start.get_line_offset() == 4 && end.get_line() == 2 && end.get_line_offset() == 15); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 1 && start.get_line_offset() == 7 && end.get_line() == 7 && end.get_line_offset() == 2); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 1 && start.get_line_offset() == 2 && end.get_line() == 7 && end.get_line_offset() == 8); + + view.place_cursor_at_line_offset(6, 8); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 6 && start.get_line_offset() == 4 && end.get_line() == 6 && end.get_line_offset() == 15); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 1 && start.get_line_offset() == 7 && end.get_line() == 7 && end.get_line_offset() == 2); + view.extend_selection(); + buffer->get_selection_bounds(start, end); + g_assert(start.get_line() == 1 && start.get_line_offset() == 2 && end.get_line() == 7 && end.get_line_offset() == 8); + + view.is_js = false; + } } // Snippet tests From 50299e7b08c17f7271ada5387fd587dbf3a9669e Mon Sep 17 00:00:00 2001 From: eidheim Date: Sat, 5 Jun 2021 08:12:14 +0200 Subject: [PATCH 111/375] Renamed SearchView to CommonView --- src/source_base.cpp | 32 ++++++++++++++++---------------- src/source_base.hpp | 9 +++++---- src/terminal.cpp | 4 ++-- src/terminal.hpp | 2 +- src/window.cpp | 8 ++++---- src/window.hpp | 2 +- 6 files changed, 29 insertions(+), 28 deletions(-) diff --git a/src/source_base.cpp b/src/source_base.cpp index 97d5abb..29759c4 100644 --- a/src/source_base.cpp +++ b/src/source_base.cpp @@ -10,7 +10,7 @@ #include #include -Source::SearchView::SearchView(const Glib::RefPtr &language) : Gsv::View(), language(language) { +Source::CommonView::CommonView(const Glib::RefPtr &language) : Gsv::View(), language(language) { 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); @@ -18,19 +18,19 @@ Source::SearchView::SearchView(const Glib::RefPtr &language) : Gs g_signal_connect(search_context, "notify::occurrences-count", G_CALLBACK(search_occurrences_updated), this); } -Source::SearchView::~SearchView() { +Source::CommonView::~CommonView() { g_clear_object(&search_context); g_clear_object(&search_settings); } -void Source::SearchView::search_highlight(const std::string &text, bool case_sensitive, bool regex) { +void Source::CommonView::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); } -void Source::SearchView::search_forward() { +void Source::CommonView::search_forward() { Gtk::TextIter start, end; get_buffer()->get_selection_bounds(start, end); Gtk::TextIter match_start, match_end; @@ -48,7 +48,7 @@ void Source::SearchView::search_forward() { #endif } -void Source::SearchView::search_backward() { +void Source::CommonView::search_backward() { Gtk::TextIter start, end; get_buffer()->get_selection_bounds(start, end); Gtk::TextIter match_start, match_end; @@ -66,7 +66,7 @@ void Source::SearchView::search_backward() { #endif } -void Source::SearchView::replace_forward(const std::string &replacement) { +void Source::CommonView::replace_forward(const std::string &replacement) { Gtk::TextIter start, end; get_buffer()->get_selection_bounds(start, end); Gtk::TextIter match_start, match_end; @@ -90,7 +90,7 @@ void Source::SearchView::replace_forward(const std::string &replacement) { #endif } -void Source::SearchView::replace_backward(const std::string &replacement) { +void Source::CommonView::replace_backward(const std::string &replacement) { Gtk::TextIter start, end; get_buffer()->get_selection_bounds(start, end); Gtk::TextIter match_start, match_end; @@ -112,17 +112,17 @@ void Source::SearchView::replace_backward(const std::string &replacement) { #endif } -void Source::SearchView::replace_all(const std::string &replacement) { +void Source::CommonView::replace_all(const std::string &replacement) { gtk_source_search_context_replace_all(search_context, replacement.c_str(), replacement.size(), nullptr); } -void Source::SearchView::search_occurrences_updated(GtkWidget *widget, GParamSpec *property, gpointer data) { +void Source::CommonView::search_occurrences_updated(GtkWidget *widget, GParamSpec *property, gpointer data) { auto view = static_cast(data); if(view->update_search_occurrences) view->update_search_occurrences(gtk_source_search_context_get_occurrences_count(view->search_context)); } -bool Source::SearchView::on_key_press_event(GdkEventKey *event) { +bool Source::CommonView::on_key_press_event(GdkEventKey *event) { if(event->keyval == GDK_KEY_Home && event->state & GDK_CONTROL_MASK) { auto iter = get_buffer()->begin(); scroll_to(iter); // Home key should always scroll to start, even though cursor does not move @@ -136,7 +136,7 @@ bool Source::SearchView::on_key_press_event(GdkEventKey *event) { return Gsv::View::on_key_press_event(event); } -void Source::SearchView::cut() { +void Source::CommonView::cut() { if(!get_editable()) return copy(); @@ -168,7 +168,7 @@ void Source::SearchView::cut() { keep_clipboard = true; } -void Source::SearchView::cut_lines() { +void Source::CommonView::cut_lines() { if(!get_editable()) return copy_lines(); @@ -211,7 +211,7 @@ void Source::SearchView::cut_lines() { keep_clipboard = true; } -void Source::SearchView::copy() { +void Source::CommonView::copy() { if(!get_buffer()->get_has_selection()) copy_lines(); else if(language && language->get_id() == "diff") { @@ -233,7 +233,7 @@ void Source::SearchView::copy() { get_buffer()->copy_clipboard(Gtk::Clipboard::get()); } -void Source::SearchView::copy_lines() { +void Source::CommonView::copy_lines() { Gtk::TextIter start, end; get_buffer()->get_selection_bounds(start, end); start = get_buffer()->get_iter_at_line(start.get_line()); @@ -259,7 +259,7 @@ void Source::SearchView::copy_lines() { Gtk::Clipboard::get()->set_text(get_buffer()->get_text(start, end)); } -Source::BaseView::BaseView(const boost::filesystem::path &file_path, const Glib::RefPtr &language) : SearchView(language), file_path(file_path), status_diagnostics(0, 0, 0) { +Source::BaseView::BaseView(const boost::filesystem::path &file_path, const Glib::RefPtr &language) : CommonView(language), file_path(file_path), status_diagnostics(0, 0, 0) { get_style_context()->add_class("juci_source_view"); load(true); @@ -1187,7 +1187,7 @@ bool Source::BaseView::on_key_press_event(GdkEventKey *event) { return true; } - return Source::SearchView::on_key_press_event(event); + return Source::CommonView::on_key_press_event(event); } bool Source::BaseView::on_key_press_event_extra_cursors(GdkEventKey *event) { diff --git a/src/source_base.hpp b/src/source_base.hpp index 0d24a03..864b93b 100644 --- a/src/source_base.hpp +++ b/src/source_base.hpp @@ -27,10 +27,11 @@ namespace Source { } }; - class SearchView : public Gsv::View { + /// Also used for terminal + class CommonView : public Gsv::View { public: - SearchView(const Glib::RefPtr &language = {}); - ~SearchView() override; + CommonView(const Glib::RefPtr &language = {}); + ~CommonView() override; void search_highlight(const std::string &text, bool case_sensitive, bool regex); void search_forward(); void search_backward(); @@ -61,7 +62,7 @@ namespace Source { static void search_occurrences_updated(GtkWidget *widget, GParamSpec *property, gpointer data); }; - class BaseView : public SearchView { + class BaseView : public CommonView { public: BaseView(const boost::filesystem::path &file_path, const Glib::RefPtr &language); ~BaseView() override; diff --git a/src/terminal.cpp b/src/terminal.cpp index 5a99e3e..687c753 100644 --- a/src/terminal.cpp +++ b/src/terminal.cpp @@ -10,7 +10,7 @@ #include #include -Terminal::Terminal() : Source::SearchView() { +Terminal::Terminal() : Source::CommonView() { get_style_context()->add_class("juci_terminal"); set_editable(false); @@ -628,7 +628,7 @@ bool Terminal::on_key_press_event(GdkEventKey *event) { event->keyval == GDK_KEY_Page_Up || event->keyval == GDK_KEY_Page_Down || event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_Down || event->keyval == GDK_KEY_Left || event->keyval == GDK_KEY_Right) - return Source::SearchView::on_key_press_event(event); + return Source::CommonView::on_key_press_event(event); LockGuard lock(processes_mutex); bool debug_is_running = false; diff --git a/src/terminal.hpp b/src/terminal.hpp index 726ca05..100046f 100644 --- a/src/terminal.hpp +++ b/src/terminal.hpp @@ -10,7 +10,7 @@ #include #include -class Terminal : public Source::SearchView { +class Terminal : public Source::CommonView { Terminal(); public: diff --git a/src/window.cpp b/src/window.cpp index f805405..562e0be 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -632,7 +632,7 @@ void Window::set_menu_actions() { else entry->cut_clipboard(); } - else if(auto view = dynamic_cast(widget)) + else if(auto view = dynamic_cast(widget)) view->cut(); }); menu.add_action("edit_cut_lines", [this]() { @@ -648,7 +648,7 @@ void Window::set_menu_actions() { Gtk::Clipboard::get()->set_text(entry->get_text()); entry->set_text(""); } - else if(auto view = dynamic_cast(widget)) + else if(auto view = dynamic_cast(widget)) view->cut_lines(); }); menu.add_action("edit_copy", [this]() { @@ -669,7 +669,7 @@ void Window::set_menu_actions() { else entry->copy_clipboard(); } - else if(auto view = dynamic_cast(widget)) + else if(auto view = dynamic_cast(widget)) view->copy(); }); menu.add_action("edit_copy_lines", [this]() { @@ -691,7 +691,7 @@ void Window::set_menu_actions() { auto widget = get_focus(); if(auto entry = dynamic_cast(widget)) Gtk::Clipboard::get()->set_text(entry->get_text()); - else if(auto view = dynamic_cast(widget)) + else if(auto view = dynamic_cast(widget)) view->copy_lines(); }); menu.add_action("edit_paste", [this]() { diff --git a/src/window.hpp b/src/window.hpp index dea21bd..7a58199 100644 --- a/src/window.hpp +++ b/src/window.hpp @@ -40,7 +40,7 @@ private: bool regex_search = false; bool search_entry_shown = false; /// Last source view focused - Source::SearchView *focused_view = nullptr; + Source::CommonView *focused_view = nullptr; bool find_pattern_case_sensitive = true; bool find_pattern_extended_regex = false; }; From a0dfe0a055ffb235ee9df942eed74bbe5c970517 Mon Sep 17 00:00:00 2001 From: eidheim Date: Sun, 6 Jun 2021 12:19:37 +0200 Subject: [PATCH 112/375] Cleanup of extend_selection() --- src/source.cpp | 53 ++++++++++++++++++++------------------------------ 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/src/source.cpp b/src/source.cpp index 54d99c4..c1de7fd 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -1416,7 +1416,7 @@ void Source::View::extend_selection() { return get_buffer()->get_text(start, end); }; - Gtk::TextIter start = get_buffer()->end(), end = get_buffer()->end(); + Gtk::TextIter start_backward_search = get_buffer()->end(), start_forward_search = get_buffer()->end(); auto start_stored_prev = start_stored; if(start_angle_iter && end_angle_iter) { // If inside angle brackets in for instance auto next = start_angle_iter; @@ -1425,8 +1425,8 @@ void Source::View::extend_selection() { prev.backward_char(); auto element = get_element(next); if(*next == '/') { - start = end = start_angle_iter; - start.backward_char(); + start_backward_search = start_forward_search = start_angle_iter; + start_backward_search.backward_char(); } else if(*prev == '/' || std::any_of(void_elements.begin(), void_elements.end(), [&element](const std::string &e) { return e == element; })) { end_angle_iter.forward_char(); @@ -1434,8 +1434,8 @@ void Source::View::extend_selection() { return; } else { - start = end = end_angle_iter; - end.forward_char(); + start_backward_search = start_forward_search = end_angle_iter; + start_forward_search.forward_char(); } } else if(start_stored_prev.backward_char() && *start_stored_prev == '<' && *end_stored == '>') { // Matches for instance
, where div is selected @@ -1446,17 +1446,17 @@ void Source::View::extend_selection() { get_buffer()->select_range(start_stored_prev, next); return; } - start = end = end_stored; - end.forward_char(); + start_backward_search = start_forward_search = end_stored; + start_forward_search.forward_char(); } else if(start_angle_reversed_iter && end_angle_reversed_iter) { // If not inside angle brackets, for instance <>selection here - start = start_angle_reversed_iter; - end = end_angle_reversed_iter; + start_backward_search = start_angle_reversed_iter; + start_forward_search = end_angle_reversed_iter; } - if(start && end) { + if(start_backward_search && start_forward_search) { Gtk::TextIter start_children, end_children; - auto iter = start; + auto iter = start_backward_search; int depth = 0; // Search backward for opening element while(find_open_symbol_backward(iter, iter, '>', '<') && iter.backward_char()) { // Backward to > (end of opening element) @@ -1469,9 +1469,9 @@ void Source::View::extend_selection() { if(!no_child_element) { iter.forward_char(); if(*iter == '/') { - --depth; if(!iter.backward_chars(2)) break; + --depth; continue; } auto element = get_element(iter); @@ -1482,8 +1482,8 @@ void Source::View::extend_selection() { } else if(depth == 0) { iter.backward_char(); - auto start = iter; - iter = end; + auto start_selection = iter; + iter = start_forward_search; // Search forward for closing element int depth = 0; while(find_close_symbol_forward(iter, iter, '>', '<') && iter.forward_char()) { // Forward to < (start of closing element) @@ -1495,13 +1495,11 @@ void Source::View::extend_selection() { if(start_children <= start_stored && end_children >= end_stored && (start_children != start_stored || end_children != end_stored)) // Select children get_buffer()->select_range(start_children, end_children); else - get_buffer()->select_range(start, iter); + get_buffer()->select_range(start_selection, iter); return; } - else { + else --depth; - continue; - } } else break; @@ -1511,17 +1509,13 @@ void Source::View::extend_selection() { if(std::any_of(void_elements.begin(), void_elements.end(), [&element](const std::string &e) { return e == element; })) { if(!(find_close_symbol_forward(iter, iter, '<', '>') && iter.forward_char())) // Forward to > break; - continue; } else if(find_close_symbol_forward(iter, iter, '<', '>') && iter.backward_char()) { // Forward to > - if(*iter == '/') { + if(*iter == '/') iter.forward_chars(2); - continue; - } else { ++depth; iter.forward_chars(2); - continue; } } else @@ -1530,17 +1524,12 @@ void Source::View::extend_selection() { } break; } - else { - ++depth; - if(!iter.backward_chars(2)) - break; - continue; - } - } - else { - if(!iter.backward_char()) + else if(!iter.backward_chars(2)) break; + ++depth; } + else if(!iter.backward_char()) + break; } else break; From de43494f253ed77261450ad594933490e6e47124 Mon Sep 17 00:00:00 2001 From: eidheim Date: Sun, 6 Jun 2021 16:31:11 +0200 Subject: [PATCH 113/375] MacOS: fixed selection marks when selecting text through mouse drag --- src/source.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/source.cpp b/src/source.cpp index c1de7fd..88a698e 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -3529,7 +3529,7 @@ bool Source::View::on_motion_notify_event(GdkEventMotion *event) { 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); + get_buffer()->select_range(iter, get_buffer()->get_selection_bound()->get_iter()); return true; } #else From 9c8f105780df9f4cdc217607ef9f18088caa0242 Mon Sep 17 00:00:00 2001 From: eidheim Date: Mon, 7 Jun 2021 10:19:45 +0200 Subject: [PATCH 114/375] Added support for Go projects --- README.md | 2 +- docs/language_servers.md | 23 +++++++++++++++++++++++ src/project.cpp | 30 ++++++++++++++++++++++++++++++ src/project.hpp | 9 +++++++++ src/project_build.cpp | 6 ++++++ src/project_build.hpp | 5 +++-- src/source_base.cpp | 16 ++++++++++------ 7 files changed, 82 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 04855da..7a42658 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ See [installation guide](docs/install.md). `[language identifier]-language-server` executable is found. This executable can be a symbolic link to one of your installed language server binaries. - For additional instructions, see: [setup of tested language servers](docs/language_servers.md) -- Non-C/C++ projects are also supported, such as Python, JavaScript, and Rust projects +- Non-C/C++ projects are also supported, such as JavaScript, Python, Rust, and Go projects - Git support through libgit2 - Find symbol through Ctags ([Universal Ctags](https://github.com/universal-ctags/ctags) is recommended) diff --git a/docs/language_servers.md b/docs/language_servers.md index fbbae20..8f02af0 100644 --- a/docs/language_servers.md +++ b/docs/language_servers.md @@ -1,5 +1,11 @@ # Setup of tested language servers +- [JavaScript/TypeScript](#javascripttypescript) +- [Python3](#python3) +- [Rust](#rust) +- [Go](#go) +- [GLSL](#glsl) + ## JavaScript/TypeScript ### JavaScript with Flow static type checker @@ -87,6 +93,23 @@ ln -s ~/.cargo/bin/rust-analyzer /usr/local/bin/rust-language-server - Additional setup within a Rust project: - Add an empty `.rustfmt.toml` file to enable style format on save +## Go + +- Prerequisites: + - [Go](https://golang.org/doc/install) + - [gopls](https://github.com/golang/tools/blob/master/gopls/README.md#installation) (must be + installed) + +Create symbolic link to enable language server in juCi++: + +```sh +# Usually as root: +ln -s `which gopls` /usr/local/bin/go-language-server +``` + +- Additional setup within a Go project: + - Add an empty `.go-format` file to enable style format on save + ## GLSL Install language server, and create a script to enable server in juCi++: diff --git a/src/project.cpp b/src/project.cpp index bcac224..48dfd08 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -182,6 +182,8 @@ std::shared_ptr Project::create() { return std::shared_ptr(new Project::Python(std::move(build))); if(language_id == "html") return std::shared_ptr(new Project::HTML(std::move(build))); + if(language_id == "go") + return std::shared_ptr(new Project::Go(std::move(build))); } } else @@ -195,6 +197,8 @@ std::shared_ptr Project::create() { return std::shared_ptr(new Project::JavaScript(std::move(build))); if(dynamic_cast(build.get())) return std::shared_ptr(new Project::Python(std::move(build))); + if(dynamic_cast(build.get())) + return std::shared_ptr(new Project::Go(std::move(build))); return std::shared_ptr(new Project::Base(std::move(build))); } @@ -955,3 +959,29 @@ void Project::Rust::compile_and_run() { } }); } + +void Project::Go::compile_and_run() { + std::string command; + boost::filesystem::path path; + if(dynamic_cast(build.get())) { + command = "go run ."; + path = build->project_path; + } + else { + auto view = Notebook::get().get_current_view(); + if(!view) { + Info::get().print("No executable found"); + return; + } + command = "go run " + filesystem::escape_argument(filesystem::get_short_path(view->file_path).string()); + path = view->file_path.parent_path(); + } + + if(Config::get().terminal.clear_on_compile) + Terminal::get().clear(); + + Terminal::get().print("\e[2mRunning: " + command + "\e[m\n"); + Terminal::get().async_process(command, build->project_path, [command](int exit_status) { + Terminal::get().print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); + }); +} diff --git a/src/project.hpp b/src/project.hpp index e5e3f94..5f37798 100644 --- a/src/project.hpp +++ b/src/project.hpp @@ -162,6 +162,15 @@ namespace Project { std::string get_language_id() override { return "rust"; } }; + class Go : public LanguageProtocol { + public: + Go(std::unique_ptr &&build) : Base(std::move(build)) {} + + void compile_and_run() override; + + std::string get_language_id() override { return "go"; } + }; + std::shared_ptr create(); extern std::shared_ptr current; }; // namespace Project diff --git a/src/project_build.cpp b/src/project_build.cpp index a73cccb..6bc8f1d 100644 --- a/src/project_build.cpp +++ b/src/project_build.cpp @@ -52,6 +52,12 @@ std::unique_ptr Project::Build::create(const boost::filesystem:: return build; } + if(boost::filesystem::exists(search_path / "go.mod", ec)) { + std::unique_ptr build(new GoBuild()); + build->project_path = search_path; + return build; + } + if(search_path == search_path.root_directory()) break; search_path = search_path.parent_path(); diff --git a/src/project_build.hpp b/src/project_build.hpp index a69add0..b9ade14 100644 --- a/src/project_build.hpp +++ b/src/project_build.hpp @@ -72,10 +72,11 @@ namespace Project { }; class NpmBuild : public Build { - public: }; class PythonMain : public Build { - public: + }; + + class GoBuild : public Build { }; } // namespace Project diff --git a/src/source_base.cpp b/src/source_base.cpp index 29759c4..eaa94a5 100644 --- a/src/source_base.cpp +++ b/src/source_base.cpp @@ -287,14 +287,18 @@ Source::BaseView::BaseView(const boost::filesystem::path &file_path, const Glib: is_bracket_language = true; } -#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 + set_tab_width(4); // Visual size of a \t hardcoded to be equal to visual size of 4 spaces tab_char = Config::get().source.default_tab_char; tab_size = Config::get().source.default_tab_size; - if(language && (language->get_id() == "python" || language->get_id() == "rust")) { - tab_char = ' '; - tab_size = 4; + if(language) { + if(language->get_id() == "python" || language->get_id() == "rust") { + tab_char = ' '; + tab_size = 4; + } + else if(language->get_id() == "go") { + tab_char = '\t'; + tab_size = 1; + } } if(Config::get().source.auto_tab_char_and_size) { auto tab_char_and_size = find_tab_char_and_size(); From 68fe6bab1a64cf69f1ad6244f19c026a2621fdae Mon Sep 17 00:00:00 2001 From: eidheim Date: Mon, 7 Jun 2021 11:59:13 +0200 Subject: [PATCH 115/375] Added compile and run support for Julia --- docs/language_servers.md | 29 ++++++++++++++++++++ src/project.cpp | 46 +++++++++++++++++++------------- src/project.hpp | 9 +++++++ src/source.cpp | 3 ++- src/source_base.cpp | 5 ++-- src/source_language_protocol.cpp | 2 +- src/tooltips.cpp | 10 ++++--- 7 files changed, 77 insertions(+), 27 deletions(-) diff --git a/docs/language_servers.md b/docs/language_servers.md index 8f02af0..8af301b 100644 --- a/docs/language_servers.md +++ b/docs/language_servers.md @@ -4,6 +4,7 @@ - [Python3](#python3) - [Rust](#rust) - [Go](#go) +- [Julia](#julia) - [GLSL](#glsl) ## JavaScript/TypeScript @@ -110,6 +111,34 @@ ln -s `which gopls` /usr/local/bin/go-language-server - Additional setup within a Go project: - Add an empty `.go-format` file to enable style format on save +## Julia + +- Prerequisites: + - [Julia](https://julialang.org/downloads/) + +Install language server, and create symbolic link to enable server in juCi++: + +```sh +julia -e 'using Pkg;Pkg.add("LanguageServer");Pkg.add("SymbolServer");Pkg.add("StaticLint");' + +# Usually as root: +echo '#!/bin/sh +julia --startup-file=no --history-file=no -e '\'' +using LanguageServer; +using Pkg; +import StaticLint; +import SymbolServer; +env_path = dirname(Pkg.Types.Context().env.project_file); +server = LanguageServer.LanguageServerInstance(stdin, stdout, env_path, ""); +server.runlinter = true; +run(server); +'\''' > /usr/local/bin/julia-language-server +chmod 755 /usr/local/bin/julia-language-server +``` + +- Additional setup within a Julia project: + - Add an empty `.julia-format` file to enable style format on save + ## GLSL Install language server, and create a script to enable server in juCi++: diff --git a/src/project.cpp b/src/project.cpp index 48dfd08..206f206 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -184,6 +184,8 @@ std::shared_ptr Project::create() { return std::shared_ptr(new Project::HTML(std::move(build))); if(language_id == "go") return std::shared_ptr(new Project::Go(std::move(build))); + if(language_id == "julia") + return std::shared_ptr(new Project::Julia(std::move(build))); } } else @@ -853,15 +855,12 @@ void Project::Python::compile_and_run() { command += filesystem::get_short_path(build->project_path).string(); path = build->project_path; } - else { - auto view = Notebook::get().get_current_view(); - if(!view) { - Info::get().print("No executable found"); - return; - } + else if(auto view = Notebook::get().get_current_view()) { command += filesystem::escape_argument(filesystem::get_short_path(view->file_path).string()); path = view->file_path.parent_path(); } + else + return; if(Config::get().terminal.clear_on_compile) Terminal::get().clear(); @@ -879,15 +878,12 @@ void Project::JavaScript::compile_and_run() { command = "npm start"; path = build->project_path; } - else { - auto view = Notebook::get().get_current_view(); - if(!view) { - Info::get().print("No executable found"); - return; - } + else if(auto view = Notebook::get().get_current_view()) { command = "node " + filesystem::escape_argument(filesystem::get_short_path(view->file_path).string()); path = view->file_path.parent_path(); } + else + return; if(Config::get().terminal.clear_on_compile) Terminal::get().clear(); @@ -967,21 +963,33 @@ void Project::Go::compile_and_run() { command = "go run ."; path = build->project_path; } - else { - auto view = Notebook::get().get_current_view(); - if(!view) { - Info::get().print("No executable found"); - return; - } + else if(auto view = Notebook::get().get_current_view()) { command = "go run " + filesystem::escape_argument(filesystem::get_short_path(view->file_path).string()); path = view->file_path.parent_path(); } + else + return; if(Config::get().terminal.clear_on_compile) Terminal::get().clear(); Terminal::get().print("\e[2mRunning: " + command + "\e[m\n"); - Terminal::get().async_process(command, build->project_path, [command](int exit_status) { + Terminal::get().async_process(command, path, [command](int exit_status) { Terminal::get().print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); }); } + +void Project::Julia::compile_and_run() { + if(auto view = Notebook::get().get_current_view()) { + auto command = "julia " + filesystem::escape_argument(filesystem::get_short_path(Notebook::get().get_current_view()->file_path).string()); + auto path = view->file_path.parent_path(); + + if(Config::get().terminal.clear_on_compile) + Terminal::get().clear(); + + Terminal::get().print("\e[2mRunning: " + command + "\e[m\n"); + Terminal::get().async_process(command, path, [command](int exit_status) { + Terminal::get().print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n"); + }); + } +} diff --git a/src/project.hpp b/src/project.hpp index 5f37798..ecdb0f7 100644 --- a/src/project.hpp +++ b/src/project.hpp @@ -171,6 +171,15 @@ namespace Project { std::string get_language_id() override { return "go"; } }; + class Julia : public LanguageProtocol { + public: + Julia(std::unique_ptr &&build) : Base(std::move(build)) {} + + void compile_and_run() override; + + std::string get_language_id() override { return "julia"; } + }; + std::shared_ptr create(); extern std::shared_ptr current; }; // namespace Project diff --git a/src/source.cpp b/src/source.cpp index 88a698e..e736550 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -224,7 +224,8 @@ Source::View::View(const boost::filesystem::path &file_path, const Glib::RefPtr< if(language_id == "cmake" || language_id == "makefile" || language_id == "python" || language_id == "python3" || language_id == "sh" || language_id == "perl" || language_id == "ruby" || language_id == "r" || language_id == "asm" || - language_id == "automake" || language_id == "yaml" || language_id == "docker") + language_id == "automake" || language_id == "yaml" || language_id == "docker" || + language_id == "julia") comment_characters = "#"; else if(language_id == "latex" || language_id == "matlab" || language_id == "octave" || language_id == "bibtex") comment_characters = "%"; diff --git a/src/source_base.cpp b/src/source_base.cpp index eaa94a5..cd955e0 100644 --- a/src/source_base.cpp +++ b/src/source_base.cpp @@ -291,11 +291,12 @@ Source::BaseView::BaseView(const boost::filesystem::path &file_path, const Glib: tab_char = Config::get().source.default_tab_char; tab_size = Config::get().source.default_tab_size; if(language) { - if(language->get_id() == "python" || language->get_id() == "rust") { + auto language_id = language->get_id(); + if(language_id == "python" || language_id == "rust" || language_id == "julia") { tab_char = ' '; tab_size = 4; } - else if(language->get_id() == "go") { + else if(language_id == "go") { tab_char = '\t'; tab_size = 1; } diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index cc4e588..e894e00 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -369,7 +369,7 @@ void LanguageProtocol::Client::write_request(Source::LanguageProtocolView *view, LockGuard 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::this_thread::sleep_for(std::chrono::milliseconds(50000) * (language_id == "julia" ? 100 : 1)); LockGuard lock(read_write_mutex); auto id_it = handlers.find(message_id); if(id_it == handlers.end()) diff --git a/src/tooltips.cpp b/src/tooltips.cpp index e433f36..8f2c3eb 100644 --- a/src/tooltips.cpp +++ b/src/tooltips.cpp @@ -155,19 +155,21 @@ void Tooltip::show(bool disregard_drawn, const std::function &on_motion) return true; } - const static std::regex regex("^([^:]+):([^:]+):([^:]+)$", std::regex::optimize); + const static std::regex regex("^([^:]+):([^:]+)(:[^:]+)?$", std::regex::optimize); std::smatch sm; if(std::regex_match(link, sm, regex)) { auto path = boost::filesystem::path(sm[1].str()); - if(auto source_view = dynamic_cast(view)) - path = filesystem::get_normal_path(source_view->file_path.parent_path() / path); + if(path.is_relative()) { + if(auto source_view = dynamic_cast(view)) + path = filesystem::get_normal_path(source_view->file_path.parent_path() / path); + } boost::system::error_code ec; if(boost::filesystem::is_regular_file(path, ec)) { if(Notebook::get().open(path)) { try { auto line = std::stoi(sm[2].str()) - 1; - auto offset = std::stoi(sm[3].str()) - 1; + auto offset = sm.length(3) ? std::stoi(sm[3].str().substr(1)) - 1 : 0; auto view = Notebook::get().get_current_view(); view->place_cursor_at_line_offset(line, offset); view->scroll_to_cursor_delayed(true, false); From 967ce0cf30589fc198173f5abe2650f1703c5d79 Mon Sep 17 00:00:00 2001 From: eidheim Date: Mon, 7 Jun 2021 14:25:06 +0200 Subject: [PATCH 116/375] Fixed minor typo in Julia language server install instructions --- docs/language_servers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/language_servers.md b/docs/language_servers.md index 8af301b..5a547a3 100644 --- a/docs/language_servers.md +++ b/docs/language_servers.md @@ -116,7 +116,7 @@ ln -s `which gopls` /usr/local/bin/go-language-server - Prerequisites: - [Julia](https://julialang.org/downloads/) -Install language server, and create symbolic link to enable server in juCi++: +Install language server, and create executable to enable server in juCi++: ```sh julia -e 'using Pkg;Pkg.add("LanguageServer");Pkg.add("SymbolServer");Pkg.add("StaticLint");' From 68202c648ad779c29178d1526e9d44e2398ddb56 Mon Sep 17 00:00:00 2001 From: eidheim Date: Mon, 7 Jun 2021 14:27:46 +0200 Subject: [PATCH 117/375] Added warning messages on missing go or julia language server --- src/notebook.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/notebook.cpp b/src/notebook.cpp index bef2af1..cd57c82 100644 --- a/src/notebook.cpp +++ b/src/notebook.cpp @@ -211,6 +211,16 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position Terminal::get().print("You will need to restart juCi++ after installing Rust.\n"); shown.emplace(language_id); } + else if(language_id == "go") { + Terminal::get().print("\e[33mWarning\e[m: could not find Go language server.\n"); + Terminal::get().print("For installation instructions please visit: https://gitlab.com/cppit/jucipp/-/blob/master/docs/language_servers.md#go.\n"); + shown.emplace(language_id); + } + else if(language_id == "julia") { + Terminal::get().print("\e[33mWarning\e[m: could not find Julia language server.\n"); + Terminal::get().print("For installation instructions please visit: https://gitlab.com/cppit/jucipp/-/blob/master/docs/language_servers.md#julia.\n"); + shown.emplace(language_id); + } } } source_views.emplace_back(new Source::GenericView(file_path, language)); From c6bf84bd907d9e45acbd77e41c88bfed15804273 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 8 Jun 2021 09:05:52 +0200 Subject: [PATCH 118/375] Cleanup of on_motion_notify_event --- src/source.cpp | 31 ++++++------------------------- src/source.hpp | 1 - src/source_base.cpp | 16 ++++++++++++++++ src/source_base.hpp | 1 + src/terminal.cpp | 19 +------------------ 5 files changed, 24 insertions(+), 44 deletions(-) diff --git a/src/source.cpp b/src/source.cpp index e736550..4eb6ab8 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -2030,7 +2030,7 @@ bool Source::View::find_close_symbol_forward(Gtk::TextIter iter, Gtk::TextIter & long curly_count = 0; do { if(curly_count == 0 && *iter == positive_char && is_code_iter(iter)) { - if((is_c || is_cpp) && positive_char == '>') { + if(is_cpp && positive_char == '>') { auto prev = iter; if(prev.backward_char() && *prev == '-') continue; @@ -2043,7 +2043,7 @@ bool Source::View::find_close_symbol_forward(Gtk::TextIter iter, Gtk::TextIter & count++; } else if(curly_count == 0 && *iter == negative_char && is_code_iter(iter)) { - if((is_c || is_cpp) && negative_char == '>') { + if(is_cpp && negative_char == '>') { auto prev = iter; if(prev.backward_char() && *prev == '-') continue; @@ -2091,7 +2091,7 @@ bool Source::View::find_open_symbol_backward(Gtk::TextIter iter, Gtk::TextIter & long curly_count = 0; do { if(curly_count == 0 && *iter == positive_char && is_code_iter(iter)) { - if((is_c || is_cpp) && positive_char == '>') { + if(is_cpp && positive_char == '>') { auto prev = iter; if(prev.backward_char() && *prev == '-') continue; @@ -2108,7 +2108,7 @@ bool Source::View::find_open_symbol_backward(Gtk::TextIter iter, Gtk::TextIter & count++; } else if(curly_count == 0 && *iter == negative_char && is_code_iter(iter)) { - if((is_c || is_cpp) && negative_char == '>') { + if(is_cpp && negative_char == '>') { auto prev = iter; if(prev.backward_char() && *prev == '-') continue; @@ -2804,7 +2804,7 @@ bool Source::View::on_key_press_event_bracket_language(GdkEventKey *event) { if(found_tabs_end_iter.get_line_offset() == tabs_end_iter.get_line_offset()) { has_right_curly_bracket = true; // Special case for functions and classes with no indentation after: namespace { - if((is_c || is_cpp) && tabs_end_iter.starts_line()) { + if(is_cpp && tabs_end_iter.starts_line()) { auto iter = condition_iter; Gtk::TextIter open_iter; if(iter.backward_char() && find_open_symbol_backward(iter, open_iter, '{', '}')) { @@ -3350,7 +3350,7 @@ bool Source::View::on_key_press_event_smart_inserts(GdkEventKey *event) { if(found_tabs_end_iter.get_line_offset() == tabs_end_iter.get_line_offset()) { has_right_curly_bracket = true; // Special case for functions and classes with no indentation after: namespace {: - if((is_c || is_cpp) && tabs_end_iter.starts_line()) { + if(is_cpp && tabs_end_iter.starts_line()) { Gtk::TextIter open_iter; if(find_open_symbol_backward(iter, open_iter, '{', '}')) { if(open_iter.starts_line()) // in case of: namespace test\n{ @@ -3518,22 +3518,3 @@ bool Source::View::on_button_press_event(GdkEventButton *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 -#ifdef __APPLE__ - if((event->state & GDK_BUTTON1_MASK) == 0 || (event->state & GDK_SHIFT_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(iter, get_buffer()->get_selection_bound()->get_iter()); - return true; - } -#else - return Gsv::View::on_motion_notify_event(event); -#endif -} diff --git a/src/source.hpp b/src/source.hpp index a9d2ab9..c81bc42 100644 --- a/src/source.hpp +++ b/src/source.hpp @@ -170,7 +170,6 @@ namespace Source { bool on_key_press_event_smart_brackets(GdkEventKey *event); bool on_key_press_event_smart_inserts(GdkEventKey *event); bool on_button_press_event(GdkEventButton *event) override; - bool on_motion_notify_event(GdkEventMotion *motion_event) override; bool interactive_completion = true; diff --git a/src/source_base.cpp b/src/source_base.cpp index cd955e0..f5b9003 100644 --- a/src/source_base.cpp +++ b/src/source_base.cpp @@ -136,6 +136,22 @@ bool Source::CommonView::on_key_press_event(GdkEventKey *event) { return Gsv::View::on_key_press_event(event); } +bool Source::CommonView::on_motion_notify_event(GdkEventMotion *event) { + // Workaround for drag-and-drop crash on MacOS + // TODO 2023: check if this bug has been fixed +#ifdef __APPLE__ + if((event->state & GDK_BUTTON1_MASK) > 0 && (event->state & GDK_SHIFT_MASK) == 0) { + 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(iter, get_buffer()->get_selection_bound()->get_iter()); + return true; + } +#endif + return Gsv::View::on_motion_notify_event(event); +} + void Source::CommonView::cut() { if(!get_editable()) return copy(); diff --git a/src/source_base.hpp b/src/source_base.hpp index 864b93b..d957fde 100644 --- a/src/source_base.hpp +++ b/src/source_base.hpp @@ -55,6 +55,7 @@ namespace Source { protected: bool on_key_press_event(GdkEventKey *event) override; + bool on_motion_notify_event(GdkEventMotion *motion_event) override; private: GtkSourceSearchContext *search_context; diff --git a/src/terminal.cpp b/src/terminal.cpp index 687c753..fa19c1b 100644 --- a/src/terminal.cpp +++ b/src/terminal.cpp @@ -380,24 +380,7 @@ bool Terminal::on_motion_notify_event(GdkEventMotion *event) { 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); -#endif - - return Gtk::TextView::on_motion_notify_event(event); + return Source::CommonView::on_motion_notify_event(event); } boost::optional Terminal::find_link(const std::string &line) { From c8d9733385bfb42d81c837a7a1329fa12d95cc1c Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 8 Jun 2021 11:10:12 +0200 Subject: [PATCH 119/375] Changed the timeouts in the language client --- src/source_language_protocol.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index e894e00..f337d48 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -110,7 +110,7 @@ LanguageProtocol::Client::~Client() { thread.join(); int exit_status = -1; - for(size_t c = 0; c < 20; ++c) { + for(size_t c = 0; c < 10; ++c) { std::this_thread::sleep_for(std::chrono::milliseconds(500)); if(process->try_get_exit_status(exit_status)) break; @@ -368,8 +368,8 @@ void LanguageProtocol::Client::write_request(Source::LanguageProtocolView *view, auto message_id = this->message_id; LockGuard 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(50000) * (language_id == "julia" ? 100 : 1)); + for(size_t c = 0; c < 40; ++c) { + std::this_thread::sleep_for(std::chrono::milliseconds(500) * (language_id == "julia" ? 100 : 1)); LockGuard lock(read_write_mutex); auto id_it = handlers.find(message_id); if(id_it == handlers.end()) From aa1019093dc321594693f0405440a948e1e9e809 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 8 Jun 2021 11:42:29 +0200 Subject: [PATCH 120/375] Fixed autocomplete at start of buffer --- src/source_clang.cpp | 14 +++++++------- src/source_generic.cpp | 7 ++++++- src/source_language_protocol.cpp | 14 +++++++------- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/source_clang.cpp b/src/source_clang.cpp index c0bb4f7..2d87481 100644 --- a/src/source_clang.cpp +++ b/src/source_clang.cpp @@ -858,19 +858,19 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa autocomplete.run_check = [this]() { auto iter = get_buffer()->get_insert()->get_iter(); auto prefix_end = iter; - iter.backward_char(); - if(!is_code_iter(iter)) + + size_t count = 0; + while(iter.backward_char() && is_token_char(*iter)) + ++count; + + if(count == 0) return false; enable_snippets = false; show_parameters = false; - size_t count = 0; - while(is_token_char(*iter) && iter.backward_char()) - ++count; - auto prefix_start = iter; - if(prefix_start != prefix_end) + if(prefix_start != prefix_end && !is_token_char(*iter)) prefix_start.forward_char(); auto previous = iter; diff --git a/src/source_generic.cpp b/src/source_generic.cpp index b56a22a..b43c29e 100644 --- a/src/source_generic.cpp +++ b/src/source_generic.cpp @@ -206,10 +206,15 @@ void Source::GenericView::setup_autocomplete() { autocomplete.run_check = [this]() { auto start = get_buffer()->get_insert()->get_iter(); auto end = start; + size_t count = 0; while(start.backward_char() && is_token_char(*start)) ++count; - if((start.is_start() || start.forward_char()) && count >= 3 && !(*start >= '0' && *start <= '9')) { + + if(!is_token_char(*start)) + start.forward_char(); + + if(count >= 3 && !(*start >= '0' && *start <= '9')) { LockGuard lock1(autocomplete.prefix_mutex); LockGuard lock2(buffer_words_mutex); autocomplete.prefix = get_buffer()->get_text(start, end); diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index f337d48..f67b0c4 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -1632,19 +1632,19 @@ void Source::LanguageProtocolView::setup_autocomplete() { autocomplete->run_check = [this, is_possible_jsx_property]() { auto iter = get_buffer()->get_insert()->get_iter(); auto prefix_end = iter; - iter.backward_char(); - if(!is_code_iter(iter)) + + size_t count = 0; + while(iter.backward_char() && is_token_char(*iter)) + ++count; + + if(count == 0) return false; autocomplete_enable_snippets = false; autocomplete_show_arguments = false; - size_t count = 0; - while(is_token_char(*iter) && iter.backward_char()) - ++count; - auto prefix_start = iter; - if(prefix_start != prefix_end) + if(prefix_start != prefix_end && !is_token_char(*iter)) prefix_start.forward_char(); auto previous = iter; From 20a046db69565dd3958e6fb8c65f38dfb960fd2a Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 8 Jun 2021 14:07:17 +0200 Subject: [PATCH 121/375] Cleanup of autocompletion run checks --- src/source.cpp | 10 +++---- src/source_clang.cpp | 48 +++++++++++++------------------- src/source_generic.cpp | 33 +++++++++------------- src/source_language_protocol.cpp | 48 +++++++++++++------------------- 4 files changed, 56 insertions(+), 83 deletions(-) diff --git a/src/source.cpp b/src/source.cpp index 4eb6ab8..679b647 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -928,7 +928,7 @@ void Source::View::setup_format_style(bool is_generic_view) { try { boost::property_tree::ptree pt; boost::property_tree::xml_parser::read_xml(stdout_stream, pt); - auto replacements_pt = pt.get_child("replacements"); + auto replacements_pt = pt.get_child("replacements", boost::property_tree::ptree()); for(auto it = replacements_pt.rbegin(); it != replacements_pt.rend(); ++it) { if(it->first == "replacement") { auto offset = it->second.get(".offset"); @@ -2030,7 +2030,7 @@ bool Source::View::find_close_symbol_forward(Gtk::TextIter iter, Gtk::TextIter & long curly_count = 0; do { if(curly_count == 0 && *iter == positive_char && is_code_iter(iter)) { - if(is_cpp && positive_char == '>') { + if((is_c || is_cpp) && positive_char == '>') { auto prev = iter; if(prev.backward_char() && *prev == '-') continue; @@ -2043,7 +2043,7 @@ bool Source::View::find_close_symbol_forward(Gtk::TextIter iter, Gtk::TextIter & count++; } else if(curly_count == 0 && *iter == negative_char && is_code_iter(iter)) { - if(is_cpp && negative_char == '>') { + if((is_c || is_cpp) && negative_char == '>') { auto prev = iter; if(prev.backward_char() && *prev == '-') continue; @@ -2091,7 +2091,7 @@ bool Source::View::find_open_symbol_backward(Gtk::TextIter iter, Gtk::TextIter & long curly_count = 0; do { if(curly_count == 0 && *iter == positive_char && is_code_iter(iter)) { - if(is_cpp && positive_char == '>') { + if((is_c || is_cpp) && positive_char == '>') { auto prev = iter; if(prev.backward_char() && *prev == '-') continue; @@ -2108,7 +2108,7 @@ bool Source::View::find_open_symbol_backward(Gtk::TextIter iter, Gtk::TextIter & count++; } else if(curly_count == 0 && *iter == negative_char && is_code_iter(iter)) { - if(is_cpp && negative_char == '>') { + if((is_c || is_cpp) && negative_char == '>') { auto prev = iter; if(prev.backward_char() && *prev == '-') continue; diff --git a/src/source_clang.cpp b/src/source_clang.cpp index 2d87481..29e618c 100644 --- a/src/source_clang.cpp +++ b/src/source_clang.cpp @@ -856,25 +856,29 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa }; autocomplete.run_check = [this]() { - auto iter = get_buffer()->get_insert()->get_iter(); - auto prefix_end = iter; + auto prefix_start = get_buffer()->get_insert()->get_iter(); + auto prefix_end = prefix_start; + + auto prev = prefix_start; + prev.backward_char(); + if(!is_code_iter(prev)) + return false; size_t count = 0; - while(iter.backward_char() && is_token_char(*iter)) + while(prefix_start.backward_char() && is_token_char(*prefix_start)) ++count; - if(count == 0) - return false; - enable_snippets = false; show_parameters = false; - auto prefix_start = iter; - if(prefix_start != prefix_end && !is_token_char(*iter)) + if(prefix_start != prefix_end && !is_token_char(*prefix_start)) prefix_start.forward_char(); - auto previous = iter; - if(*iter == '.') { + prev = prefix_start; + prev.backward_char(); + auto prevprev = prev; + if(*prev == '.') { + auto iter = prev; bool starts_with_num = false; size_t count = 0; while(iter.backward_char() && is_token_char(*iter)) { @@ -889,8 +893,8 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa return true; } } - else if((previous.backward_char() && ((*previous == ':' && *iter == ':') || (*previous == '-' && *iter == '>')))) { - if(*iter == ':' || (previous.backward_char() && (is_token_char(*previous) || *previous == ')' || *previous == ']'))) { + else if((prevprev.backward_char() && ((*prevprev == ':' && *prev == ':') || (*prevprev == '-' && *prev == '>')))) { + if(*prev == ':' || (prevprev.backward_char() && (is_token_char(*prevprev) || *prevprev == ')' || *prevprev == ']'))) { { LockGuard lock(autocomplete.prefix_mutex); autocomplete.prefix = get_buffer()->get_text(prefix_start, prefix_end); @@ -913,26 +917,12 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa return true; } if(!interactive_completion) { - auto end_iter = get_buffer()->get_insert()->get_iter(); - auto iter = end_iter; - while(iter.backward_char() && is_token_char(*iter)) { - } - if(iter != end_iter) - iter.forward_char(); - { LockGuard lock(autocomplete.prefix_mutex); - autocomplete.prefix = get_buffer()->get_text(iter, end_iter); - } - auto prev1 = iter; - if(prev1.backward_char() && *prev1 != '.') { - auto prev2 = prev1; - if(!prev2.backward_char()) - enable_snippets = true; - else if(!(*prev2 == ':' && *prev1 == ':') && !(*prev2 == '-' && *prev1 == '>')) - enable_snippets = true; + autocomplete.prefix = get_buffer()->get_text(prefix_start, prefix_end); } - + auto prevprev = prev; + enable_snippets = !(*prev == '.' || (prevprev.backward_char() && ((*prevprev == ':' && *prev == ':') || (*prevprev == '-' && *prev == '>')))); return true; } diff --git a/src/source_generic.cpp b/src/source_generic.cpp index b43c29e..d310bc4 100644 --- a/src/source_generic.cpp +++ b/src/source_generic.cpp @@ -204,34 +204,27 @@ void Source::GenericView::setup_autocomplete() { }; autocomplete.run_check = [this]() { - auto start = get_buffer()->get_insert()->get_iter(); - auto end = start; + auto prefix_start = get_buffer()->get_insert()->get_iter(); + auto prefix_end = prefix_start; size_t count = 0; - while(start.backward_char() && is_token_char(*start)) + while(prefix_start.backward_char() && is_token_char(*prefix_start)) ++count; - if(!is_token_char(*start)) - start.forward_char(); + if(prefix_start != prefix_end && !is_token_char(*prefix_start)) + prefix_start.forward_char(); - if(count >= 3 && !(*start >= '0' && *start <= '9')) { + if((count >= 3 && !(*prefix_start >= '0' && *prefix_start <= '9')) || !interactive_completion) { LockGuard lock1(autocomplete.prefix_mutex); LockGuard lock2(buffer_words_mutex); - autocomplete.prefix = get_buffer()->get_text(start, end); - show_prefix_buffer_word = buffer_words.find(autocomplete.prefix) != buffer_words.end(); - return true; - } - else if(!interactive_completion) { - auto end_iter = get_buffer()->get_insert()->get_iter(); - auto iter = end_iter; - while(iter.backward_char() && is_token_char(*iter)) { + autocomplete.prefix = get_buffer()->get_text(prefix_start, prefix_end); + + if(interactive_completion) + show_prefix_buffer_word = buffer_words.find(autocomplete.prefix) != buffer_words.end(); + else { + auto it = buffer_words.find(autocomplete.prefix); + show_prefix_buffer_word = !(it == buffer_words.end() || it->second == 1); } - if(iter != end_iter) - iter.forward_char(); - LockGuard lock1(autocomplete.prefix_mutex); - LockGuard lock2(buffer_words_mutex); - autocomplete.prefix = get_buffer()->get_text(iter, end_iter); - show_prefix_buffer_word = buffer_words.find(autocomplete.prefix) != buffer_words.end(); return true; } diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index f67b0c4..b035db6 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -1630,25 +1630,29 @@ void Source::LanguageProtocolView::setup_autocomplete() { } autocomplete->run_check = [this, is_possible_jsx_property]() { - auto iter = get_buffer()->get_insert()->get_iter(); - auto prefix_end = iter; + auto prefix_start = get_buffer()->get_insert()->get_iter(); + auto prefix_end = prefix_start; + + auto prev = prefix_start; + prev.backward_char(); + if(!is_code_iter(prev)) + return false; size_t count = 0; - while(iter.backward_char() && is_token_char(*iter)) + while(prefix_start.backward_char() && is_token_char(*prefix_start)) ++count; - if(count == 0) - return false; - autocomplete_enable_snippets = false; autocomplete_show_arguments = false; - auto prefix_start = iter; - if(prefix_start != prefix_end && !is_token_char(*iter)) + if(prefix_start != prefix_end && !is_token_char(*prefix_start)) prefix_start.forward_char(); - auto previous = iter; - if(*iter == '.') { + prev = prefix_start; + prev.backward_char(); + auto prevprev = prev; + if(*prev == '.') { + auto iter = prev; bool starts_with_num = false; size_t count = 0; while(iter.backward_char() && is_token_char(*iter)) { @@ -1663,7 +1667,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { return true; } } - else if((previous.backward_char() && *previous == ':' && *iter == ':')) { + else if((prevprev.backward_char() && *prevprev == ':' && *prev == ':')) { { LockGuard lock(autocomplete->prefix_mutex); autocomplete->prefix = get_buffer()->get_text(prefix_start, prefix_end); @@ -1678,7 +1682,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { autocomplete_enable_snippets = true; return true; } - if(is_possible_jsx_property(iter)) { + if(is_possible_jsx_property(prefix_start)) { LockGuard lock(autocomplete->prefix_mutex); autocomplete->prefix = ""; return true; @@ -1690,26 +1694,12 @@ void Source::LanguageProtocolView::setup_autocomplete() { return true; } if(!interactive_completion) { - auto end_iter = get_buffer()->get_insert()->get_iter(); - auto iter = end_iter; - while(iter.backward_char() && is_token_char(*iter)) { - } - if(iter != end_iter) - iter.forward_char(); - { LockGuard lock(autocomplete->prefix_mutex); - autocomplete->prefix = get_buffer()->get_text(iter, end_iter); - } - auto prev1 = iter; - if(prev1.backward_char() && *prev1 != '.') { - auto prev2 = prev1; - if(!prev2.backward_char()) - autocomplete_enable_snippets = true; - else if(!(*prev2 == ':' && *prev1 == ':')) - autocomplete_enable_snippets = true; + autocomplete->prefix = get_buffer()->get_text(prefix_start, prefix_end); } - + auto prevprev = prev; + autocomplete_enable_snippets = !(*prev == '.' || (prevprev.backward_char() && *prevprev == ':' && *prev == ':')); return true; } From 47caf05f9410baeb6d02aec73823cc9275324fd8 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 8 Jun 2021 14:33:39 +0200 Subject: [PATCH 122/375] Language client: improved is_possible_jsx_property --- src/source_language_protocol.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index b035db6..079948c 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -1615,7 +1615,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { return false; }; - std::function is_possible_jsx_property = [](const Gtk::TextIter &) { return false; }; + std::function is_possible_jsx_property = [](Gtk::TextIter) { return false; }; if(language && language->get_id() == "js") { autocomplete->is_restart_key = [](guint keyval) { if(keyval == '.' || keyval == ' ') @@ -1623,9 +1623,8 @@ void Source::LanguageProtocolView::setup_autocomplete() { return false; }; - is_possible_jsx_property = [this](const Gtk::TextIter &iter) { - Gtk::TextIter found_iter; - return *iter == ' ' && find_open_symbol_backward(iter, found_iter, '<', '>'); + is_possible_jsx_property = [this](Gtk::TextIter iter) { + return (*iter == ' ' || iter.ends_line() || (*iter == '>' && iter.backward_char())) && find_open_symbol_backward(iter, iter, '<', '>'); }; } @@ -1682,13 +1681,13 @@ void Source::LanguageProtocolView::setup_autocomplete() { autocomplete_enable_snippets = true; return true; } - if(is_possible_jsx_property(prefix_start)) { + if(is_possible_argument()) { + autocomplete_show_arguments = true; LockGuard lock(autocomplete->prefix_mutex); autocomplete->prefix = ""; return true; } - if(is_possible_argument()) { - autocomplete_show_arguments = true; + if(is_possible_jsx_property(prefix_start)) { LockGuard lock(autocomplete->prefix_mutex); autocomplete->prefix = ""; return true; From 2b5d778284d8fd88e7d7e985552553408faae420 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 8 Jun 2021 14:37:06 +0200 Subject: [PATCH 123/375] Language client: further improvement to is_possible_jsx_property --- src/source_language_protocol.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 079948c..36daf43 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -1624,7 +1624,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { }; is_possible_jsx_property = [this](Gtk::TextIter iter) { - return (*iter == ' ' || iter.ends_line() || (*iter == '>' && iter.backward_char())) && find_open_symbol_backward(iter, iter, '<', '>'); + return (*iter == ' ' || iter.ends_line() || *iter == '/' || (*iter == '>' && iter.backward_char())) && find_open_symbol_backward(iter, iter, '<', '>'); }; } From 2424113b9f11e441e2b7a8a24e2e9e7911063b54 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 8 Jun 2021 18:36:39 +0200 Subject: [PATCH 124/375] Language client: made use of Source::is_js --- src/source_language_protocol.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 36daf43..3a483ba 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -550,7 +550,7 @@ bool Source::LanguageProtocolView::save() { } void Source::LanguageProtocolView::setup_navigation_and_refactoring() { - if(capabilities.document_formatting && !(format_style && language && language->get_id() == "js" /* Use Prettier instead */)) { + if(capabilities.document_formatting && !(format_style && is_js /* Use Prettier instead */)) { format_style = [this](bool continue_without_style_file) { if(!continue_without_style_file) { bool has_style_file = false; @@ -1615,20 +1615,20 @@ void Source::LanguageProtocolView::setup_autocomplete() { return false; }; - std::function is_possible_jsx_property = [](Gtk::TextIter) { return false; }; - if(language && language->get_id() == "js") { + std::function is_possible_xml_attribute = [](Gtk::TextIter) { return false; }; + if(is_js) { autocomplete->is_restart_key = [](guint keyval) { if(keyval == '.' || keyval == ' ') return true; return false; }; - is_possible_jsx_property = [this](Gtk::TextIter iter) { + is_possible_xml_attribute = [this](Gtk::TextIter iter) { return (*iter == ' ' || iter.ends_line() || *iter == '/' || (*iter == '>' && iter.backward_char())) && find_open_symbol_backward(iter, iter, '<', '>'); }; } - autocomplete->run_check = [this, is_possible_jsx_property]() { + autocomplete->run_check = [this, is_possible_xml_attribute]() { auto prefix_start = get_buffer()->get_insert()->get_iter(); auto prefix_end = prefix_start; @@ -1687,7 +1687,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { autocomplete->prefix = ""; return true; } - if(is_possible_jsx_property(prefix_start)) { + if(is_possible_xml_attribute(prefix_start)) { LockGuard lock(autocomplete->prefix_mutex); autocomplete->prefix = ""; return true; From e63b4e974db296819fcd474e5cec4d4344d89cdc Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 9 Jun 2021 09:15:19 +0200 Subject: [PATCH 125/375] Cleanup of source language checks --- src/directories.cpp | 11 +-- src/notebook.cpp | 29 ++++---- src/project.cpp | 31 ++++----- src/source.cpp | 98 +++++++++++---------------- src/source.hpp | 5 -- src/source_base.cpp | 112 +++++++++++++++++-------------- src/source_base.hpp | 32 ++++++--- src/source_clang.cpp | 12 ++-- src/source_language_protocol.cpp | 2 +- src/tooltips.cpp | 8 +-- tests/source_clang_test.cpp | 2 +- 11 files changed, 166 insertions(+), 176 deletions(-) diff --git a/src/directories.cpp b/src/directories.cpp index fd154d7..1297b8d 100644 --- a/src/directories.cpp +++ b/src/directories.cpp @@ -402,14 +402,9 @@ 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(); - 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) + auto old_language_id = view->language_id; + view->set_language(Source::guess_language(target_path)); + if(view->language_id != old_language_id) Terminal::get().print("\e[33mWarning\e[m: language for " + filesystem::get_short_path(target_path).string() + " has changed.\nPlease reopen the file.\n"); } } diff --git a/src/notebook.cpp b/src/notebook.cpp index cd57c82..75403f1 100644 --- a/src/notebook.cpp +++ b/src/notebook.cpp @@ -156,22 +156,20 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position 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 if(file_path.extension() == ".tsx") - language_protocol_language_id = "typescriptreact"; - else - language_protocol_language_id = "javascript"; - } + std::string language_id = language ? language->get_id() : ""; + std::string language_protocol_language_id = language_id; + + if(language_protocol_language_id == "js") { + if(file_path.extension() == ".ts") + language_protocol_language_id = "typescript"; + else if(file_path.extension() == ".tsx") + language_protocol_language_id = "typescriptreact"; + else + language_protocol_language_id = "javascript"; } size_t source_views_previous_size = source_views.size(); - if(language && (language->get_id() == "chdr" || language->get_id() == "cpphdr" || language->get_id() == "c" || language->get_id() == "cpp" || language->get_id() == "objc")) + if(language_id == "chdr" || language_id == "cpphdr" || language_id == "c" || language_id == "cpp" || language_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, language_protocol_language_id + "-language-server")); @@ -189,9 +187,8 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position } } if(source_views_previous_size == source_views.size()) { - if(language) { + if(!language_id.empty()) { static std::set shown; - std::string language_id = language->get_id(); if(shown.find(language_id) == shown.end()) { if(language_id == "js") { Terminal::get().print("\e[33mWarning\e[m: could not find JavaScript/TypeScript language server.\n"); @@ -451,7 +448,7 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position }); #ifdef JUCI_ENABLE_DEBUG - if(dynamic_cast(view) || (view->language && view->language->get_id() == "rust")) { + if(dynamic_cast(view) || view->language_id == "rust") { view->toggle_breakpoint = [view](int line_nr) { if(view->get_source_buffer()->get_source_marks_at_line(line_nr, "debug_breakpoint").size() > 0) { auto start_iter = view->get_buffer()->get_iter_at_line(line_nr); diff --git a/src/project.cpp b/src/project.cpp index 206f206..b4db0b5 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -77,13 +77,13 @@ void Project::on_save(size_t index) { Commands::get().load(); boost::filesystem::path build_path; - if(view->language && view->language->get_id() == "cmake") { + if(view->language_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") { + else if(view->language_id == "meson") { if(view->file_path.filename() == "meson.build") build_path = view->file_path; else @@ -172,21 +172,18 @@ std::shared_ptr Project::create() { 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(new Project::Markdown(std::move(build))); - if(language_id == "js") - return std::shared_ptr(new Project::JavaScript(std::move(build))); - if(language_id == "python") - return std::shared_ptr(new Project::Python(std::move(build))); - if(language_id == "html") - return std::shared_ptr(new Project::HTML(std::move(build))); - if(language_id == "go") - return std::shared_ptr(new Project::Go(std::move(build))); - if(language_id == "julia") - return std::shared_ptr(new Project::Julia(std::move(build))); - } + if(view->language_id == "markdown") + return std::shared_ptr(new Project::Markdown(std::move(build))); + if(view->language_id == "js") + return std::shared_ptr(new Project::JavaScript(std::move(build))); + if(view->language_id == "python") + return std::shared_ptr(new Project::Python(std::move(build))); + if(view->language_id == "html") + return std::shared_ptr(new Project::HTML(std::move(build))); + if(view->language_id == "go") + return std::shared_ptr(new Project::Go(std::move(build))); + if(view->language_id == "julia") + return std::shared_ptr(new Project::Julia(std::move(build))); } else build = Build::create(Directories::get().path); diff --git a/src/source.cpp b/src/source.cpp index 679b647..80073b8 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -89,9 +89,8 @@ Glib::RefPtr Source::guess_language(const boost::filesystem::path else language = language_manager->get_language("cpp"); } - else if(language->get_id() == "opencl") { + else if(language->get_id() == "opencl") language = language_manager->get_language("cpp"); - } return language; } @@ -170,46 +169,36 @@ Source::View::View(const boost::filesystem::path &file_path, const Glib::RefPtr< hide_tag = get_buffer()->create_tag(); hide_tag->property_scale() = 0.25; - if(language) { - auto language_id = language->get_id(); - if(language_id == "chdr" || language_id == "c") - is_c = true; - else if(language_id == "cpphdr" || language_id == "cpp") - is_cpp = true; - else if(language_id == "js" || language_id == "html") - is_js = true; - - if(is_c || is_cpp) { - use_fixed_continuation_indenting = false; - // TODO 2019: check if clang-format has improved... - // boost::filesystem::path clang_format_file; - // auto search_path=file_path.parent_path(); - // boost::system::error_code ec; - // while(true) { - // clang_format_file=search_path/".clang-format"; - // if(boost::filesystem::exists(clang_format_file, ec)) - // break; - // clang_format_file=search_path/"_clang-format"; - // if(boost::filesystem::exists(clang_format_file, ec)) - // break; - // clang_format_file.clear(); - - // if(search_path==search_path.root_directory()) - // break; - // search_path=search_path.parent_path(); - // } - // if(!clang_format_file.empty()) { - // auto lines=filesystem::read_lines(clang_format_file); - // for(auto &line: lines) { - // std::cout << "1" << std::endl; - // if(!line.empty() && line.compare(0, 23, "ContinuationIndentWidth")==0) { - // std::cout << "2" << std::endl; - // use_continuation_indenting=true; - // break; - // } - // } - // } - } + if(is_c || is_cpp) { + use_fixed_continuation_indenting = false; + // TODO 2019: check if clang-format has improved... + // boost::filesystem::path clang_format_file; + // auto search_path=file_path.parent_path(); + // boost::system::error_code ec; + // while(true) { + // clang_format_file=search_path/".clang-format"; + // if(boost::filesystem::exists(clang_format_file, ec)) + // break; + // clang_format_file=search_path/"_clang-format"; + // if(boost::filesystem::exists(clang_format_file, ec)) + // break; + // clang_format_file.clear(); + + // if(search_path==search_path.root_directory()) + // break; + // search_path=search_path.parent_path(); + // } + // if(!clang_format_file.empty()) { + // auto lines=filesystem::read_lines(clang_format_file); + // for(auto &line: lines) { + // std::cout << "1" << std::endl; + // if(!line.empty() && line.compare(0, 23, "ContinuationIndentWidth")==0) { + // std::cout << "2" << std::endl; + // use_continuation_indenting=true; + // break; + // } + // } + // } } setup_signals(); @@ -219,15 +208,10 @@ Source::View::View(const boost::filesystem::path &file_path, const Glib::RefPtr< std::string comment_characters; if(is_bracket_language) comment_characters = "//"; - else if(language) { - auto language_id = language->get_id(); - if(language_id == "cmake" || language_id == "makefile" || language_id == "python" || - language_id == "python3" || language_id == "sh" || language_id == "perl" || - language_id == "ruby" || language_id == "r" || language_id == "asm" || - language_id == "automake" || language_id == "yaml" || language_id == "docker" || - language_id == "julia") + else { + if(is_language({"cmake", "makefile", "python", "python3", "sh", "perl", "ruby", "r", "asm", "automake", "yaml", "docker", "julia"})) comment_characters = "#"; - else if(language_id == "latex" || language_id == "matlab" || language_id == "octave" || language_id == "bibtex") + else if(is_language({"latex", "matlab", "octave", "bibtex"})) comment_characters = "%"; else if(language_id == "fortran") comment_characters = "!"; @@ -451,11 +435,10 @@ void Source::View::configure() { set_draw_spaces(parse_show_whitespace_characters(Config::get().source.show_whitespace_characters)); { // Set Word Wrap - auto language_id = language ? language->get_id() : ""; namespace qi = boost::spirit::qi; std::set word_wrap_language_ids; qi::phrase_parse(Config::get().source.word_wrap.begin(), Config::get().source.word_wrap.end(), (+(~qi::char_(','))) % ',', qi::space, word_wrap_language_ids); - if(std::any_of(word_wrap_language_ids.begin(), word_wrap_language_ids.end(), [&language_id](const std::string &word_wrap_language_id) { + if(std::any_of(word_wrap_language_ids.begin(), word_wrap_language_ids.end(), [this](const std::string &word_wrap_language_id) { return word_wrap_language_id == language_id || word_wrap_language_id == "all"; })) set_wrap_mode(Gtk::WrapMode::WRAP_WORD_CHAR); @@ -730,8 +713,7 @@ void Source::View::setup_signals() { void Source::View::setup_format_style(bool is_generic_view) { static auto prettier = filesystem::find_executable("prettier"); - auto prefer_prettier = language && (language->get_id() == "js" || language->get_id() == "json" || language->get_id() == "css" || language->get_id() == "html" || - language->get_id() == "markdown" || language->get_id() == "yaml"); + auto prefer_prettier = is_language({"js", "json", "css", "html", "markdown", "yaml"}); if(prettier.empty() && prefer_prettier) { static bool shown = false; if(!shown) { @@ -1566,7 +1548,7 @@ void Source::View::extend_selection() { // Attempt to select a sentence, for instance: int a = 2; if(!is_bracket_language) { // If for instance cmake, meson or python if(!select_matching_brackets) { - bool select_end_block = language->get_id() == "cmake" || language->get_id() == "meson"; + bool select_end_block = is_language({"cmake", "meson"}); auto get_tabs = [this](Gtk::TextIter iter) -> boost::optional { iter = get_buffer()->get_iter_at_line(iter.get_line()); @@ -1665,7 +1647,7 @@ void Source::View::extend_selection() { } // Select no_spellcheck_tag block if markdown - if(no_spellcheck_tag && language->get_id() == "markdown" && start_stored.has_tag(no_spellcheck_tag) && end_stored.has_tag(no_spellcheck_tag) && + if(no_spellcheck_tag && language_id == "markdown" && start_stored.has_tag(no_spellcheck_tag) && end_stored.has_tag(no_spellcheck_tag) && (!start.has_tag(no_spellcheck_tag) || !end.has_tag(no_spellcheck_tag))) { start = start_stored; end = end_stored; @@ -2372,7 +2354,7 @@ bool Source::View::on_key_press_event_basic(GdkEventKey *event) { auto tabs = get_line_before(tabs_end_iter); // Python indenting after : - if(*condition_iter == ':' && language && language->get_id() == "python") { + if(*condition_iter == ':' && language_id == "python") { get_buffer()->insert_at_cursor('\n' + tabs + tab); scroll_to(get_buffer()->get_insert()); return true; @@ -3241,7 +3223,7 @@ bool Source::View::on_key_press_event_smart_inserts(GdkEventKey *event) { left = "/*"; right = "*/"; } - else if((language && language->get_id() == "markdown") || + else if(language_id == "markdown" || !is_code_iter(get_buffer()->get_insert()->get_iter()) || !is_code_iter(get_buffer()->get_selection_bound()->get_iter())) { // Insert `` around selection if(event->keyval == GDK_KEY_dead_grave) { diff --git a/src/source.hpp b/src/source.hpp index c81bc42..1369486 100644 --- a/src/source.hpp +++ b/src/source.hpp @@ -173,11 +173,6 @@ namespace Source { bool interactive_completion = true; - bool is_c = false; - bool is_cpp = false; - /// Set to true if language is html or js (including typescript) - bool is_js = false; - private: void setup_signals(); void setup_format_style(bool is_generic_view); diff --git a/src/source_base.cpp b/src/source_base.cpp index f5b9003..9aacbc8 100644 --- a/src/source_base.cpp +++ b/src/source_base.cpp @@ -10,7 +10,15 @@ #include #include -Source::CommonView::CommonView(const Glib::RefPtr &language) : Gsv::View(), language(language) { +Source::CommonView::CommonView(const Glib::RefPtr &language) : Gsv::View() { + set_language(language); + if(is_language({"chdr", "c"})) + is_c = true; + else if(is_language({"cpphdr", "cpp"})) + is_cpp = true; + else if(is_language({"js", "html"})) + is_js = true; + 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); @@ -23,6 +31,49 @@ Source::CommonView::~CommonView() { g_clear_object(&search_settings); } +void Source::CommonView::set_language(const Glib::RefPtr &language) { + this->language = language; + language_id = language ? language->get_id() : ""; +} + +bool Source::CommonView::is_language(const std::initializer_list &languages) { + if(!language) + return false; + return std::any_of(languages.begin(), languages.end(), [this](const std::string &e) { + return e == language_id; + }); +} + +bool Source::CommonView::on_key_press_event(GdkEventKey *event) { + if(event->keyval == GDK_KEY_Home && event->state & GDK_CONTROL_MASK) { + auto iter = get_buffer()->begin(); + scroll_to(iter); // Home key should always scroll to start, even though cursor does not move + return Gsv::View::on_key_press_event(event); + } + else if(event->keyval == GDK_KEY_End && event->state & GDK_CONTROL_MASK) { + auto iter = get_buffer()->end(); + scroll_to(iter); // End key should always scroll to start, even though cursor does not move + return Gsv::View::on_key_press_event(event); + } + return Gsv::View::on_key_press_event(event); +} + +bool Source::CommonView::on_motion_notify_event(GdkEventMotion *event) { + // Workaround for drag-and-drop crash on MacOS + // TODO 2023: check if this bug has been fixed +#ifdef __APPLE__ + if((event->state & GDK_BUTTON1_MASK) > 0 && (event->state & GDK_SHIFT_MASK) == 0) { + 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(iter, get_buffer()->get_selection_bound()->get_iter()); + return true; + } +#endif + return Gsv::View::on_motion_notify_event(event); +} + void Source::CommonView::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); @@ -122,36 +173,6 @@ void Source::CommonView::search_occurrences_updated(GtkWidget *widget, GParamSpe view->update_search_occurrences(gtk_source_search_context_get_occurrences_count(view->search_context)); } -bool Source::CommonView::on_key_press_event(GdkEventKey *event) { - if(event->keyval == GDK_KEY_Home && event->state & GDK_CONTROL_MASK) { - auto iter = get_buffer()->begin(); - scroll_to(iter); // Home key should always scroll to start, even though cursor does not move - return Gsv::View::on_key_press_event(event); - } - else if(event->keyval == GDK_KEY_End && event->state & GDK_CONTROL_MASK) { - auto iter = get_buffer()->end(); - scroll_to(iter); // End key should always scroll to start, even though cursor does not move - return Gsv::View::on_key_press_event(event); - } - return Gsv::View::on_key_press_event(event); -} - -bool Source::CommonView::on_motion_notify_event(GdkEventMotion *event) { - // Workaround for drag-and-drop crash on MacOS - // TODO 2023: check if this bug has been fixed -#ifdef __APPLE__ - if((event->state & GDK_BUTTON1_MASK) > 0 && (event->state & GDK_SHIFT_MASK) == 0) { - 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(iter, get_buffer()->get_selection_bound()->get_iter()); - return true; - } -#endif - return Gsv::View::on_motion_notify_event(event); -} - void Source::CommonView::cut() { if(!get_editable()) return copy(); @@ -163,7 +184,7 @@ void Source::CommonView::cut() { if(!get_buffer()->get_has_selection()) cut_lines(); - else if(language && language->get_id() == "diff") { + else if(language_id == "diff") { Gtk::TextIter start, end; get_buffer()->get_selection_bounds(start, end); Glib::ustring selection; @@ -201,7 +222,7 @@ void Source::CommonView::cut_lines() { end.forward_to_line_end(); end.forward_char(); - if(language && language->get_id() == "diff") { + if(language_id == "diff") { Glib::ustring selection; selection.reserve(end.get_offset() - start.get_offset()); for(auto iter = start; iter < end; ++iter) { @@ -230,7 +251,7 @@ void Source::CommonView::cut_lines() { void Source::CommonView::copy() { if(!get_buffer()->get_has_selection()) copy_lines(); - else if(language && language->get_id() == "diff") { + else if(language_id == "diff") { Gtk::TextIter start, end; get_buffer()->get_selection_bounds(start, end); Glib::ustring selection; @@ -258,7 +279,7 @@ void Source::CommonView::copy_lines() { end.forward_to_line_end(); end.forward_char(); - if(language && language->get_id() == "diff") { + if(language_id == "diff") { Glib::ustring selection; selection.reserve(end.get_offset() - start.get_offset()); for(auto iter = start; iter < end; ++iter) { @@ -292,14 +313,8 @@ Source::BaseView::BaseView(const boost::filesystem::path &file_path, const Glib: if(language) { get_source_buffer()->set_language(language); get_source_buffer()->set_highlight_syntax(true); - auto language_id = language->get_id(); - if(language_id == "chdr" || language_id == "cpphdr" || language_id == "c" || - language_id == "cpp" || language_id == "objc" || language_id == "java" || - language_id == "js" || language_id == "ts" || language_id == "proto" || - language_id == "c-sharp" || language_id == "html" || language_id == "cuda" || - language_id == "php" || language_id == "rust" || language_id == "swift" || - language_id == "go" || language_id == "scala" || language_id == "opencl" || - language_id == "json" || language_id == "css" || language_id == "glsl") + if(is_language({"chdr", "cpphdr", "c", "cpp", "objc", "java", "js", "proto", "c-sharp", "html", "cuda", + "php", "rust", "swift", "go", "scala", "opencl", "json", "css", "glsl"})) is_bracket_language = true; } @@ -307,8 +322,7 @@ Source::BaseView::BaseView(const boost::filesystem::path &file_path, const Glib: tab_char = Config::get().source.default_tab_char; tab_size = Config::get().source.default_tab_size; if(language) { - auto language_id = language->get_id(); - if(language_id == "python" || language_id == "rust" || language_id == "julia") { + if(is_language({"python", "rust", "julia"})) { tab_char = ' '; tab_size = 4; } @@ -564,7 +578,7 @@ std::pair Source::BaseView::find_tab_char_and_size() { 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")) { + if(is_bracket_language && language_id != "html") { bool line_comment = false; bool comment = false; bool bracket_last_line = false; @@ -993,7 +1007,7 @@ void Source::BaseView::paste() { first_paste_line_has_tabs = true; paste_line_tabs = tabs; } - else if(language && language->get_id() == "python") { // Special case for Python code where the first line ends with ':' + else if(language_id == "python") { // Special case for Python code where the first line ends with ':' char last_char = 0; for(auto &chr : line) { if(chr != ' ' && chr != '\t') @@ -1505,10 +1519,10 @@ void Source::BaseView::set_snippets() { snippets = nullptr; - if(language) { + if(!language_id.empty()) { for(auto &pair : Snippets::get().snippets) { std::smatch sm; - if(std::regex_match(language->get_id().raw(), sm, pair.first)) { + if(std::regex_match(language_id, sm, pair.first)) { snippets = &pair.second; break; } diff --git a/src/source_base.hpp b/src/source_base.hpp index d957fde..43741f5 100644 --- a/src/source_base.hpp +++ b/src/source_base.hpp @@ -32,19 +32,35 @@ namespace Source { public: CommonView(const Glib::RefPtr &language = {}); ~CommonView() override; - void search_highlight(const std::string &text, bool case_sensitive, bool regex); - 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); + protected: Glib::RefPtr language; + public: + std::string language_id; + void set_language(const Glib::RefPtr &language); + /// Checks if parameter languages contain language_id + bool is_language(const std::initializer_list &languages); + protected: + bool is_c = false; + bool is_cpp = false; + /// Set to true if language is html or js (including typescript) + bool is_js = false; + bool keep_clipboard = false; + bool on_key_press_event(GdkEventKey *event) override; + bool on_motion_notify_event(GdkEventMotion *motion_event) override; + public: + void search_highlight(const std::string &text, bool case_sensitive, bool regex); + 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 cut(); void cut_lines(); void copy(); @@ -53,10 +69,6 @@ namespace Source { std::function update_search_occurrences; - protected: - bool on_key_press_event(GdkEventKey *event) override; - bool on_motion_notify_event(GdkEventMotion *motion_event) override; - private: GtkSourceSearchContext *search_context; GtkSourceSearchSettings *search_settings; diff --git a/src/source_clang.cpp b/src/source_clang.cpp index 29e618c..87d52c4 100644 --- a/src/source_clang.cpp +++ b/src/source_clang.cpp @@ -30,7 +30,7 @@ Source::ClangViewParse::ClangViewParse(const boost::filesystem::path &file_path, syntax_tags.emplace(item.first, tag); } - if(get_buffer()->size() == 0 && (language->get_id() == "chdr" || language->get_id() == "cpphdr")) { + if(get_buffer()->size() == 0 && is_language({"chdr", "cpphdr"})) { disable_spellcheck = true; get_buffer()->insert_at_cursor("#pragma once\n"); disable_spellcheck = false; @@ -53,7 +53,7 @@ bool Source::ClangViewParse::save() { if(!Source::View::save()) return false; - if(language->get_id() == "chdr" || language->get_id() == "cpphdr") { + if(is_language({"chdr", "cpphdr"})) { for(auto &view : views) { if(auto clang_view = dynamic_cast(view)) { if(this != clang_view) @@ -115,7 +115,7 @@ void Source::ClangViewParse::parse_initialize() { } } - if(language && (language->get_id() == "chdr" || language->get_id() == "cpphdr")) + if(is_language({"chdr", "cpphdr"})) clangmm::remove_include_guard(buffer_raw); auto build = Project::Build::create(file_path); @@ -161,7 +161,7 @@ void Source::ClangViewParse::parse_initialize() { } else if(parse_process_state == ParseProcessState::processing && parse_mutex.try_lock()) { auto &parse_thread_buffer_raw = const_cast(parse_thread_buffer.raw()); - if(this->language && (this->language->get_id() == "chdr" || this->language->get_id() == "cpphdr")) + if(is_language({"chdr", "cpphdr"})) clangmm::remove_include_guard(parse_thread_buffer_raw); auto status = clang_tu->reparse(parse_thread_buffer_raw); if(status == 0) { @@ -949,7 +949,7 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa }; 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")) + if(is_language({"chdr", "cpphdr"})) clangmm::remove_include_guard(buffer); code_complete_results = std::make_unique(clang_tu->get_code_completions(buffer, line_number, column)); if(!code_complete_results->cx_results) @@ -2141,7 +2141,7 @@ void Source::ClangView::async_delete() { if(stream) { std::string buffer; buffer.assign(std::istreambuf_iterator(stream), std::istreambuf_iterator()); - if(language && (language->get_id() == "chdr" || language->get_id() == "cpphdr")) + if(is_language({"chdr", "cpphdr"})) clangmm::remove_include_guard(buffer); clang_tu->reparse(buffer); clang_tokens = clang_tu->get_tokens(); diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 3a483ba..748892e 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -568,7 +568,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { style_file_search_path = style_file_search_path.parent_path(); } - if(!has_style_file && language && language->get_id() == "rust") { + if(!has_style_file && language_id == "rust") { auto style_file_search_path = file_path.parent_path(); while(true) { if(boost::filesystem::exists(style_file_search_path / "rustfmt.toml", ec) || diff --git a/src/tooltips.cpp b/src/tooltips.cpp index 8f2c3eb..08f2f98 100644 --- a/src/tooltips.cpp +++ b/src/tooltips.cpp @@ -953,7 +953,7 @@ void Tooltip::insert_code(const std::string &code, boost::variant(view)) - language = source_view->language; + language = Source::LanguageManager::get_default()->get_language(source_view->language_id); } } } @@ -1225,10 +1225,8 @@ void Tooltip::insert_doxygen(const std::string &input_, bool remove_delimiters) auto token = get_token(); if(token == "endcode") { if(language_id.empty() && view) { - if(auto source_view = dynamic_cast(view)) { - if(source_view->language) - language_id = source_view->language->get_id(); - } + if(auto source_view = dynamic_cast(view)) + language_id = source_view->language_id; } markdown += "```" + language_id + '\n' + input.substr(start, end - start) + "\n```\n"; ++i; diff --git a/tests/source_clang_test.cpp b/tests/source_clang_test.cpp index 46f931b..32e23fb 100644 --- a/tests/source_clang_test.cpp +++ b/tests/source_clang_test.cpp @@ -88,7 +88,7 @@ int main() { // test remove_include_guard { - clang_view->language = Source::LanguageManager::get_default()->get_language("chdr"); + clang_view->set_language(Source::LanguageManager::get_default()->get_language("chdr")); std::string source = "#ifndef F\n#define F\n#endif // F"; clangmm::remove_include_guard(source); g_assert_cmpstr(source.c_str(), ==, " \n \n "); From 9a67a7557ef58e537e5b7303bf27a180d97102e7 Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 9 Jun 2021 20:01:23 +0200 Subject: [PATCH 126/375] Improved extend selection for non-bracket languages like Python and Julia --- src/source.cpp | 181 ++++++++++++++++++++++++++++++------------------- 1 file changed, 110 insertions(+), 71 deletions(-) diff --git a/src/source.cpp b/src/source.cpp index 80073b8..527ca5f 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -1548,7 +1548,7 @@ void Source::View::extend_selection() { // Attempt to select a sentence, for instance: int a = 2; if(!is_bracket_language) { // If for instance cmake, meson or python if(!select_matching_brackets) { - bool select_end_block = is_language({"cmake", "meson"}); + bool select_end_block = is_language({"cmake", "meson", "julia"}); auto get_tabs = [this](Gtk::TextIter iter) -> boost::optional { iter = get_buffer()->get_iter_at_line(iter.get_line()); @@ -1563,94 +1563,134 @@ void Source::View::extend_selection() { return tabs; }; - // Forward start to non-empty line start = start_stored; + end = end_stored; + + if(start == get_buffer()->begin() && end == get_buffer()->end()) { + get_buffer()->select_range(start, end); + return; + } + + // Select following line with code (for instance when inside comment) + // Forward start to non-empty line forward_to_code(start); start = get_buffer()->get_iter_at_line(start.get_line()); while(!start.is_end() && (*start == ' ' || *start == '\t') && start.forward_char()) { } - - // Forward end to end of line - end = end_stored; + // Forward end of line if(start > end) end = start; - if(!end.ends_line()) - end.forward_to_line_end(); - - // Try select block that starts at cursor - auto iter = end; - if(auto end_tabs = get_tabs(end)) { - bool can_select_end_block = false; - while(iter.forward_char()) { - auto tabs = get_tabs(iter); - if(!tabs || tabs > end_tabs || (select_end_block && can_select_end_block && tabs == end_tabs)) { - if(!iter.ends_line()) - iter.forward_to_line_end(); - end = iter; - if(tabs > end_tabs) - can_select_end_block = true; - else if(tabs == end_tabs) - break; - continue; - } - break; - } + end = get_iter_at_line_end(end.get_line()); + while(end.backward_char() && (*end == ' ' || *end == '\t' || end.ends_line())) { } - while(end > end_stored && end.starts_line() && end.ends_line() && end.backward_char()) { + end.forward_char(); + if(end == end_stored) // Cancel line selection if the line is already selected + start = start_stored; + + if(start != start_stored || end != end_stored) { + get_buffer()->select_range(start, end); + return; } - if(start == start_stored && end == end_stored) { // Try select block that cursor is within - // Backward start to line with less indentation - auto iter = get_buffer()->get_iter_at_line(start.get_line()); - auto start_tabs = get_tabs(iter); - if(start_tabs >= 0) { - while(iter.backward_char()) { - auto tabs = get_tabs(iter); - iter = get_buffer()->get_iter_at_line(iter.get_line()); - if(tabs >= 0 && tabs < start_tabs) { - start = iter; + // Select current line + // Backward to line start + start = get_buffer()->get_iter_at_line(start.get_line()); + auto start_tabs = get_tabs(start); + while((*start == ' ' || *start == '\t' || start.ends_line()) && start.forward_char()) { + } + // Forward to line end + end = get_iter_at_line_end(end.get_line()); + bool include_children = false; + if(start_tabs) { + while(end.forward_char()) { + auto tabs = get_tabs(end); + if(tabs) { + if(tabs > start_tabs) + include_children = true; + else if(tabs == start_tabs) { + if(include_children && select_end_block) + end = get_iter_at_line_end(end.get_line()); break; } + else + break; } + end = get_iter_at_line_end(end.get_line()); } - // Forward start to non-empty line + } + // Backward end to non-empty line + while(end.backward_char() && (*end == ' ' || *end == '\t' || end.ends_line())) { + } + end.forward_char(); + + if(start != start_stored || end != end_stored) { + get_buffer()->select_range(start, end); + return; + } + + // Select block that cursor is within + // Backward start block start + if(start_tabs > 0) { start = get_buffer()->get_iter_at_line(start.get_line()); - while(!start.is_end() && (*start == ' ' || *start == '\t') && start.forward_char()) { - } - - if(start != start_stored) { - // Forward end through lines with higher indentation - start_tabs = get_tabs(start); - iter = end; - if(start_tabs >= 0) { - while(iter.forward_char()) { - auto tabs = get_tabs(iter); - if(tabs < 0 || tabs > start_tabs || (select_end_block && tabs == start_tabs)) { - if(!iter.ends_line()) - iter.forward_to_line_end(); - end = iter; - if(tabs == start_tabs) - break; - continue; - } - break; + while(start.backward_char()) { + auto tabs = get_tabs(start); + if(tabs && tabs < start_tabs) + break; + start = get_buffer()->get_iter_at_line(start.get_line()); + } + while((*start == ' ' || *start == '\t' || start.ends_line()) && start.forward_char()) { + } + // Forward to block end + end = get_iter_at_line_end(end.get_line()); + while(end.forward_char()) { + auto tabs = get_tabs(end); + if(tabs && tabs < start_tabs) + break; + end = get_iter_at_line_end(end.get_line()); + } + // Backward end to non-empty line + while(end.backward_char() && (*end == ' ' || *end == '\t' || end.ends_line())) { + } + end.forward_char(); + + if(start != start_stored || end != end_stored) { + get_buffer()->select_range(start, end); + return; + } + + // Select expression surrounding block + // Backward to expression starting block + if(start.backward_char()) { + backward_to_code(start); + if(start_tabs > get_tabs(start)) { + start = get_buffer()->get_iter_at_line(start.get_line()); + while((*start == ' ' || *start == '\t' || start.ends_line()) && start.forward_char()) { } } - while(end > end_stored && end.starts_line() && end.ends_line() && end.backward_char()) { + else + start = start_stored; + } + // Forward to expression ending block + if(select_end_block) { + forward_to_code(end); + if(start_tabs > get_tabs(end)) { + end = get_iter_at_line_end(end.get_line()); + while(end.backward_char() && (*end == ' ' || *end == '\t' || end.ends_line())) { + } + end.forward_char(); } + else + end = end_stored; } - if(start == start_stored && end == end_stored) { - start = get_buffer()->begin(); - end = get_buffer()->end(); + if(start != start_stored || end != end_stored) { + get_buffer()->select_range(start, end); + return; } } // Select no_spellcheck_tag block if markdown - if(no_spellcheck_tag && language_id == "markdown" && start_stored.has_tag(no_spellcheck_tag) && end_stored.has_tag(no_spellcheck_tag) && - (!start.has_tag(no_spellcheck_tag) || !end.has_tag(no_spellcheck_tag))) { - start = start_stored; - end = end_stored; + if(language_id == "markdown" && no_spellcheck_tag && start.has_tag(no_spellcheck_tag) && end.has_tag(no_spellcheck_tag)) { if(!start.starts_tag(no_spellcheck_tag)) start.backward_to_tag_toggle(no_spellcheck_tag); if(!end.ends_tag(no_spellcheck_tag)) @@ -1667,13 +1707,13 @@ void Source::View::extend_selection() { if(!end.ends_line()) end.forward_char(); - if(start == start_stored && end == end_stored) { - start = get_buffer()->begin(); - end = get_buffer()->end(); + if(start != start_stored || end != end_stored) { + get_buffer()->select_range(start, end); + return; } } - get_buffer()->select_range(start, end); + get_buffer()->select_range(get_buffer()->begin(), get_buffer()->end()); return; } } @@ -1709,9 +1749,8 @@ void Source::View::extend_selection() { if(start == start_stored && end == end_stored) { // In case of no change due to inbalanced brackets previous_extended_selections.pop_back(); - if(!start.backward_char() && !end.forward_char()) { + if(!start.backward_char() && !end.forward_char()) return; - } get_buffer()->select_range(start, end); extend_selection(); return; From 34eadaa2e42ff3c17c1ed28d3ef2d67b2322ab4e Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 9 Jun 2021 21:04:37 +0200 Subject: [PATCH 127/375] Minor improvement to extend selection for non-bracket languages --- src/source.cpp | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/source.cpp b/src/source.cpp index 527ca5f..fc98f8a 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -1624,6 +1624,50 @@ void Source::View::extend_selection() { end.forward_char(); if(start != start_stored || end != end_stored) { + // Forward to closing symbol if open symbol is found between start and end + auto iter = start; + para_count = 0; + square_count = 0; + 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(para_count < 0 || square_count < 0 || curly_count < 0) + break; + } while(iter.forward_char() && iter < end); + if(iter == end && (para_count > 0 || square_count > 0 || 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(para_count == 0 && square_count == 0 && curly_count == 0) + break; + } while(iter.forward_char()); + if(iter) { + end = iter; + end.forward_char(); + } + } + get_buffer()->select_range(start, end); return; } From bfff0bba2bee7ee9806013fa5a767de03be4ee74 Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 9 Jun 2021 23:08:18 +0200 Subject: [PATCH 128/375] Tab width set to 4 in terminal as well --- src/source_base.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/source_base.cpp b/src/source_base.cpp index 9aacbc8..b905780 100644 --- a/src/source_base.cpp +++ b/src/source_base.cpp @@ -11,6 +11,8 @@ #include Source::CommonView::CommonView(const Glib::RefPtr &language) : Gsv::View() { + set_tab_width(4); // Visual size of a \t hardcoded to be equal to visual size of 4 spaces + set_language(language); if(is_language({"chdr", "c"})) is_c = true; @@ -318,7 +320,6 @@ Source::BaseView::BaseView(const boost::filesystem::path &file_path, const Glib: is_bracket_language = true; } - set_tab_width(4); // Visual size of a \t hardcoded to be equal to visual size of 4 spaces tab_char = Config::get().source.default_tab_char; tab_size = Config::get().source.default_tab_size; if(language) { From 31d86b22babf78ae4a78e8925dcd65b21f765a38 Mon Sep 17 00:00:00 2001 From: eidheim Date: Thu, 10 Jun 2021 12:59:49 +0200 Subject: [PATCH 129/375] Minor cleanup of extend_selection() --- src/source.cpp | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/source.cpp b/src/source.cpp index fc98f8a..9935ed7 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -1164,6 +1164,11 @@ void Source::View::extend_selection() { Gtk::TextIter start, end; get_buffer()->get_selection_bounds(start, end); + + // If entire buffer is selected, do nothing + if(start == get_buffer()->begin() && end == get_buffer()->end()) + return; + auto start_stored = start; auto end_stored = end; @@ -1566,11 +1571,6 @@ void Source::View::extend_selection() { start = start_stored; end = end_stored; - if(start == get_buffer()->begin() && end == get_buffer()->end()) { - get_buffer()->select_range(start, end); - return; - } - // Select following line with code (for instance when inside comment) // Forward start to non-empty line forward_to_code(start); @@ -1780,28 +1780,29 @@ void Source::View::extend_selection() { } } + // Select sentence end_sentence_iter.forward_char(); - if((end_sentence_iter != end_stored || start_sentence_iter != start_stored) && + if((start_sentence_iter != start_stored || end_sentence_iter != end_stored) && ((*start == '{' && *end == '}') || (start.is_start() && end.is_end()))) { - start = start_sentence_iter; - end = end_sentence_iter; - select_matching_brackets = false; + get_buffer()->select_range(start_sentence_iter, end_sentence_iter); + return; } } + if(select_matching_brackets) start.forward_char(); - if(start == start_stored && end == end_stored) { // In case of no change due to inbalanced brackets - previous_extended_selections.pop_back(); - if(!start.backward_char() && !end.forward_char()) - return; + if(start != start_stored || end != end_stored) { get_buffer()->select_range(start, end); - extend_selection(); return; } + // In case of no change due to inbalanced brackets + previous_extended_selections.pop_back(); + if(!start.backward_char() && !end.forward_char()) + return; get_buffer()->select_range(start, end); - return; + extend_selection(); } void Source::View::shrink_selection() { From aa91e38bfaab01dde908eb55541654c4a87dcd59 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 11 Jun 2021 13:19:55 +0200 Subject: [PATCH 130/375] Added language protocol tests --- src/source_language_protocol.cpp | 3 + src/source_language_protocol.hpp | 2 + tests/CMakeLists.txt | 6 + tests/language_protocol_client_test.cpp | 55 +++ tests/language_protocol_server_test.cpp | 359 ++++++++++++++++++ .../.rustfmt.toml | 0 tests/language_protocol_test_files/main.rs | 3 + tests/stubs/notebook.cpp | 4 + 8 files changed, 432 insertions(+) create mode 100644 tests/language_protocol_client_test.cpp create mode 100644 tests/language_protocol_server_test.cpp create mode 100644 tests/language_protocol_test_files/.rustfmt.toml create mode 100644 tests/language_protocol_test_files/main.rs diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 748892e..bd6ee53 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -119,6 +119,9 @@ LanguageProtocol::Client::~Client() { process->kill(); exit_status = process->get_exit_status(); } + + if(on_exit_status) + on_exit_status(exit_status); if(Config::get().log.language_server) std::cout << "Language server exit status: " << exit_status << std::endl; } diff --git a/src/source_language_protocol.hpp b/src/source_language_protocol.hpp index b4638c9..c581a8a 100644 --- a/src/source_language_protocol.hpp +++ b/src/source_language_protocol.hpp @@ -155,6 +155,8 @@ namespace LanguageProtocol { void write_notification(const std::string &method, const std::string ¶ms); void handle_server_notification(const std::string &method, const boost::property_tree::ptree ¶ms); void handle_server_request(size_t id, const std::string &method, const boost::property_tree::ptree ¶ms); + + std::function on_exit_status; }; } // namespace LanguageProtocol diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9d1c817..c07a884 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -84,6 +84,12 @@ if(BUILD_TESTING) add_executable(utility_test utility_test.cpp $) target_link_libraries(utility_test juci_shared) add_test(utility_test utility_test) + + add_executable(language_protocol_client_test language_protocol_client_test.cpp $) + target_link_libraries(language_protocol_client_test juci_shared) + add_test(language_protocol_client_test language_protocol_client_test) + + add_executable(language_protocol_server_test language_protocol_server_test.cpp) endif() if(BUILD_FUZZING) diff --git a/tests/language_protocol_client_test.cpp b/tests/language_protocol_client_test.cpp new file mode 100644 index 0000000..a5bb7dc --- /dev/null +++ b/tests/language_protocol_client_test.cpp @@ -0,0 +1,55 @@ +#include "config.hpp" +#include "source_language_protocol.hpp" +#include + +//Requires display server to work +//However, it is possible to use the Broadway backend if the test is run in a pure terminal environment: +//broadwayd& +//make test + +void flush_events() { + while(Gtk::Main::events_pending()) + Gtk::Main::iteration(); +} + +int main() { + auto app = Gtk::Application::create(); + Gsv::init(); + + auto tests_path = boost::filesystem::canonical(JUCI_TESTS_PATH); + auto build_path = boost::filesystem::canonical(JUCI_BUILD_PATH); + + auto view = new Source::LanguageProtocolView(boost::filesystem::canonical(tests_path / "language_protocol_test_files" / "main.rs"), + Source::LanguageManager::get_default()->get_language("rust"), + "rust", + (build_path / "tests" / "language_protocol_server_test").string()); + + while(!view->initialized) + flush_events(); + + g_assert(view->capabilities.document_formatting); + + view->get_buffer()->insert_at_cursor(" "); + g_assert(view->get_buffer()->get_text() == R"( fn main() { + println!("Hello, world!"); +} +)"); + + view->format_style(false); + g_assert(view->get_buffer()->get_text() == R"(fn main() { + println!("Hello, world!"); +} +)"); + + std::atomic exit_status(-1); + view->client->on_exit_status = [&exit_status](int exit_status_) { + exit_status = exit_status_; + }; + + delete view; + + while(exit_status == -1) + flush_events(); + + g_assert_cmpint(exit_status, ==, 0); +} diff --git a/tests/language_protocol_server_test.cpp b/tests/language_protocol_server_test.cpp new file mode 100644 index 0000000..98d04de --- /dev/null +++ b/tests/language_protocol_server_test.cpp @@ -0,0 +1,359 @@ +#include +#include + +int main() { + std::string line; + try { + // Read initialize and respond + { + std::getline(std::cin, line); + auto size = std::atoi(line.substr(16).c_str()); + std::getline(std::cin, line); + std::string buffer; + buffer.resize(size); + std::cin.read(&buffer[0], size); + std::stringstream ss(buffer); + boost::property_tree::ptree pt; + boost::property_tree::json_parser::read_json(ss, pt); + if(pt.get("method") != "initialize") + return 1; + + std::string result = R"({ + "jsonrpc": "2.0", + "id": "0", + "result": { + "capabilities": { + "textDocumentSync": { + "openClose": "true", + "change": "2", + "save": "" + }, + "selectionRangeProvider": "true", + "hoverProvider": "true", + "completionProvider": { + "triggerCharacters": [ + ":", + ".", + "'" + ] + }, + "signatureHelpProvider": { + "triggerCharacters": [ + "(", + "," + ] + }, + "definitionProvider": "true", + "typeDefinitionProvider": "true", + "implementationProvider": "true", + "referencesProvider": "true", + "documentHighlightProvider": "true", + "documentSymbolProvider": "true", + "workspaceSymbolProvider": "true", + "codeActionProvider": { + "codeActionKinds": [ + "", + "quickfix", + "refactor", + "refactor.extract", + "refactor.inline", + "refactor.rewrite" + ], + "resolveProvider": "true" + }, + "codeLensProvider": { + "resolveProvider": "true" + }, + "documentFormattingProvider": "true", + "documentOnTypeFormattingProvider": { + "firstTriggerCharacter": "=", + "moreTriggerCharacter": [ + ".", + ">", + "{" + ] + }, + "renameProvider": { + "prepareProvider": "true" + }, + "foldingRangeProvider": "true", + "workspace": { + "fileOperations": { + "willRename": { + "filters": [ + { + "scheme": "file", + "pattern": { + "glob": "**\/*.rs", + "matches": "file" + } + }, + { + "scheme": "file", + "pattern": { + "glob": "**", + "matches": "folder" + } + } + ] + } + } + }, + "callHierarchyProvider": "true", + "semanticTokensProvider": { + "legend": { + "tokenTypes": [ + "comment", + "keyword", + "string", + "number", + "regexp", + "operator", + "namespace", + "type", + "struct", + "class", + "interface", + "enum", + "enumMember", + "typeParameter", + "function", + "method", + "property", + "macro", + "variable", + "parameter", + "angle", + "arithmetic", + "attribute", + "bitwise", + "boolean", + "brace", + "bracket", + "builtinType", + "characterLiteral", + "colon", + "comma", + "comparison", + "constParameter", + "dot", + "escapeSequence", + "formatSpecifier", + "generic", + "label", + "lifetime", + "logical", + "operator", + "parenthesis", + "punctuation", + "selfKeyword", + "semicolon", + "typeAlias", + "union", + "unresolvedReference" + ], + "tokenModifiers": [ + "documentation", + "declaration", + "definition", + "static", + "abstract", + "deprecated", + "readonly", + "constant", + "controlFlow", + "injected", + "mutable", + "consuming", + "async", + "unsafe", + "attribute", + "trait", + "callable", + "intraDocLink" + ] + }, + "range": "true", + "full": { + "delta": "true" + } + }, + "experimental": { + "joinLines": "true", + "ssr": "true", + "onEnter": "true", + "parentModule": "true", + "runnables": { + "kinds": [ + "cargo" + ] + }, + "workspaceSymbolScopeKindFiltering": "true" + } + }, + "serverInfo": { + "name": "rust-analyzer", + "version": "3022a2c3a 2021-05-25 dev" + }, + "offsetEncoding": "utf-8" + } +})"; + std::cout << "Content-Length: " << result.size() << "\r\n\r\n" + << result; + } + + // Read initialized + { + std::getline(std::cin, line); + auto size = std::atoi(line.substr(16).c_str()); + std::getline(std::cin, line); + std::string buffer; + buffer.resize(size); + std::cin.read(&buffer[0], size); + std::stringstream ss(buffer); + boost::property_tree::ptree pt; + boost::property_tree::json_parser::read_json(ss, pt); + if(pt.get("method") != "initialized") + return 2; + } + + // Read textDocument/didOpen + { + std::getline(std::cin, line); + auto size = std::atoi(line.substr(16).c_str()); + std::getline(std::cin, line); + std::string buffer; + buffer.resize(size); + std::cin.read(&buffer[0], size); + std::stringstream ss(buffer); + boost::property_tree::ptree pt; + boost::property_tree::json_parser::read_json(ss, pt); + if(pt.get("method") != "textDocument/didOpen") + return 3; + } + + // Read textDocument/didChange + { + std::getline(std::cin, line); + auto size = std::atoi(line.substr(16).c_str()); + std::getline(std::cin, line); + std::string buffer; + buffer.resize(size); + std::cin.read(&buffer[0], size); + std::stringstream ss(buffer); + boost::property_tree::ptree pt; + boost::property_tree::json_parser::read_json(ss, pt); + if(pt.get("method") != "textDocument/didChange") + return 4; + } + + // Read and write textDocument/formatting + { + std::getline(std::cin, line); + auto size = std::atoi(line.substr(16).c_str()); + std::getline(std::cin, line); + std::string buffer; + buffer.resize(size); + std::cin.read(&buffer[0], size); + std::stringstream ss(buffer); + boost::property_tree::ptree pt; + boost::property_tree::json_parser::read_json(ss, pt); + if(pt.get("method") != "textDocument/formatting") + return 5; + + std::string result = R"({ + "jsonrpc": "2.0", + "id": "1", + "result": [ + { + "range": { + "start": { + "line": "0", + "character": "0" + }, + "end": { + "line": "0", + "character": "1" + } + }, + "newText": "" + } + ] +})"; + std::cout << "Content-Length: " << result.size() << "\r\n\r\n" + << result; + } + + // Read textDocument/didChange + { + std::getline(std::cin, line); + auto size = std::atoi(line.substr(16).c_str()); + std::getline(std::cin, line); + std::string buffer; + buffer.resize(size); + std::cin.read(&buffer[0], size); + std::stringstream ss(buffer); + boost::property_tree::ptree pt; + boost::property_tree::json_parser::read_json(ss, pt); + if(pt.get("method") != "textDocument/didChange") + return 6; + } + + // Read textDocument/didClose + { + std::getline(std::cin, line); + auto size = std::atoi(line.substr(16).c_str()); + std::getline(std::cin, line); + std::string buffer; + buffer.resize(size); + std::cin.read(&buffer[0], size); + std::stringstream ss(buffer); + boost::property_tree::ptree pt; + boost::property_tree::json_parser::read_json(ss, pt); + if(pt.get("method") != "textDocument/didClose") + return 7; + } + + // Read shutdown and respond + { + std::getline(std::cin, line); + auto size = std::atoi(line.substr(16).c_str()); + std::getline(std::cin, line); + std::string buffer; + buffer.resize(size); + std::cin.read(&buffer[0], size); + std::stringstream ss(buffer); + boost::property_tree::ptree pt; + boost::property_tree::json_parser::read_json(ss, pt); + if(pt.get("method") != "shutdown") + return 8; + + std::string result = R"({ + "jsonrpc": "2.0", + "id": "2", + "result": {} +})"; + std::cout << "Content-Length: " << result.size() << "\r\n\r\n" + << result; + } + + // Read exit + { + std::getline(std::cin, line); + auto size = std::atoi(line.substr(16).c_str()); + std::getline(std::cin, line); + std::string buffer; + buffer.resize(size); + std::cin.read(&buffer[0], size); + std::stringstream ss(buffer); + boost::property_tree::ptree pt; + boost::property_tree::json_parser::read_json(ss, pt); + if(pt.get("method") != "exit") + return 9; + } + } + catch(const std::exception &e) { + std::cerr << e.what() << std::endl; + return 100; + } +} diff --git a/tests/language_protocol_test_files/.rustfmt.toml b/tests/language_protocol_test_files/.rustfmt.toml new file mode 100644 index 0000000..e69de29 diff --git a/tests/language_protocol_test_files/main.rs b/tests/language_protocol_test_files/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/tests/language_protocol_test_files/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/tests/stubs/notebook.cpp b/tests/stubs/notebook.cpp index 8957310..5fe3f79 100644 --- a/tests/stubs/notebook.cpp +++ b/tests/stubs/notebook.cpp @@ -8,4 +8,8 @@ Source::View *Notebook::get_current_view() { bool Notebook::open(const boost::filesystem::path &file_path, Position position) { return true; } +bool Notebook::open(Source::View *view) { return true; } + void Notebook::open_uri(const std::string &uri) {} + +bool Notebook::close(Source::View *view) { return true; } From 300cac658924b28fd25a7af38b619abc379ec3e3 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 11 Jun 2021 19:50:33 +0200 Subject: [PATCH 131/375] Fixed MSYS2 test failure --- src/source_language_protocol.cpp | 44 +++++++++++++++++--------------- src/source_language_protocol.hpp | 1 + 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index bd6ee53..d45bbc1 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -456,6 +456,8 @@ void LanguageProtocol::Client::handle_server_request(size_t id, const std::strin Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path &file_path, const Glib::RefPtr &language, std::string language_id_, std::string language_server_) : Source::BaseView(file_path, language), Source::View(file_path, language), uri(filesystem::get_uri_from_path(file_path)), language_id(std::move(language_id_)), language_server(std::move(language_server_)), client(LanguageProtocol::Client::get(file_path, language_id, language_server)) { + uri_escaped = uri; + escape_text(uri_escaped); initialize(); } @@ -476,7 +478,7 @@ void Source::LanguageProtocolView::initialize() { 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 + "\"}"); + client->write_notification("textDocument/didOpen", R"("textDocument":{"uri":")" + uri_escaped + R"(","languageId":")" + language_id + R"(","version":)" + std::to_string(document_version++) + R"(,"text":")" + text + "\"}"); if(!initialized) { setup_signals(); @@ -521,7 +523,7 @@ void Source::LanguageProtocolView::close() { autocomplete->thread.join(); } - client->write_notification("textDocument/didClose", R"("textDocument":{"uri":")" + uri + "\"}"); + client->write_notification("textDocument/didClose", R"("textDocument":{"uri":")" + uri_escaped + "\"}"); client->close(this); client = nullptr; } @@ -537,6 +539,8 @@ void Source::LanguageProtocolView::rename(const boost::filesystem::path &path) { dispatcher.reset(); Source::DiffView::rename(path); uri = filesystem::get_uri_from_path(path); + uri_escaped = uri; + escape_text(uri_escaped); client = LanguageProtocol::Client::get(file_path, language_id, language_server); initialize(); } @@ -545,7 +549,7 @@ bool Source::LanguageProtocolView::save() { if(!Source::View::save()) return false; - client->write_notification("textDocument/didSave", R"("textDocument":{"uri":")" + uri + "\"}"); + client->write_notification("textDocument/didSave", R"("textDocument":{"uri":")" + uri_escaped + "\"}"); update_type_coverage(); @@ -599,11 +603,11 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { method = "textDocument/rangeFormatting"; Gtk::TextIter start, end; get_buffer()->get_selection_bounds(start, end); - params = R"("textDocument":{"uri":")" + uri + R"("},"range":{"start":{"line":)" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(end.get_line()) + ",\"character\":" + std::to_string(end.get_line_offset()) + "}},\"options\":{" + options + "}"; + params = R"("textDocument":{"uri":")" + uri_escaped + R"("},"range":{"start":{"line":)" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(end.get_line()) + ",\"character\":" + std::to_string(end.get_line_offset()) + "}},\"options\":{" + options + "}"; } else { method = "textDocument/formatting"; - params = R"("textDocument":{"uri":")" + uri + R"("},"options":{)" + options + "}"; + params = R"("textDocument":{"uri":")" + uri_escaped + R"("},"options":{)" + options + "}"; } client->write_request(this, method, params, [&text_edits, &result_processed](const boost::property_tree::ptree &result, bool error) { @@ -670,7 +674,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { else method = "textDocument/documentHighlight"; - client->write_request(this, method, R"("textDocument":{"uri":")" + uri + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "context": {"includeDeclaration": true})", [this, &locations, &result_processed](const boost::property_tree::ptree &result, bool error) { + client->write_request(this, method, R"("textDocument":{"uri":")" + uri_escaped + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "context": {"includeDeclaration": true})", [this, &locations, &result_processed](const boost::property_tree::ptree &result, bool error) { if(!error) { try { for(auto it = result.begin(); it != result.end(); ++it) @@ -795,7 +799,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { std::vector changes_vec; std::promise result_processed; if(capabilities.rename) { - client->write_request(this, "textDocument/rename", R"("textDocument":{"uri":")" + uri + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "newName": ")" + text + "\"", [this, &changes_vec, &result_processed](const boost::property_tree::ptree &result, bool error) { + client->write_request(this, "textDocument/rename", R"("textDocument":{"uri":")" + uri_escaped + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "newName": ")" + text + "\"", [this, &changes_vec, &result_processed](const boost::property_tree::ptree &result, bool error) { if(!error) { boost::filesystem::path project_path; auto build = Project::Build::create(file_path); @@ -832,7 +836,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { }); } else { - client->write_request(this, "textDocument/documentHighlight", R"("textDocument":{"uri":")" + uri + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "context": {"includeDeclaration": true})", [this, &changes_vec, &text, &result_processed](const boost::property_tree::ptree &result, bool error) { + client->write_request(this, "textDocument/documentHighlight", R"("textDocument":{"uri":")" + uri_escaped + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "context": {"includeDeclaration": true})", [this, &changes_vec, &text, &result_processed](const boost::property_tree::ptree &result, bool error) { if(!error) { try { std::vector edits; @@ -957,7 +961,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { std::vector> methods; std::promise result_processed; - client->write_request(this, "textDocument/documentSymbol", R"("textDocument":{"uri":")" + uri + "\"}", [&result_processed, &methods](const boost::property_tree::ptree &result, bool error) { + client->write_request(this, "textDocument/documentSymbol", R"("textDocument":{"uri":")" + uri_escaped + "\"}", [&result_processed, &methods](const boost::property_tree::ptree &result, bool error) { if(!error) { std::function parse_result = [&methods, &parse_result](const boost::property_tree::ptree &pt, const std::string &container) { for(auto it = pt.begin(); it != pt.end(); ++it) { @@ -1068,7 +1072,7 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vectorwrite_request(this, "textDocument/hover", R"("textDocument": {"uri":")" + uri + R"("}, "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) { + client->write_request(this, "textDocument/hover", R"("textDocument": {"uri":")" + uri_escaped + R"("}, "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 struct Content { @@ -1454,7 +1458,7 @@ void Source::LanguageProtocolView::apply_similar_symbol_tag() { static int request_count = 0; request_count++; auto current_request = request_count; - client->write_request(this, method, R"("textDocument":{"uri":")" + uri + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "context": {"includeDeclaration": true})", [this, current_request](const boost::property_tree::ptree &result, bool error) { + client->write_request(this, method, R"("textDocument":{"uri":")" + uri_escaped + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "context": {"includeDeclaration": true})", [this, current_request](const boost::property_tree::ptree &result, bool error) { if(!error) { std::vector ranges; for(auto it = result.begin(); it != result.end(); ++it) { @@ -1485,7 +1489,7 @@ void Source::LanguageProtocolView::apply_clickable_tag(const Gtk::TextIter &iter auto current_request = request_count; auto line = iter.get_line(); auto offset = iter.get_line_offset(); - client->write_request(this, "textDocument/definition", R"("textDocument":{"uri":")" + uri + R"("}, "position": {"line": )" + std::to_string(line) + ", \"character\": " + std::to_string(offset) + "}", [this, current_request, line, offset](const boost::property_tree::ptree &result, bool error) { + client->write_request(this, "textDocument/definition", R"("textDocument":{"uri":")" + uri_escaped + R"("}, "position": {"line": )" + std::to_string(line) + ", \"character\": " + std::to_string(offset) + "}", [this, current_request, line, offset](const boost::property_tree::ptree &result, bool error) { if(!error && !result.empty()) { dispatcher.post([this, current_request, line, offset] { if(current_request != request_count || !clickable_tag_applied) @@ -1501,7 +1505,7 @@ void Source::LanguageProtocolView::apply_clickable_tag(const Gtk::TextIter &iter Source::Offset Source::LanguageProtocolView::get_declaration(const Gtk::TextIter &iter) { auto offset = std::make_shared(); std::promise result_processed; - client->write_request(this, "textDocument/definition", R"("textDocument":{"uri":")" + uri + R"("}, "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) { + client->write_request(this, "textDocument/definition", R"("textDocument":{"uri":")" + uri_escaped + R"("}, "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) { try { @@ -1527,13 +1531,13 @@ void Source::LanguageProtocolView::setup_signals() { [this](const Gtk::TextIter &start, const Glib::ustring &text_, int bytes) { std::string text = text_; escape_text(text); - client->write_notification("textDocument/didChange", R"("textDocument":{"uri":")" + this->uri + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + R"({"range":{"start":{"line": )" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(}},"text":")" + text + "\"}" + "]"); + client->write_notification("textDocument/didChange", R"("textDocument":{"uri":")" + uri_escaped + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + R"({"range":{"start":{"line": )" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(}},"text":")" + text + "\"}" + "]"); }, false); get_buffer()->signal_erase().connect( [this](const Gtk::TextIter &start, const Gtk::TextIter &end) { - client->write_notification("textDocument/didChange", R"("textDocument":{"uri":")" + this->uri + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + R"({"range":{"start":{"line": )" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(end.get_line()) + ",\"character\":" + std::to_string(end.get_line_offset()) + R"(}},"text":""})" + "]"); + client->write_notification("textDocument/didChange", R"("textDocument":{"uri":")" + uri_escaped + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + R"({"range":{"start":{"line": )" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(end.get_line()) + ",\"character\":" + std::to_string(end.get_line_offset()) + R"(}},"text":""})" + "]"); }, false); } @@ -1541,7 +1545,7 @@ void Source::LanguageProtocolView::setup_signals() { get_buffer()->signal_changed().connect([this]() { std::string text = get_buffer()->get_text(); escape_text(text); - client->write_notification("textDocument/didChange", R"("textDocument":{"uri":")" + this->uri + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + R"({"text":")" + text + "\"}" + "]"); + client->write_notification("textDocument/didChange", R"("textDocument":{"uri":")" + uri_escaped + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + R"({"text":")" + text + "\"}" + "]"); }); } } @@ -1774,7 +1778,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { } bool using_named_parameters = named_parameter_symbol && !(current_parameter_position > 0 && used_named_parameters.empty()); - client->write_request(this, "textDocument/signatureHelp", R"("textDocument":{"uri":")" + uri + R"("}, "position": {"line": )" + std::to_string(line_number - 1) + ", \"character\": " + std::to_string(column - 1) + "}", [this, &result_processed, current_parameter_position, using_named_parameters, used_named_parameters = std::move(used_named_parameters)](const boost::property_tree::ptree &result, bool error) { + client->write_request(this, "textDocument/signatureHelp", R"("textDocument":{"uri":")" + uri_escaped + R"("}, "position": {"line": )" + std::to_string(line_number - 1) + ", \"character\": " + std::to_string(column - 1) + "}", [this, &result_processed, current_parameter_position, using_named_parameters, used_named_parameters = std::move(used_named_parameters)](const boost::property_tree::ptree &result, bool error) { if(!error) { auto signatures = result.get_child("signatures", boost::property_tree::ptree()); for(auto signature_it = signatures.begin(); signature_it != signatures.end(); ++signature_it) { @@ -1809,7 +1813,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { }); } else { - client->write_request(this, "textDocument/completion", R"("textDocument":{"uri":")" + uri + R"("}, "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) { + client->write_request(this, "textDocument/completion", R"("textDocument":{"uri":")" + uri_escaped + R"("}, "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) { boost::property_tree::ptree::const_iterator begin, end; if(auto items = result.get_child_optional("items")) { @@ -1963,7 +1967,7 @@ boost::optional Source::LanguageProtocolView::get_named_parameter_symbol() void Source::LanguageProtocolView::update_type_coverage() { if(capabilities.type_coverage) { - client->write_request(this, "textDocument/typeCoverage", R"("textDocument": {"uri":")" + uri + "\"}", [this](const boost::property_tree::ptree &result, bool error) { + client->write_request(this, "textDocument/typeCoverage", R"("textDocument": {"uri":")" + uri_escaped + "\"}", [this](const boost::property_tree::ptree &result, bool error) { if(error) { if(update_type_coverage_retries > 0) { // Retry typeCoverage request, since these requests can fail while waiting for language server to start dispatcher.post([this] { diff --git a/src/source_language_protocol.hpp b/src/source_language_protocol.hpp index c581a8a..9d608f3 100644 --- a/src/source_language_protocol.hpp +++ b/src/source_language_protocol.hpp @@ -182,6 +182,7 @@ namespace Source { Gtk::TextIter get_iter_at_line_pos(int line, int pos) override; std::string uri; + std::string uri_escaped; protected: void show_type_tooltips(const Gdk::Rectangle &rectangle) override; From 47c38ef22747d6d523507ec1befb75f454cc5b34 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 11 Jun 2021 20:53:04 +0200 Subject: [PATCH 132/375] Another fix to MSYS2 test failure --- src/source_language_protocol.cpp | 78 ++++++++++++++------------------ src/source_language_protocol.hpp | 4 +- 2 files changed, 36 insertions(+), 46 deletions(-) diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index d45bbc1..bdcae94 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -55,6 +55,32 @@ LanguageProtocol::TextDocumentEdit::TextDocumentEdit(const boost::property_tree: edits.emplace_back(it->second); } +std::string LanguageProtocol::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] == '\t') { + text.replace(c, 1, "\\t"); + ++c; + } + else if(text[c] == '"') { + text.replace(c, 1, "\\\""); + ++c; + } + else if(text[c] == '\\') { + text.replace(c, 1, "\\\\"); + ++c; + } + } + return text; +} + LanguageProtocol::Client::Client(boost::filesystem::path root_path_, std::string language_id_, const std::string &language_server) : root_path(std::move(root_path_)), language_id(std::move(language_id_)) { process = std::make_unique( filesystem::escape_argument(language_server), root_path.string(), @@ -156,7 +182,7 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang process_id = process->get_id(); } write_request( - nullptr, "initialize", "\"processId\":" + std::to_string(process_id) + R"(,"rootUri":")" + filesystem::get_uri_from_path(root_path) + R"(","capabilities": { + nullptr, "initialize", "\"processId\":" + std::to_string(process_id) + R"(,"rootUri":")" + LanguageProtocol::escape_text(filesystem::get_uri_from_path(root_path)) + R"(","capabilities": { "workspace": { "symbol": { "dynamicRegistration": false } }, @@ -455,9 +481,7 @@ void LanguageProtocol::Client::handle_server_request(size_t id, const std::strin } Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path &file_path, const Glib::RefPtr &language, std::string language_id_, std::string language_server_) - : Source::BaseView(file_path, language), Source::View(file_path, language), uri(filesystem::get_uri_from_path(file_path)), language_id(std::move(language_id_)), language_server(std::move(language_server_)), client(LanguageProtocol::Client::get(file_path, language_id, language_server)) { - uri_escaped = uri; - escape_text(uri_escaped); + : Source::BaseView(file_path, language), Source::View(file_path, language), uri(filesystem::get_uri_from_path(file_path)), uri_escaped(LanguageProtocol::escape_text(uri)), language_id(std::move(language_id_)), language_server(std::move(language_server_)), client(LanguageProtocol::Client::get(file_path, language_id, language_server)) { initialize(); } @@ -476,9 +500,7 @@ void Source::LanguageProtocolView::initialize() { this->capabilities = capabilities; set_editable(true); - std::string text = get_buffer()->get_text(); - escape_text(text); - client->write_notification("textDocument/didOpen", R"("textDocument":{"uri":")" + uri_escaped + R"(","languageId":")" + language_id + R"(","version":)" + std::to_string(document_version++) + R"(,"text":")" + text + "\"}"); + client->write_notification("textDocument/didOpen", R"("textDocument":{"uri":")" + uri_escaped + R"(","languageId":")" + language_id + R"(","version":)" + std::to_string(document_version++) + R"(,"text":")" + LanguageProtocol::escape_text(get_buffer()->get_text().raw()) + "\"}"); if(!initialized) { setup_signals(); @@ -539,8 +561,7 @@ void Source::LanguageProtocolView::rename(const boost::filesystem::path &path) { dispatcher.reset(); Source::DiffView::rename(path); uri = filesystem::get_uri_from_path(path); - uri_escaped = uri; - escape_text(uri_escaped); + uri_escaped = LanguageProtocol::escape_text(uri); client = LanguageProtocol::Client::get(file_path, language_id, language_server); initialize(); } @@ -1017,31 +1038,6 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { }; } -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] == '\t') { - text.replace(c, 1, "\\t"); - ++c; - } - else if(text[c] == '"') { - text.replace(c, 1, "\\\""); - ++c; - } - else if(text[c] == '\\') { - text.replace(c, 1, "\\\\"); - ++c; - } - } -} - void Source::LanguageProtocolView::update_diagnostics_async(std::vector &&diagnostics) { update_diagnostics_async_count++; size_t last_count = update_diagnostics_async_count; @@ -1055,11 +1051,9 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vectorsignal_insert().connect( - [this](const Gtk::TextIter &start, const Glib::ustring &text_, int bytes) { - std::string text = text_; - escape_text(text); - client->write_notification("textDocument/didChange", R"("textDocument":{"uri":")" + uri_escaped + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + R"({"range":{"start":{"line": )" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(}},"text":")" + text + "\"}" + "]"); + [this](const Gtk::TextIter &start, const Glib::ustring &text, int bytes) { + client->write_notification("textDocument/didChange", R"("textDocument":{"uri":")" + uri_escaped + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + R"({"range":{"start":{"line": )" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(}},"text":")" + LanguageProtocol::escape_text(text.raw()) + "\"}" + "]"); }, false); @@ -1543,9 +1535,7 @@ void Source::LanguageProtocolView::setup_signals() { } else if(capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::full) { get_buffer()->signal_changed().connect([this]() { - std::string text = get_buffer()->get_text(); - escape_text(text); - client->write_notification("textDocument/didChange", R"("textDocument":{"uri":")" + uri_escaped + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + R"({"text":")" + text + "\"}" + "]"); + client->write_notification("textDocument/didChange", R"("textDocument":{"uri":")" + uri_escaped + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + R"({"text":")" + LanguageProtocol::escape_text(get_buffer()->get_text().raw()) + "\"}" + "]"); }); } } diff --git a/src/source_language_protocol.hpp b/src/source_language_protocol.hpp index 9d608f3..4b983b6 100644 --- a/src/source_language_protocol.hpp +++ b/src/source_language_protocol.hpp @@ -112,6 +112,8 @@ namespace LanguageProtocol { bool type_coverage = false; }; + std::string escape_text(std::string text); + class Client { Client(boost::filesystem::path root_path, std::string language_id, const std::string &language_server); boost::filesystem::path root_path; @@ -206,8 +208,6 @@ namespace Source { void setup_navigation_and_refactoring(); - void escape_text(std::string &text); - void tag_similar_symbols(); Offset get_declaration(const Gtk::TextIter &iter); From 8acf82bb7594ba66e3bdc6687838a5ba1be60caf Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 11 Jun 2021 21:11:31 +0200 Subject: [PATCH 133/375] Removed unnecessary .raw() uses --- src/source_language_protocol.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index bdcae94..25c9a41 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -500,7 +500,7 @@ void Source::LanguageProtocolView::initialize() { this->capabilities = capabilities; set_editable(true); - client->write_notification("textDocument/didOpen", R"("textDocument":{"uri":")" + uri_escaped + R"(","languageId":")" + language_id + R"(","version":)" + std::to_string(document_version++) + R"(,"text":")" + LanguageProtocol::escape_text(get_buffer()->get_text().raw()) + "\"}"); + client->write_notification("textDocument/didOpen", R"("textDocument":{"uri":")" + uri_escaped + R"(","languageId":")" + language_id + R"(","version":)" + std::to_string(document_version++) + R"(,"text":")" + LanguageProtocol::escape_text(get_buffer()->get_text()) + "\"}"); if(!initialized) { setup_signals(); @@ -1523,7 +1523,7 @@ void Source::LanguageProtocolView::setup_signals() { if(capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::incremental) { get_buffer()->signal_insert().connect( [this](const Gtk::TextIter &start, const Glib::ustring &text, int bytes) { - client->write_notification("textDocument/didChange", R"("textDocument":{"uri":")" + uri_escaped + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + R"({"range":{"start":{"line": )" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(}},"text":")" + LanguageProtocol::escape_text(text.raw()) + "\"}" + "]"); + client->write_notification("textDocument/didChange", R"("textDocument":{"uri":")" + uri_escaped + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + R"({"range":{"start":{"line": )" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(}},"text":")" + LanguageProtocol::escape_text(text) + "\"}" + "]"); }, false); @@ -1535,7 +1535,7 @@ void Source::LanguageProtocolView::setup_signals() { } else if(capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::full) { get_buffer()->signal_changed().connect([this]() { - client->write_notification("textDocument/didChange", R"("textDocument":{"uri":")" + uri_escaped + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + R"({"text":")" + LanguageProtocol::escape_text(get_buffer()->get_text().raw()) + "\"}" + "]"); + client->write_notification("textDocument/didChange", R"("textDocument":{"uri":")" + uri_escaped + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + R"({"text":")" + LanguageProtocol::escape_text(get_buffer()->get_text()) + "\"}" + "]"); }); } } From 5164f3f956e2fb4f13ef4f39ebc086f3dcae573f Mon Sep 17 00:00:00 2001 From: eidheim Date: Sat, 12 Jun 2021 07:45:57 +0200 Subject: [PATCH 134/375] Another fix to MSYS2 test failure --- tests/language_protocol_server_test.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/language_protocol_server_test.cpp b/tests/language_protocol_server_test.cpp index 98d04de..ac3aeba 100644 --- a/tests/language_protocol_server_test.cpp +++ b/tests/language_protocol_server_test.cpp @@ -1,7 +1,17 @@ #include #include +#ifdef _WIN32 +#include +#include +#endif + + int main() { +#ifdef _WIN32 + _setmode(_fileno(stdout), _O_BINARY); +#endif + std::string line; try { // Read initialize and respond From 5aeb065f4bc984370bf69f1474d92ffcf31c8168 Mon Sep 17 00:00:00 2001 From: eidheim Date: Sat, 12 Jun 2021 12:46:23 +0200 Subject: [PATCH 135/375] Language client: cleanup of embolden_token --- src/source_language_protocol.cpp | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 25c9a41..f76a15a 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -709,15 +709,13 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { }); result_processed.get_future().get(); - auto embolden_token = [](std::string &line_, int token_start_pos, int token_end_pos) { - Glib::ustring line = line_; - if(static_cast(token_start_pos) > line.size() || static_cast(token_end_pos) > line.size()) - return; + auto embolden_token = [](std::string &line, int token_start_pos, int token_end_pos) { + Glib::ustring uline = std::move(line); //markup token as bold size_t pos = 0; - while((pos = line.find('&', pos)) != Glib::ustring::npos) { - size_t pos2 = line.find(';', pos + 2); + while((pos = uline.find('&', pos)) != Glib::ustring::npos) { + size_t pos2 = uline.find(';', pos + 2); if(static_cast(token_start_pos) > pos) { token_start_pos += pos2 - pos; token_end_pos += pos2 - pos; @@ -728,16 +726,22 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { break; pos = pos2 + 1; } - line.insert(token_end_pos, ""); - line.insert(token_start_pos, ""); + if(static_cast(token_start_pos) > uline.size()) + token_start_pos = uline.size(); + if(static_cast(token_end_pos) > uline.size()) + token_end_pos = uline.size(); + if(token_start_pos != token_end_pos) { + uline.insert(token_end_pos, ""); + uline.insert(token_start_pos, ""); + } size_t start_pos = 0; - while(start_pos < line.size() && (line[start_pos] == ' ' || line[start_pos] == '\t')) + while(start_pos < uline.size() && (uline[start_pos] == ' ' || uline[start_pos] == '\t')) ++start_pos; if(start_pos > 0) - line.erase(0, start_pos); + uline.erase(0, start_pos); - line_ = line.raw(); + line = std::move(uline); }; std::unordered_map> file_lines; From be5e36627d079089255e97eb05d1da4f707fdc0d Mon Sep 17 00:00:00 2001 From: eidheim Date: Sun, 13 Jun 2021 15:25:33 +0200 Subject: [PATCH 136/375] Language client: improved support for both UTF-16 offsets and offsetEncoding set to utf-8 --- src/source_language_protocol.cpp | 41 ++++++++++++--------- src/source_language_protocol.hpp | 1 + src/utility.cpp | 61 ++++++++++++++++++++++++++++++-- src/utility.hpp | 5 ++- tests/utility_test.cpp | 34 ++++++++++++++++++ 5 files changed, 122 insertions(+), 20 deletions(-) diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index f76a15a..ccdae62 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -254,6 +254,8 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang capabilities.type_coverage = capabilities_pt->get("typeCoverageProvider", false); } + capabilities.use_line_index = result.get("offsetEncoding", "") == "utf-8"; + write_notification("initialized", ""); } result_processed.set_value(); @@ -709,13 +711,11 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { }); result_processed.get_future().get(); - auto embolden_token = [](std::string &line, int token_start_pos, int token_end_pos) { - Glib::ustring uline = std::move(line); - - //markup token as bold + auto embolden_token = [this](std::string &line, int token_start_pos, int token_end_pos) { + // Markup token as bold size_t pos = 0; - while((pos = uline.find('&', pos)) != Glib::ustring::npos) { - size_t pos2 = uline.find(';', pos + 2); + while((pos = line.find('&', pos)) != std::string::npos) { + size_t pos2 = line.find(';', pos + 2); if(static_cast(token_start_pos) > pos) { token_start_pos += pos2 - pos; token_end_pos += pos2 - pos; @@ -726,22 +726,27 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { break; pos = pos2 + 1; } - if(static_cast(token_start_pos) > uline.size()) - token_start_pos = uline.size(); - if(static_cast(token_end_pos) > uline.size()) - token_end_pos = uline.size(); - if(token_start_pos != token_end_pos) { - uline.insert(token_end_pos, ""); - uline.insert(token_start_pos, ""); + + if(!capabilities.use_line_index) { + auto code_units_diff = token_end_pos - token_start_pos; + token_start_pos = utf16_code_units_byte_count(line, token_start_pos); + token_end_pos = token_start_pos + utf16_code_units_byte_count(line, code_units_diff, token_start_pos); + } + + if(static_cast(token_start_pos) > line.size()) + token_start_pos = line.size(); + if(static_cast(token_end_pos) > line.size()) + token_end_pos = line.size(); + if(token_start_pos < token_end_pos) { + line.insert(token_end_pos, ""); + line.insert(token_start_pos, ""); } size_t start_pos = 0; - while(start_pos < uline.size() && (uline[start_pos] == ' ' || uline[start_pos] == '\t')) + while(start_pos < line.size() && (line[start_pos] == ' ' || line[start_pos] == '\t')) ++start_pos; if(start_pos > 0) - uline.erase(0, start_pos); - - line = std::move(uline); + line.erase(0, start_pos); }; std::unordered_map> file_lines; @@ -1278,6 +1283,8 @@ void Source::LanguageProtocolView::update_diagnostics(std::vector(text[pos]) <= 0b01111111 || static_cast(text[pos]) >= 0b11000000) + for(; pos < size;) { + if(static_cast(text[pos]) <= 0b01111111) { ++count; + ++pos; + } + else if(static_cast(text[pos]) >= 0b11111000) // Invalid UTF-8 byte + ++pos; + else if(static_cast(text[pos]) >= 0b11110000) { + ++count; + pos += 4; + } + else if(static_cast(text[pos]) >= 0b11100000) { + ++count; + pos += 3; + } + else if(static_cast(text[pos]) >= 0b11000000) { + ++count; + pos += 2; + } + else // // Invalid start of UTF-8 character + ++pos; } return count; } +size_t utf16_code_units_byte_count(const std::string &text, size_t code_units, size_t start_pos) { + if(code_units == 0) + return 0; + + size_t pos = start_pos; + size_t current_code_units = 0; + for(; pos < text.size();) { + if(static_cast(text[pos]) <= 0b01111111) { + ++current_code_units; + ++pos; + if(current_code_units >= code_units) + break; + } + else if(static_cast(text[pos]) >= 0b11111000) // Invalid UTF-8 byte + ++pos; + else if(static_cast(text[pos]) >= 0b11110000) { + current_code_units += 2; + pos += 4; + if(current_code_units >= code_units) + break; + } + else if(static_cast(text[pos]) >= 0b11100000) { + ++current_code_units; + pos += 3; + if(current_code_units >= code_units) + break; + } + else if(static_cast(text[pos]) >= 0b11000000) { + ++current_code_units; + pos += 2; + if(current_code_units >= code_units) + break; + } + else // // Invalid start of UTF-8 character + ++pos; + } + return pos - start_pos; +} + bool starts_with(const char *str, const std::string &test) noexcept { for(size_t i = 0; i < test.size(); ++i) { if(*str == '\0') diff --git a/src/utility.hpp b/src/utility.hpp index 16679e1..798875f 100644 --- a/src/utility.hpp +++ b/src/utility.hpp @@ -8,9 +8,12 @@ public: ~ScopeGuard(); }; -/// Returns number of utf8 characters in text argument +/// Returns number of utf8 characters in text size_t utf8_character_count(const std::string &text, size_t pos = 0, size_t length = std::string::npos) noexcept; +/// Returns number of bytes in the given utf16 code units in text +size_t utf16_code_units_byte_count(const std::string &text, size_t code_units, size_t start_pos = 0); + bool starts_with(const char *str, const std::string &test) noexcept; bool starts_with(const char *str, const char *test) noexcept; bool starts_with(const std::string &str, const std::string &test) noexcept; diff --git a/tests/utility_test.cpp b/tests/utility_test.cpp index b778612..47c1553 100644 --- a/tests/utility_test.cpp +++ b/tests/utility_test.cpp @@ -16,6 +16,40 @@ int main() { g_assert(utf8_character_count("æøå") == 3); g_assert(utf8_character_count("æøåtest") == 7); + g_assert_cmpuint(utf16_code_units_byte_count("", 0), ==, 0); + g_assert_cmpuint(utf16_code_units_byte_count("", 1), ==, 0); + g_assert_cmpuint(utf16_code_units_byte_count("test", 0), ==, 0); + g_assert_cmpuint(utf16_code_units_byte_count("test", 1), ==, 1); + g_assert_cmpuint(utf16_code_units_byte_count("test", 3), ==, 3); + g_assert_cmpuint(utf16_code_units_byte_count("test", 4), ==, 4); + g_assert_cmpuint(utf16_code_units_byte_count("test", 5), ==, 4); + + g_assert_cmpuint(utf16_code_units_byte_count("æøå", 0), ==, 0); + g_assert_cmpuint(utf16_code_units_byte_count("æøå", 1), ==, 2); + g_assert_cmpuint(utf16_code_units_byte_count("æøå", 2), ==, 4); + g_assert_cmpuint(utf16_code_units_byte_count("æøå", 3), ==, 6); + g_assert_cmpuint(utf16_code_units_byte_count("æøå", 4), ==, 6); + g_assert_cmpuint(utf16_code_units_byte_count("æøå", 5), ==, 6); + + g_assert_cmpuint(utf16_code_units_byte_count("æøå", 0, 2), ==, 0); + g_assert_cmpuint(utf16_code_units_byte_count("æøå", 1, 2), ==, 2); + g_assert_cmpuint(utf16_code_units_byte_count("æøå", 2, 2), ==, 4); + g_assert_cmpuint(utf16_code_units_byte_count("æøå", 3, 2), ==, 4); + g_assert_cmpuint(utf16_code_units_byte_count("æøå", 1, 6), ==, 0); + g_assert_cmpuint(utf16_code_units_byte_count("æøå", 0, 6), ==, 0); + + g_assert_cmpuint(strlen("🔥"), ==, 4); // Fire emoji + + g_assert_cmpuint(utf16_code_units_byte_count("🔥", 0), ==, 0); // Fire emoji + g_assert_cmpuint(utf16_code_units_byte_count("🔥", 2), ==, 4); // Fire emoji + g_assert_cmpuint(utf16_code_units_byte_count("🔥", 3), ==, 4); // Fire emoji + g_assert_cmpuint(utf16_code_units_byte_count("test🔥test", 0), ==, 0); // Fire emoji between test words + g_assert_cmpuint(utf16_code_units_byte_count("test🔥test", 4), ==, 4); // Fire emoji between test words + g_assert_cmpuint(utf16_code_units_byte_count("test🔥test", 6), ==, 8); // Fire emoji between test words + g_assert_cmpuint(utf16_code_units_byte_count("test🔥test", 7), ==, 9); // Fire emoji between test words + g_assert_cmpuint(utf16_code_units_byte_count("test🔥test", 10), ==, 12); // Fire emoji between test words + g_assert_cmpuint(utf16_code_units_byte_count("test🔥test", 11), ==, 12); // Fire emoji between test words + std::string empty; std::string test("test"); std::string testtest("testtest"); From 0c42dd0fa91d4a029942a49cf8f3b352edb9cde1 Mon Sep 17 00:00:00 2001 From: eidheim Date: Sun, 13 Jun 2021 16:38:15 +0200 Subject: [PATCH 137/375] Language client: use proper utf-16 offsets when getting iters and offsetEncoding is not set to utf-8 --- src/source_language_protocol.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index ccdae62..2b26c9f 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -1285,7 +1285,7 @@ void Source::LanguageProtocolView::update_diagnostics(std::vector Date: Mon, 14 Jun 2021 09:12:43 +0200 Subject: [PATCH 138/375] Added link to documentation on Language Protocol extension offsetEncoding --- src/source_language_protocol.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 2b26c9f..549d509 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -254,6 +254,7 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang capabilities.type_coverage = capabilities_pt->get("typeCoverageProvider", false); } + // See https://clangd.llvm.org/extensions.html#utf-8-offsets for documentation on offsetEncoding capabilities.use_line_index = result.get("offsetEncoding", "") == "utf-8"; write_notification("initialized", ""); From 2e34b4ba6cdc0894646a480066a312196a180330 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 15 Jun 2021 08:23:15 +0200 Subject: [PATCH 139/375] Updated libclangmm --- lib/libclangmm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/libclangmm b/lib/libclangmm index 663e548..0df7169 160000 --- a/lib/libclangmm +++ b/lib/libclangmm @@ -1 +1 @@ -Subproject commit 663e54847f091b9e143db48361538a1e9ea540e9 +Subproject commit 0df7169ddc918d80a95e50f04813be82fff7ce24 From 70d9818772b0cb78864b32a453567e546164e6bc Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 15 Jun 2021 13:01:20 +0200 Subject: [PATCH 140/375] Language client: added support for type declaration and implementation location. Also fixes to utf-8 byte count and regular utf-16 offsets, and cleaned up write_request and write_notification calls --- src/autocomplete.cpp | 10 +- src/autocomplete.hpp | 3 +- src/source_clang.cpp | 4 +- src/source_generic.cpp | 2 +- src/source_language_protocol.cpp | 1855 ++++++++++++++++-------------- src/source_language_protocol.hpp | 33 +- src/utility.cpp | 40 +- src/utility.hpp | 2 + tests/utility_test.cpp | 19 + 9 files changed, 1087 insertions(+), 881 deletions(-) diff --git a/src/autocomplete.cpp b/src/autocomplete.cpp index 6893cca..3307826 100644 --- a/src/autocomplete.cpp +++ b/src/autocomplete.cpp @@ -59,19 +59,19 @@ void Autocomplete::run() { if(thread.joinable()) thread.join(); auto iter = view->get_buffer()->get_insert()->get_iter(); - auto line_nr = iter.get_line() + 1; - auto column_nr = iter.get_line_index() + 1; + auto line = iter.get_line(); + auto line_index = iter.get_line_index(); Glib::ustring buffer; if(pass_buffer_and_strip_word) { auto pos = iter.get_offset() - 1; buffer = view->get_buffer()->get_text(); while(pos >= 0 && Source::BaseView::is_token_char(buffer[pos])) { buffer.replace(pos, 1, " "); - column_nr--; + line_index--; pos--; } } - thread = std::thread([this, line_nr, column_nr, buffer = std::move(buffer)] { + thread = std::thread([this, line, line_index, buffer = std::move(buffer)] { auto lock = get_parse_lock(); if(!is_processing()) return; @@ -79,7 +79,7 @@ void Autocomplete::run() { rows.clear(); auto &buffer_raw = const_cast(buffer.raw()); - bool success = add_rows(buffer_raw, line_nr, column_nr); + bool success = add_rows(buffer_raw, line, line_index); if(!is_processing()) return; diff --git a/src/autocomplete.hpp b/src/autocomplete.hpp index f023fae..76586ed 100644 --- a/src/autocomplete.hpp +++ b/src/autocomplete.hpp @@ -48,7 +48,8 @@ public: std::function on_add_rows_error = [] {}; /// The handler is not run in the main loop. Should return false on error. - std::function add_rows = [](std::string &, int, int) { return true; }; + /// Column is line byte index. + std::function add_rows = [](std::string &, int, int) { return true; }; std::function on_show = [] {}; std::function on_hide = [] {}; diff --git a/src/source_clang.cpp b/src/source_clang.cpp index 87d52c4..3f4e32a 100644 --- a/src/source_clang.cpp +++ b/src/source_clang.cpp @@ -948,10 +948,10 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa full_reparse(); }; - autocomplete.add_rows = [this](std::string &buffer, int line_number, int column) { + autocomplete.add_rows = [this](std::string &buffer, int line, int line_index) { if(is_language({"chdr", "cpphdr"})) clangmm::remove_include_guard(buffer); - code_complete_results = std::make_unique(clang_tu->get_code_completions(buffer, line_number, column)); + code_complete_results = std::make_unique(clang_tu->get_code_completions(buffer, line + 1, line_index + 1)); if(!code_complete_results->cx_results) return false; diff --git a/src/source_generic.cpp b/src/source_generic.cpp index d310bc4..83c37c2 100644 --- a/src/source_generic.cpp +++ b/src/source_generic.cpp @@ -243,7 +243,7 @@ void Source::GenericView::setup_autocomplete() { update_status_state(this); }; - autocomplete.add_rows = [this](std::string &buffer, int line_number, int column) { + autocomplete.add_rows = [this](std::string &buffer, int /*line*/, int /*line_index*/) { if(autocomplete.state == Autocomplete::State::starting) { autocomplete_comment.clear(); autocomplete_insert.clear(); diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 549d509..ee1aeba 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -126,7 +126,7 @@ LanguageProtocol::Client::~Client() { std::promise result_processed; write_request(nullptr, "shutdown", "", [this, &result_processed](const boost::property_tree::ptree &result, bool error) { if(!error) - this->write_notification("exit", ""); + this->write_notification("exit"); result_processed.set_value(); }); result_processed.get_future().get(); @@ -182,7 +182,7 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang process_id = process->get_id(); } write_request( - nullptr, "initialize", "\"processId\":" + std::to_string(process_id) + R"(,"rootUri":")" + LanguageProtocol::escape_text(filesystem::get_uri_from_path(root_path)) + R"(","capabilities": { + nullptr, "initialize", "\"processId\":" + std::to_string(process_id) + ",\"rootUri\":\"" + LanguageProtocol::escape_text(filesystem::get_uri_from_path(root_path)) + R"(","capabilities": { "workspace": { "symbol": { "dynamicRegistration": false } }, @@ -206,6 +206,8 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang } }, "definition": { "dynamicRegistration": false }, + "typeDefinition": { "dynamicRegistration": false }, + "implementation": { "dynamicRegistration": false }, "references": { "dynamicRegistration": false }, "documentHighlight": { "dynamicRegistration": false }, "documentSymbol": { "dynamicRegistration": false }, @@ -219,8 +221,7 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang "codeActionKind": { "valueSet": ["quickfix"] } } } - }, - "offsetEncoding": ["utf-8"] + } }, "initializationOptions": { "checkOnSave": { "enable": true } @@ -239,6 +240,8 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang capabilities.completion = static_cast(capabilities_pt->get_child_optional("completionProvider")); capabilities.signature_help = static_cast(capabilities_pt->get_child_optional("signatureHelpProvider")); capabilities.definition = capabilities_pt->get("definitionProvider", false); + capabilities.type_definition = capabilities_pt->get("typeDefinitionProvider", false); + capabilities.implementation = capabilities_pt->get("implementationProvider", false); capabilities.references = capabilities_pt->get("referencesProvider", false); capabilities.document_highlight = capabilities_pt->get("documentHighlightProvider", false); capabilities.workspace_symbol = capabilities_pt->get("workspaceSymbolProvider", false); @@ -255,9 +258,10 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang } // See https://clangd.llvm.org/extensions.html#utf-8-offsets for documentation on offsetEncoding - capabilities.use_line_index = result.get("offsetEncoding", "") == "utf-8"; + // Disabled for now since rust-analyzer does not seem to support utf-8 byte offsets from clients + // capabilities.use_line_index = result.get("offsetEncoding", "") == "utf-8"; - write_notification("initialized", ""); + write_notification("initialized"); } result_processed.set_value(); }); @@ -419,11 +423,10 @@ void LanguageProtocol::Client::write_request(Source::LanguageProtocolView *view, } }); } - std::string content(R"({"jsonrpc":"2.0","id":)" + std::to_string(message_id++) + R"(,"method":")" + method + R"(","params":{)" + params + "}}"); - auto message = "Content-Length: " + std::to_string(content.size()) + "\r\n\r\n" + content; + std::string content("{\"jsonrpc\":\"2.0\",\"id\":" + std::to_string(message_id++) + ",\"method\":\"" + method + "\",\"params\":{" + params + "}}"); if(Config::get().log.language_server) std::cout << "Language client: " << content << std::endl; - if(!process->write(message)) { + if(!process->write("Content-Length: " + std::to_string(content.size()) + "\r\n\r\n" + content)) { Terminal::get().async_print("\e[31mError\e[m: could not write to language server. Please close and reopen all project files.\n", true); auto id_it = handlers.find(message_id - 1); if(id_it != handlers.end()) { @@ -438,20 +441,18 @@ void LanguageProtocol::Client::write_request(Source::LanguageProtocolView *view, void LanguageProtocol::Client::write_response(size_t id, const std::string &result) { LockGuard lock(read_write_mutex); - std::string content(R"({"jsonrpc":"2.0","id":)" + std::to_string(id) + R"(,"result":{)" + result + "}}"); - auto message = "Content-Length: " + std::to_string(content.size()) + "\r\n\r\n" + content; + std::string content("{\"jsonrpc\":\"2.0\",\"id\":" + std::to_string(id) + ",\"result\":{" + result + "}}"); if(Config::get().log.language_server) std::cout << "Language client: " << content << std::endl; - process->write(message); + process->write("Content-Length: " + std::to_string(content.size()) + "\r\n\r\n" + content); } void LanguageProtocol::Client::write_notification(const std::string &method, const std::string ¶ms) { LockGuard lock(read_write_mutex); - std::string content(R"({"jsonrpc":"2.0","method":")" + method + R"(","params":{)" + params + "}}"); - auto message = "Content-Length: " + std::to_string(content.size()) + "\r\n\r\n" + content; + std::string content("{\"jsonrpc\":\"2.0\",\"method\":\"" + method + "\",\"params\":{" + params + "}}"); if(Config::get().log.language_server) std::cout << "Language client: " << content << std::endl; - process->write(message); + process->write("Content-Length: " + std::to_string(content.size()) + "\r\n\r\n" + content); } void LanguageProtocol::Client::handle_server_notification(const std::string &method, const boost::property_tree::ptree ¶ms) { @@ -503,7 +504,7 @@ void Source::LanguageProtocolView::initialize() { this->capabilities = capabilities; set_editable(true); - client->write_notification("textDocument/didOpen", R"("textDocument":{"uri":")" + uri_escaped + R"(","languageId":")" + language_id + R"(","version":)" + std::to_string(document_version++) + R"(,"text":")" + LanguageProtocol::escape_text(get_buffer()->get_text()) + "\"}"); + write_did_open_notification(); if(!initialized) { setup_signals(); @@ -548,7 +549,7 @@ void Source::LanguageProtocolView::close() { autocomplete->thread.join(); } - client->write_notification("textDocument/didClose", R"("textDocument":{"uri":")" + uri_escaped + "\"}"); + write_notification("textDocument/didClose"); client->close(this); client = nullptr; } @@ -558,6 +559,76 @@ Source::LanguageProtocolView::~LanguageProtocolView() { thread_pool.shutdown(true); } +int Source::LanguageProtocolView::get_line_pos(const Gtk::TextIter &iter) { + if(capabilities.use_line_index) + return iter.get_line_index(); + return utf16_code_unit_count(get_line(iter), 0, iter.get_line_index()); +} + +int Source::LanguageProtocolView::get_line_pos(int line, int line_index) { + if(capabilities.use_line_index) + return line_index; + return utf16_code_unit_count(get_line(line), 0, line_index); +} + +Gtk::TextIter Source::LanguageProtocolView::get_iter_at_line_pos(int line, int pos) { + if(capabilities.use_line_index) + return get_iter_at_line_index(line, pos); + return get_iter_at_line_index(line, utf16_code_units_byte_count(get_line(line), pos)); +} + +std::pair Source::LanguageProtocolView::make_position(int line, int character) { + return {"position", "{\"line\":" + std::to_string(line) + ",\"character\":" + std::to_string(character) + "}"}; +} + +std::pair Source::LanguageProtocolView::make_range(const std::pair &start, const std::pair &end) { + return {"range", "{\"start\":{\"line\":" + std::to_string(start.first) + ",\"character\":" + std::to_string(start.second) + "},\"end\":{\"line\":" + std::to_string(end.first) + ",\"character\":" + std::to_string(end.second) + "}}"}; +} + +std::string Source::LanguageProtocolView::to_string(const std::pair ¶m) { + std::string result; + result.reserve(param.first.size() + param.second.size() + 3); + result += '"'; + result += param.first; + result += "\":"; + result += param.second; + return result; +} + +std::string Source::LanguageProtocolView::to_string(const std::vector> ¶ms) { + size_t size = params.empty() ? 0 : params.size() - 1; + for(auto ¶m : params) + size += param.first.size() + param.second.size() + 3; + + std::string result; + result.reserve(size); + for(auto ¶m : params) { + if(!result.empty()) + result += ','; + result += '"'; + result += param.first; + result += "\":"; + result += param.second; + } + return result; +} + +void Source::LanguageProtocolView::write_request(const std::string &method, const std::vector> ¶ms, std::function &&function) { + client->write_request(this, method, "\"textDocument\":{\"uri\":\"" + uri_escaped + "\"}" + (params.empty() ? "" : "," + to_string(params)), std::move(function)); +} + +void Source::LanguageProtocolView::write_notification(const std::string &method) { + client->write_notification(method, "\"textDocument\":{\"uri\":\"" + uri_escaped + "\"}"); +} + +void Source::LanguageProtocolView::write_did_open_notification() { + client->write_notification("textDocument/didOpen", "\"textDocument\":{\"uri\":\"" + uri_escaped + "\",\"version\":" + std::to_string(document_version++) + ",\"languageId\":\"" + language_id + "\",\"text\":\"" + LanguageProtocol::escape_text(get_buffer()->get_text().raw()) + "\"}"); +} + +void Source::LanguageProtocolView::write_did_change_notification(const std::vector> ¶ms) { + client->write_notification("textDocument/didChange", "\"textDocument\":{\"uri\":\"" + uri_escaped + "\",\"version\":" + std::to_string(document_version++) + (params.empty() ? "}" : "}," + to_string(params))); +} + void Source::LanguageProtocolView::rename(const boost::filesystem::path &path) { // Reset view close(); @@ -573,7 +644,7 @@ bool Source::LanguageProtocolView::save() { if(!Source::View::save()) return false; - client->write_notification("textDocument/didSave", R"("textDocument":{"uri":")" + uri_escaped + "\"}"); + write_notification("textDocument/didSave"); update_type_coverage(); @@ -621,20 +692,17 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { std::promise result_processed; std::string method; - std::string params; - std::string options("\"tabSize\":" + std::to_string(tab_size) + ",\"insertSpaces\":" + (tab_char == ' ' ? "true" : "false")); + std::vector> params = {{"options", '{' + to_string({{"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 = R"("textDocument":{"uri":")" + uri_escaped + R"("},"range":{"start":{"line":)" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(end.get_line()) + ",\"character\":" + std::to_string(end.get_line_offset()) + "}},\"options\":{" + options + "}"; + params.emplace_back(make_range({start.get_line(), get_line_pos(start)}, {end.get_line(), get_line_pos(end)})); } - else { + else method = "textDocument/formatting"; - params = R"("textDocument":{"uri":")" + uri_escaped + R"("},"options":{)" + options + "}"; - } - client->write_request(this, method, params, [&text_edits, &result_processed](const boost::property_tree::ptree &result, bool error) { + write_request(method, params, [&text_edits, &result_processed](const boost::property_tree::ptree &result, bool error) { if(!error) { for(auto it = result.begin(); it != result.end(); ++it) { try { @@ -653,7 +721,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { if(text_edits.size() == 1 && text_edits[0].range.start.line == 0 && text_edits[0].range.start.character == 0 && (text_edits[0].range.end.line > end_iter.get_line() || - (text_edits[0].range.end.line == end_iter.get_line() && text_edits[0].range.end.character >= end_iter.get_line_offset()))) { + (text_edits[0].range.end.line == end_iter.get_line() && text_edits[0].range.end.character >= get_line_pos(end_iter)))) { replace_text(text_edits[0].new_text); } else { @@ -677,11 +745,50 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { Info::get().print("No declaration found"); return offset; }; - get_declaration_or_implementation_locations = [this]() { - std::vector offsets; - auto offset = get_declaration_location(); - if(offset) - offsets.emplace_back(std::move(offset)); + + // Assumes that capabilities.definition is available when capabilities.implementation is supported + if(capabilities.implementation) { + get_declaration_or_implementation_locations = [this]() { + // Try implementation locations first + auto iter = get_buffer()->get_insert()->get_iter(); + auto offsets = get_implementations(iter); + auto token_iters = get_token_iters(iter); + bool is_implementation = false; + for(auto &offset : offsets) { + if(offset.file_path == file_path && get_iter_at_line_pos(offset.line, offset.index) == token_iters.first) { + is_implementation = true; + break; + } + } + if(offsets.empty() || is_implementation) { + if(auto offset = get_declaration_location()) + return std::vector({std::move(offset)}); + } + return offsets; + }; + } + else { + get_declaration_or_implementation_locations = [this]() { + std::vector offsets; + if(auto offset = get_declaration_location()) + offsets.emplace_back(std::move(offset)); + return offsets; + }; + } + } + if(capabilities.type_definition) { + get_type_declaration_location = [this]() { + auto offset = get_type_declaration(get_buffer()->get_insert()->get_iter()); + if(!offset) + Info::get().print("No type declaration found"); + return offset; + }; + } + if(capabilities.implementation) { + get_implementation_locations = [this]() { + auto offsets = get_implementations(get_buffer()->get_insert()->get_iter()); + if(offsets.empty()) + Info::get().print("No implementation found"); return offsets; }; } @@ -698,7 +805,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { else method = "textDocument/documentHighlight"; - client->write_request(this, method, R"("textDocument":{"uri":")" + uri_escaped + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "context": {"includeDeclaration": true})", [this, &locations, &result_processed](const boost::property_tree::ptree &result, bool error) { + write_request(method, {make_position(iter.get_line(), get_line_pos(iter)), {"context", "{\"includeDeclaration\":true}"}}, [this, &locations, &result_processed](const boost::property_tree::ptree &result, bool error) { if(!error) { try { for(auto it = result.begin(); it != result.end(); ++it) @@ -830,7 +937,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { std::vector changes_vec; std::promise result_processed; if(capabilities.rename) { - client->write_request(this, "textDocument/rename", R"("textDocument":{"uri":")" + uri_escaped + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "newName": ")" + text + "\"", [this, &changes_vec, &result_processed](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/rename", {make_position(iter.get_line(), get_line_pos(iter)), {"newName", '"' + text + '"'}}, [this, &changes_vec, &result_processed](const boost::property_tree::ptree &result, bool error) { if(!error) { boost::filesystem::path project_path; auto build = Project::Build::create(file_path); @@ -867,7 +974,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { }); } else { - client->write_request(this, "textDocument/documentHighlight", R"("textDocument":{"uri":")" + uri_escaped + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "context": {"includeDeclaration": true})", [this, &changes_vec, &text, &result_processed](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/documentHighlight", {make_position(iter.get_line(), get_line_pos(iter)), {"context", "{\"includeDeclaration\":true}"}}, [this, &changes_vec, &text, &result_processed](const boost::property_tree::ptree &result, bool error) { if(!error) { try { std::vector edits; @@ -933,7 +1040,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { if(changes->text_edits.size() == 1 && changes->text_edits[0].range.start.line == 0 && changes->text_edits[0].range.start.character == 0 && (changes->text_edits[0].range.end.line > end_iter.get_line() || - (changes->text_edits[0].range.end.line == end_iter.get_line() && changes->text_edits[0].range.end.character >= end_iter.get_line_offset()))) { + (changes->text_edits[0].range.end.line == end_iter.get_line() && changes->text_edits[0].range.end.character >= get_line_pos(end_iter)))) { view->replace_text(changes->text_edits[0].new_text); Terminal::get().print(filesystem::get_short_path(view->file_path).string() + ":1:1\n"); @@ -992,7 +1099,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { std::vector> methods; std::promise result_processed; - client->write_request(this, "textDocument/documentSymbol", R"("textDocument":{"uri":")" + uri_escaped + "\"}", [&result_processed, &methods](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/documentSymbol", {}, [&result_processed, &methods](const boost::property_tree::ptree &result, bool error) { if(!error) { std::function parse_result = [&methods, &parse_result](const boost::property_tree::ptree &pt, const std::string &container) { for(auto it = pt.begin(); it != pt.end(); ++it) { @@ -1048,917 +1155,945 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { }; } -void Source::LanguageProtocolView::update_diagnostics_async(std::vector &&diagnostics) { - update_diagnostics_async_count++; - size_t last_count = update_diagnostics_async_count; - if(capabilities.code_action && !diagnostics.empty()) { - dispatcher.post([this, diagnostics = std::move(diagnostics), last_count]() mutable { - if(last_count != update_diagnostics_async_count) - return; - std::string range; - std::string diagnostics_string; - for(auto &diagnostic : diagnostics) { - auto start = get_iter_at_line_pos(diagnostic.range.start.line, diagnostic.range.start.character); - auto end = get_iter_at_line_pos(diagnostic.range.end.line, diagnostic.range.end.character); - range = "{\"start\":{\"line\": " + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(end.get_line()) + ",\"character\":" + std::to_string(end.get_line_offset()) + "}}"; - if(!diagnostics_string.empty()) - diagnostics_string += ','; - diagnostics_string += "{\"range\":" + range + ",\"message\":\"" + LanguageProtocol::escape_text(diagnostic.message) + "\""; - if(diagnostic.severity != 0) - diagnostics_string += ",\"severity\":" + std::to_string(diagnostic.severity); - if(!diagnostic.code.empty()) - diagnostics_string += ",\"code\":\"" + diagnostic.code + "\""; - diagnostics_string += "}"; - } - if(diagnostics.size() != 1) { // Use diagnostic range if only one diagnostic, otherwise use whole buffer - auto start = get_buffer()->begin(); - auto end = get_buffer()->end(); - range = "{\"start\":{\"line\": " + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(end.get_line()) + ",\"character\":" + std::to_string(end.get_line_offset()) + "}}"; - } +void Source::LanguageProtocolView::setup_signals() { + if(capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::incremental) { + get_buffer()->signal_insert().connect( + [this](const Gtk::TextIter &start, const Glib::ustring &text, int bytes) { + std::pair location = {start.get_line(), get_line_pos(start)}; + write_did_change_notification({{"contentChanges", "[{" + to_string({make_range(location, location), {"text", '"' + LanguageProtocol::escape_text(text.raw()) + '"'}}) + "}]"}}); + }, + false); - auto request = (R"("textDocument":{"uri":")" + uri_escaped + "\"},\"range\":" + range + ",\"context\":{\"diagnostics\":[" + diagnostics_string + "],\"only\":[\"quickfix\"]}"); - thread_pool.push([this, diagnostics = std::move(diagnostics), request = std::move(request), last_count]() mutable { - if(last_count != update_diagnostics_async_count) - return; - std::promise result_processed; - client->write_request(this, "textDocument/codeAction", request, [this, &result_processed, &diagnostics, last_count](const boost::property_tree::ptree &result, bool error) { - if(!error && last_count == update_diagnostics_async_count) { - try { - for(auto it = result.begin(); it != result.end(); ++it) { - auto kind = it->second.get("kind", ""); - if(kind == "quickfix" || kind.empty()) { // Workaround for typescript-language-server (kind.empty()) - auto title = it->second.get("title"); - std::vector quickfix_diagnostics; - if(auto diagnostics_pt = it->second.get_child_optional("diagnostics")) { - for(auto it = diagnostics_pt->begin(); it != diagnostics_pt->end(); ++it) - quickfix_diagnostics.emplace_back(it->second); - } - if(auto changes = it->second.get_child_optional("edit.changes")) { - for(auto file_it = changes->begin(); file_it != changes->end(); ++file_it) { - for(auto edit_it = file_it->second.begin(); edit_it != file_it->second.end(); ++edit_it) { - LanguageProtocol::TextEdit edit(edit_it->second); - if(!quickfix_diagnostics.empty()) { - for(auto &diagnostic : diagnostics) { - for(auto &quickfix_diagnostic : quickfix_diagnostics) { - if(diagnostic.message == quickfix_diagnostic.message && diagnostic.range == quickfix_diagnostic.range) { - auto pair = diagnostic.quickfixes.emplace(title, std::set{}); - pair.first->second.emplace( - edit.new_text, - filesystem::get_path_from_uri(file_it->first).string(), - std::make_pair(Offset(edit.range.start.line, edit.range.start.character), - Offset(edit.range.end.line, edit.range.end.character))); - break; - } - } - } - } - else { // Workaround for language server that does not report quickfix diagnostics - for(auto &diagnostic : diagnostics) { - if(edit.range.start.line == diagnostic.range.start.line) { - auto pair = diagnostic.quickfixes.emplace(title, std::set{}); - pair.first->second.emplace( - edit.new_text, - filesystem::get_path_from_uri(file_it->first).string(), - std::make_pair(Offset(edit.range.start.line, edit.range.start.character), - Offset(edit.range.end.line, edit.range.end.character))); - break; - } - } - } - } - } - } - else { - auto changes_pt = it->second.get_child_optional("edit.documentChanges"); - if(!changes_pt) { // Workaround for typescript-language-server - if(auto arguments_pt = it->second.get_child_optional("arguments")) { - if(!arguments_pt->empty()) - changes_pt = arguments_pt->begin()->second.get_child_optional("documentChanges"); - } - } - if(changes_pt) { - for(auto change_it = changes_pt->begin(); change_it != changes_pt->end(); ++change_it) { - LanguageProtocol::TextDocumentEdit text_document_edit(change_it->second); - for(auto &edit : text_document_edit.edits) { - if(!quickfix_diagnostics.empty()) { - for(auto &diagnostic : diagnostics) { - for(auto &quickfix_diagnostic : quickfix_diagnostics) { - if(diagnostic.message == quickfix_diagnostic.message && diagnostic.range == quickfix_diagnostic.range) { - auto pair = diagnostic.quickfixes.emplace(title, std::set{}); - pair.first->second.emplace( - edit.new_text, - text_document_edit.file, - std::make_pair(Offset(edit.range.start.line, edit.range.start.character), - Offset(edit.range.end.line, edit.range.end.character))); - break; - } - } - } - } - else { // Workaround for language server that does not report quickfix diagnostics - for(auto &diagnostic : diagnostics) { - if(edit.range.start.line == diagnostic.range.start.line) { - auto pair = diagnostic.quickfixes.emplace(title, std::set{}); - pair.first->second.emplace( - edit.new_text, - text_document_edit.file, - std::make_pair(Offset(edit.range.start.line, edit.range.start.character), - Offset(edit.range.end.line, edit.range.end.character))); - break; - } - } - } - } - } - } - } - } - } - } - catch(...) { - } - } - result_processed.set_value(); - }); - result_processed.get_future().get(); - dispatcher.post([this, diagnostics = std::move(diagnostics), last_count]() mutable { - if(last_count == update_diagnostics_async_count) { - last_diagnostics = diagnostics; - update_diagnostics(std::move(diagnostics)); - } - }); - }); - }); + get_buffer()->signal_erase().connect( + [this](const Gtk::TextIter &start, const Gtk::TextIter &end) { + write_did_change_notification({{"contentChanges", "[{" + to_string({make_range({start.get_line(), get_line_pos(start)}, {end.get_line(), get_line_pos(end)}), {"text", "\"\""}}) + "}]"}}); + }, + false); } - else { - dispatcher.post([this, diagnostics = std::move(diagnostics), last_count]() mutable { - if(last_count == update_diagnostics_async_count) { - last_diagnostics = diagnostics; - update_diagnostics(std::move(diagnostics)); - } + else if(capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::full) { + get_buffer()->signal_changed().connect([this]() { + write_did_change_notification({{"contentChanges", "[{" + to_string({"text", '"' + LanguageProtocol::escape_text(get_buffer()->get_text().raw()) + '"'}) + "}]"}}); }); } } -void Source::LanguageProtocolView::update_diagnostics(std::vector diagnostics) { - diagnostic_offsets.clear(); - diagnostic_tooltips.clear(); - fix_its.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()); - num_warnings = 0; - num_errors = 0; - num_fix_its = 0; - - for(auto &diagnostic : diagnostics) { - auto start = get_iter_at_line_pos(diagnostic.range.start.line, diagnostic.range.start.character); - auto end = get_iter_at_line_pos(diagnostic.range.end.line, diagnostic.range.end.character); - - if(start == end) { - if(!end.ends_line()) - end.forward_char(); - else - while(start.ends_line() && start.backward_char()) { // Move start so that diagnostic underline is visible - } - } - - bool error = false; - if(diagnostic.severity >= 2) - num_warnings++; - else { - num_errors++; - error = true; - } - num_fix_its += diagnostic.quickfixes.size(); +void Source::LanguageProtocolView::setup_autocomplete() { + autocomplete = std::make_unique(this, interactive_completion, last_keyval, false); - for(auto &quickfix : diagnostic.quickfixes) - fix_its.insert(fix_its.end(), quickfix.second.begin(), quickfix.second.end()); + if(!capabilities.completion) + return; - add_diagnostic_tooltip(start, end, error, [this, diagnostic = std::move(diagnostic)](Tooltip &tooltip) { - if(language_id == "python") { // Python might support markdown in the future - tooltip.insert_with_links_tagged(diagnostic.message); - return; - } - tooltip.insert_markdown(diagnostic.message); + non_interactive_completion = [this] { + if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) + return; + autocomplete->run(); + }; - if(!diagnostic.related_informations.empty()) { - auto link_tag = tooltip.buffer->get_tag_table()->lookup("link"); - for(size_t i = 0; i < diagnostic.related_informations.size(); ++i) { - auto link = filesystem::get_relative_path(diagnostic.related_informations[i].location.file, file_path.parent_path()).string(); - link += ':' + std::to_string(diagnostic.related_informations[i].location.range.start.line + 1); - link += ':' + std::to_string(diagnostic.related_informations[i].location.range.start.character + 1); + autocomplete->reparse = [this] { + autocomplete_rows.clear(); + }; - if(i == 0) - tooltip.buffer->insert_at_cursor("\n\n"); - else - tooltip.buffer->insert_at_cursor("\n"); - tooltip.insert_markdown(diagnostic.related_informations[i].message); - tooltip.buffer->insert_at_cursor(": "); - tooltip.buffer->insert_with_tag(tooltip.buffer->get_insert()->get_iter(), link, link_tag); - } - } + if(capabilities.signature_help) { + // 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(autocomplete_show_arguments) + autocomplete->stop(); + autocomplete_show_arguments = false; + autocomplete_delayed_show_arguments_connection.disconnect(); + autocomplete_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_argument()) { + autocomplete->stop(); + autocomplete->run(); + } + return false; + }, + 500); + }, + false); - if(!diagnostic.quickfixes.empty()) { - if(diagnostic.quickfixes.size() == 1) - tooltip.buffer->insert_at_cursor("\n\nFix-it:"); - else - tooltip.buffer->insert_at_cursor("\n\nFix-its:"); - for(auto &quickfix : diagnostic.quickfixes) { - tooltip.buffer->insert_at_cursor("\n"); - tooltip.insert_markdown(quickfix.first); - } - } - }); + // Remove argument completions + signal_key_press_event().connect( + [this](GdkEventKey *event) { + if(autocomplete_show_arguments && CompletionDialog::get() && CompletionDialog::get()->is_visible() && + event->keyval != GDK_KEY_Down && event->keyval != GDK_KEY_Up && + event->keyval != GDK_KEY_Return && event->keyval != GDK_KEY_KP_Enter && + event->keyval != GDK_KEY_ISO_Left_Tab && event->keyval != GDK_KEY_Tab && + (event->keyval < GDK_KEY_Shift_L || event->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); } - for(auto &mark : type_coverage_marks) { - add_diagnostic_tooltip(mark.first->get_iter(), mark.second->get_iter(), false, [](Tooltip &tooltip) { - tooltip.buffer->insert_at_cursor(type_coverage_message); - }); - num_warnings++; + 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; + }; + + std::function is_possible_xml_attribute = [](Gtk::TextIter) { return false; }; + if(is_js) { + autocomplete->is_restart_key = [](guint keyval) { + if(keyval == '.' || keyval == ' ') + return true; + return false; + }; + + is_possible_xml_attribute = [this](Gtk::TextIter iter) { + return (*iter == ' ' || iter.ends_line() || *iter == '/' || (*iter == '>' && iter.backward_char())) && find_open_symbol_backward(iter, iter, '<', '>'); + }; } - status_diagnostics = std::make_tuple(num_warnings, num_errors, num_fix_its); - if(update_status_diagnostics) - update_status_diagnostics(this); -} + autocomplete->run_check = [this, is_possible_xml_attribute]() { + auto prefix_start = get_buffer()->get_insert()->get_iter(); + auto prefix_end = prefix_start; -Gtk::TextIter Source::LanguageProtocolView::get_iter_at_line_pos(int line, int pos) { - if(capabilities.use_line_index) - return get_iter_at_line_index(line, pos); - return get_iter_at_line_index(line, utf16_code_units_byte_count(get_line(line), pos)); -} + auto prev = prefix_start; + prev.backward_char(); + if(!is_code_iter(prev)) + return false; -void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rectangle) { - if(!capabilities.hover) - return; + size_t count = 0; + while(prefix_start.backward_char() && is_token_char(*prefix_start)) + ++count; - 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; + autocomplete_enable_snippets = false; + autocomplete_show_arguments = false; - auto offset = iter.get_offset(); + if(prefix_start != prefix_end && !is_token_char(*prefix_start)) + prefix_start.forward_char(); - static int request_count = 0; - request_count++; - auto current_request = request_count; - client->write_request(this, "textDocument/hover", R"("textDocument": {"uri":")" + uri_escaped + R"("}, "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 - struct Content { - std::string value; - std::string kind; - }; - std::list contents; - auto contents_pt = result.get_child_optional("contents"); - if(!contents_pt) - return; - auto value = contents_pt->get_value(""); - if(!value.empty()) - contents.emplace_back(Content{value, "markdown"}); - else { - auto value_pt = contents_pt->get_optional("value"); - if(value_pt) { - auto kind = contents_pt->get("kind", ""); - if(kind.empty()) - kind = contents_pt->get("language", ""); - contents.emplace_back(Content{*value_pt, kind}); - } - else { - bool first_value = true; - for(auto it = contents_pt->begin(); it != contents_pt->end(); ++it) { - auto value = it->second.get("value", ""); - if(!value.empty()) { - auto kind = it->second.get("kind", ""); - if(kind.empty()) - kind = it->second.get("language", ""); - if(first_value) // Place first value, which most likely is type information, to front (workaround for flow-bin's language server) - contents.emplace_front(Content{value, kind}); - else - contents.emplace_back(Content{value, kind}); - first_value = false; - } - else { - value = it->second.get_value(""); - if(!value.empty()) - contents.emplace_back(Content{value, "markdown"}); - } - } + prev = prefix_start; + prev.backward_char(); + auto prevprev = prev; + if(*prev == '.') { + auto iter = prev; + bool starts_with_num = false; + size_t count = 0; + while(iter.backward_char() && is_token_char(*iter)) { + ++count; + starts_with_num = Glib::Unicode::isdigit(*iter); + } + if((count >= 1 || *iter == ')' || *iter == ']' || *iter == '"' || *iter == '\'' || *iter == '?') && !starts_with_num) { + { + LockGuard lock(autocomplete->prefix_mutex); + autocomplete->prefix = get_buffer()->get_text(prefix_start, prefix_end); } + return true; } - if(!contents.empty()) { - dispatcher.post([this, offset, contents = std::move(contents), current_request]() mutable { - if(current_request != request_count) - return; - if(Notebook::get().get_current_view() != this) - return; - if(offset >= get_buffer()->get_char_count()) - return; - type_tooltips.clear(); + } + else if((prevprev.backward_char() && *prevprev == ':' && *prev == ':')) { + { + LockGuard lock(autocomplete->prefix_mutex); + autocomplete->prefix = get_buffer()->get_text(prefix_start, prefix_end); + } + return true; + } + else if(count >= 3) { // part of symbol + { + LockGuard lock(autocomplete->prefix_mutex); + autocomplete->prefix = get_buffer()->get_text(prefix_start, prefix_end); + } + autocomplete_enable_snippets = true; + return true; + } + if(is_possible_argument()) { + autocomplete_show_arguments = true; + LockGuard lock(autocomplete->prefix_mutex); + autocomplete->prefix = ""; + return true; + } + if(is_possible_xml_attribute(prefix_start)) { + LockGuard lock(autocomplete->prefix_mutex); + autocomplete->prefix = ""; + return true; + } + if(!interactive_completion) { + { + LockGuard lock(autocomplete->prefix_mutex); + autocomplete->prefix = get_buffer()->get_text(prefix_start, prefix_end); + } + auto prevprev = prev; + autocomplete_enable_snippets = !(*prev == '.' || (prevprev.backward_char() && *prevprev == ':' && *prev == ':')); + return true; + } - auto token_iters = get_token_iters(get_buffer()->get_iter_at_offset(offset)); - type_tooltips.emplace_back(this, token_iters.first, token_iters.second, [this, offset, contents = std::move(contents)](Tooltip &tooltip) mutable { - bool first = true; - if(language_id == "python") { // Python might support markdown in the future - for(auto &content : contents) { - if(!first) - tooltip.buffer->insert_at_cursor("\n\n"); - first = false; - if(content.kind == "python") - tooltip.insert_code(content.value, content.kind); - else - tooltip.insert_docstring(content.value); - } - } - else { - for(auto &content : contents) { - if(!first) - tooltip.buffer->insert_at_cursor("\n\n"); - first = false; - if(content.kind == "plaintext" || content.kind.empty()) - tooltip.insert_with_links_tagged(content.value); - else if(content.kind == "markdown") - tooltip.insert_markdown(content.value); - else - tooltip.insert_code(content.value, content.kind); - tooltip.remove_trailing_newlines(); - } - } + return false; + }; -#ifdef JUCI_ENABLE_DEBUG - if(language_id == "rust" && capabilities.definition) { - if(Debug::LLDB::get().is_stopped()) { - Glib::ustring value_type = "Value"; + autocomplete->before_add_rows = [this] { + status_state = "autocomplete..."; + if(update_status_state) + update_status_state(this); + }; - auto token_iters = get_token_iters(get_buffer()->get_iter_at_offset(offset)); - auto offset = get_declaration(token_iters.first); + autocomplete->after_add_rows = [this] { + status_state = ""; + if(update_status_state) + update_status_state(this); + }; - auto variable = get_buffer()->get_text(token_iters.first, token_iters.second); - Glib::ustring debug_value = Debug::LLDB::get().get_value(variable, offset.file_path, offset.line + 1, offset.index + 1); - if(debug_value.empty()) { - debug_value = Debug::LLDB::get().get_return_value(file_path, token_iters.first.get_line() + 1, token_iters.first.get_line_index() + 1); - if(!debug_value.empty()) - value_type = "Return value"; + autocomplete->add_rows = [this](std::string &buffer, int line, int line_index) { + if(autocomplete->state == Autocomplete::State::starting) { + autocomplete_rows.clear(); + std::promise result_processed; + if(autocomplete_show_arguments) { + if(!capabilities.signature_help) + return true; + dispatcher.post([this, line, line_index, &result_processed] { + // Find current parameter number and previously used named parameters + unsigned current_parameter_position = 0; + auto named_parameter_symbol = get_named_parameter_symbol(); + std::set used_named_parameters; + auto iter = get_buffer()->get_insert()->get_iter(); + int para_count = 0; + int square_count = 0; + int angle_count = 0; + int curly_count = 0; + while(iter.backward_char() && backward_to_code(iter)) { + if(para_count == 0 && square_count == 0 && angle_count == 0 && curly_count == 0) { + if(named_parameter_symbol && (*iter == ',' || *iter == '(')) { + auto next = iter; + if(next.forward_char() && forward_to_code(next)) { + auto pair = get_token_iters(next); + if(pair.first != pair.second) { + auto symbol = pair.second; + if(forward_to_code(symbol) && *symbol == static_cast(*named_parameter_symbol)) + used_named_parameters.emplace(get_buffer()->get_text(pair.first, pair.second)); + } } - if(debug_value.empty()) { - auto end = token_iters.second; - while((end.ends_line() || *end == ' ' || *end == '\t') && end.forward_char()) { + } + if(*iter == ',') + ++current_parameter_position; + else if(*iter == '(') + break; + } + if(*iter == '(') + ++para_count; + else if(*iter == ')') + --para_count; + else if(*iter == '[') + ++square_count; + else if(*iter == ']') + --square_count; + else if(*iter == '<') + ++angle_count; + else if(*iter == '>') + --angle_count; + else if(*iter == '{') + ++curly_count; + else if(*iter == '}') + --curly_count; + } + bool using_named_parameters = named_parameter_symbol && !(current_parameter_position > 0 && used_named_parameters.empty()); + + write_request("textDocument/signatureHelp", {make_position(line, get_line_pos(line, line_index))}, [this, &result_processed, current_parameter_position, using_named_parameters, used_named_parameters = std::move(used_named_parameters)](const boost::property_tree::ptree &result, bool error) { + if(!error) { + auto signatures = result.get_child("signatures", boost::property_tree::ptree()); + for(auto signature_it = signatures.begin(); signature_it != signatures.end(); ++signature_it) { + auto parameters = signature_it->second.get_child("parameters", boost::property_tree::ptree()); + unsigned parameter_position = 0; + for(auto parameter_it = parameters.begin(); parameter_it != parameters.end(); ++parameter_it) { + if(parameter_position == current_parameter_position || using_named_parameters) { + auto label = parameter_it->second.get("label", ""); + auto insert = label; + auto documentation = parameter_it->second.get("documentation", ""); + std::string kind; + if(documentation.empty()) { + auto documentation_pt = parameter_it->second.get_child_optional("documentation"); + if(documentation_pt) { + documentation = documentation_pt->get("value", ""); + kind = documentation_pt->get("kind", ""); + } + } + if(documentation == "null") // Python erroneously returns "null" when a parameter is not documented + documentation.clear(); + if(!using_named_parameters || used_named_parameters.find(insert) == used_named_parameters.end()) { + autocomplete->rows.emplace_back(std::move(label)); + autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), {}, std::move(documentation), std::move(kind)}); + } } - if(*end != '(') { - auto iter = token_iters.first; - auto start = iter; - while(iter.backward_char()) { - if(*iter == '.') { - while(iter.backward_char() && (*iter == ' ' || *iter == '\t' || iter.ends_line())) { + parameter_position++; + } + } + } + result_processed.set_value(); + }); + }); + } + else { + dispatcher.post([this, line, line_index, &result_processed] { + write_request("textDocument/completion", {make_position(line, get_line_pos(line, line_index))}, [this, &result_processed](const boost::property_tree::ptree &result, bool error) { + if(!error) { + boost::property_tree::ptree::const_iterator begin, end; + if(auto items = result.get_child_optional("items")) { + begin = items->begin(); + end = items->end(); + } + else { + begin = result.begin(); + end = result.end(); + } + std::string prefix; + { + LockGuard lock(autocomplete->prefix_mutex); + prefix = autocomplete->prefix; + } + for(auto it = begin; it != end; ++it) { + auto label = it->second.get("label", ""); + if(starts_with(label, prefix)) { + auto detail = it->second.get("detail", ""); + auto documentation = it->second.get("documentation", ""); + std::string documentation_kind; + if(documentation.empty()) { + if(auto documentation_pt = it->second.get_child_optional("documentation")) { + documentation = documentation_pt->get("value", ""); + documentation_kind = documentation_pt->get("kind", ""); + } + } + auto insert = it->second.get("insertText", ""); + if(insert.empty()) + insert = it->second.get("textEdit.newText", ""); + if(insert.empty()) + insert = label; + if(!insert.empty()) { + auto kind = it->second.get("kind", 0); + if(kind >= 2 && kind <= 4 && insert.find('(') == std::string::npos) // If kind is method, function or constructor, but parentheses are missing + insert += "(${1:})"; + autocomplete->rows.emplace_back(std::move(label)); + autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), std::move(detail), std::move(documentation), std::move(documentation_kind)}); + } + } + } + + if(autocomplete_enable_snippets) { + LockGuard lock(snippets_mutex); + if(snippets) { + for(auto &snippet : *snippets) { + if(starts_with(snippet.prefix, prefix)) { + autocomplete->rows.emplace_back(snippet.prefix); + autocomplete_rows.emplace_back(AutocompleteRow{snippet.body, {}, snippet.description, {}}); + } + } + } + } + } + result_processed.set_value(); + }); + }); + } + result_processed.get_future().get(); + } + return true; + }; + + autocomplete->on_show = [this] { + hide_tooltips(); + }; + + autocomplete->on_hide = [this] { + autocomplete_rows.clear(); + }; + + autocomplete->on_select = [this](unsigned int index, const std::string &text, bool hide_window) { + auto insert = hide_window ? autocomplete_rows[index].insert : text; + + get_buffer()->erase(CompletionDialog::get()->start_mark->get_iter(), get_buffer()->get_insert()->get_iter()); + + // 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.erase(bracket_pos); + } + } + + // Do not instert ?. after ., instead replace . with ?. + if(1 < insert.size() && insert[0] == '?' && insert[1] == '.') { + auto iter = get_buffer()->get_insert()->get_iter(); + auto prev = iter; + if(prev.backward_char() && *prev == '.') { + get_buffer()->erase(prev, iter); + } + } + + if(hide_window) { + if(autocomplete_show_arguments) { + if(auto symbol = get_named_parameter_symbol()) // Do not select named parameters in for instance Python + get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), insert + *symbol); + else { + get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), insert); + int start_offset = CompletionDialog::get()->start_mark->get_iter().get_offset(); + int end_offset = CompletionDialog::get()->start_mark->get_iter().get_offset() + insert.size(); + get_buffer()->select_range(get_buffer()->get_iter_at_offset(start_offset), get_buffer()->get_iter_at_offset(end_offset)); + } + return; + } + + insert_snippet(CompletionDialog::get()->start_mark->get_iter(), insert); + auto iter = get_buffer()->get_insert()->get_iter(); + if(*iter == ')' && iter.backward_char() && *iter == '(') { // If no arguments, try signatureHelp + last_keyval = '('; + autocomplete->run(); + } + } + else + get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), insert); + }; + + autocomplete->set_tooltip_buffer = [this](unsigned int index) -> std::function { + auto autocomplete = autocomplete_rows[index]; + if(autocomplete.detail.empty() && autocomplete.documentation.empty()) + return nullptr; + return [this, autocomplete = std::move(autocomplete)](Tooltip &tooltip) mutable { + if(language_id == "python") // Python might support markdown in the future + tooltip.insert_docstring(autocomplete.documentation); + else { + if(!autocomplete.detail.empty()) { + tooltip.insert_code(autocomplete.detail, language); + tooltip.remove_trailing_newlines(); + } + if(!autocomplete.documentation.empty()) { + if(tooltip.buffer->size() > 0) + tooltip.buffer->insert_at_cursor("\n\n"); + if(autocomplete.kind == "plaintext" || autocomplete.kind.empty()) + tooltip.insert_with_links_tagged(autocomplete.documentation); + else if(autocomplete.kind == "markdown") + tooltip.insert_markdown(autocomplete.documentation); + else + tooltip.insert_code(autocomplete.documentation, autocomplete.kind); + } + } + }; + }; +} + +void Source::LanguageProtocolView::update_diagnostics_async(std::vector &&diagnostics) { + update_diagnostics_async_count++; + size_t last_count = update_diagnostics_async_count; + if(capabilities.code_action && !diagnostics.empty()) { + dispatcher.post([this, diagnostics = std::move(diagnostics), last_count]() mutable { + if(last_count != update_diagnostics_async_count) + return; + std::pair range; + std::string diagnostics_string; + for(auto &diagnostic : diagnostics) { + range = make_range({diagnostic.range.start.line, diagnostic.range.start.character}, {diagnostic.range.end.line, diagnostic.range.end.character}); + std::vector> diagnostic_params = {range}; + diagnostic_params.emplace_back("message", '"' + LanguageProtocol::escape_text(diagnostic.message) + '"'); + if(diagnostic.severity != 0) + diagnostic_params.emplace_back("severity", std::to_string(diagnostic.severity)); + if(!diagnostic.code.empty()) + diagnostic_params.emplace_back("code", '"' + diagnostic.code + '"'); + diagnostics_string += (diagnostics_string.empty() ? "{" : ",{") + to_string(diagnostic_params) + '}'; + } + if(diagnostics.size() != 1) { // Use diagnostic range if only one diagnostic, otherwise use whole buffer + auto start = get_buffer()->begin(); + auto end = get_buffer()->end(); + range = make_range({start.get_line(), get_line_pos(start)}, {end.get_line(), get_line_pos(end)}); + } + std::vector> params = {range, {"context", '{' + to_string({{"diagnostics", '[' + diagnostics_string + ']'}, {"only", "[\"quickfix\"]"}}) + '}'}}; + thread_pool.push([this, diagnostics = std::move(diagnostics), params = std::move(params), last_count]() mutable { + if(last_count != update_diagnostics_async_count) + return; + std::promise result_processed; + write_request("textDocument/codeAction", params, [this, &result_processed, &diagnostics, last_count](const boost::property_tree::ptree &result, bool error) { + if(!error && last_count == update_diagnostics_async_count) { + try { + for(auto it = result.begin(); it != result.end(); ++it) { + auto kind = it->second.get("kind", ""); + if(kind == "quickfix" || kind.empty()) { // Workaround for typescript-language-server (kind.empty()) + auto title = it->second.get("title"); + std::vector quickfix_diagnostics; + if(auto diagnostics_pt = it->second.get_child_optional("diagnostics")) { + for(auto it = diagnostics_pt->begin(); it != diagnostics_pt->end(); ++it) + quickfix_diagnostics.emplace_back(it->second); + } + if(auto changes = it->second.get_child_optional("edit.changes")) { + for(auto file_it = changes->begin(); file_it != changes->end(); ++file_it) { + for(auto edit_it = file_it->second.begin(); edit_it != file_it->second.end(); ++edit_it) { + LanguageProtocol::TextEdit edit(edit_it->second); + if(!quickfix_diagnostics.empty()) { + for(auto &diagnostic : diagnostics) { + for(auto &quickfix_diagnostic : quickfix_diagnostics) { + if(diagnostic.message == quickfix_diagnostic.message && diagnostic.range == quickfix_diagnostic.range) { + auto pair = diagnostic.quickfixes.emplace(title, std::set{}); + pair.first->second.emplace( + edit.new_text, + filesystem::get_path_from_uri(file_it->first).string(), + std::make_pair(Offset(edit.range.start.line, edit.range.start.character), + Offset(edit.range.end.line, edit.range.end.character))); + break; + } + } + } + } + else { // Workaround for language server that does not report quickfix diagnostics + for(auto &diagnostic : diagnostics) { + if(edit.range.start.line == diagnostic.range.start.line) { + auto pair = diagnostic.quickfixes.emplace(title, std::set{}); + pair.first->second.emplace( + edit.new_text, + filesystem::get_path_from_uri(file_it->first).string(), + std::make_pair(Offset(edit.range.start.line, edit.range.start.character), + Offset(edit.range.end.line, edit.range.end.character))); + break; + } + } + } + } + } + } + else { + auto changes_pt = it->second.get_child_optional("edit.documentChanges"); + if(!changes_pt) { // Workaround for typescript-language-server + if(auto arguments_pt = it->second.get_child_optional("arguments")) { + if(!arguments_pt->empty()) + changes_pt = arguments_pt->begin()->second.get_child_optional("documentChanges"); + } + } + if(changes_pt) { + for(auto change_it = changes_pt->begin(); change_it != changes_pt->end(); ++change_it) { + LanguageProtocol::TextDocumentEdit text_document_edit(change_it->second); + for(auto &edit : text_document_edit.edits) { + if(!quickfix_diagnostics.empty()) { + for(auto &diagnostic : diagnostics) { + for(auto &quickfix_diagnostic : quickfix_diagnostics) { + if(diagnostic.message == quickfix_diagnostic.message && diagnostic.range == quickfix_diagnostic.range) { + auto pair = diagnostic.quickfixes.emplace(title, std::set{}); + pair.first->second.emplace( + edit.new_text, + text_document_edit.file, + std::make_pair(Offset(edit.range.start.line, edit.range.start.character), + Offset(edit.range.end.line, edit.range.end.character))); + break; + } + } + } + } + else { // Workaround for language server that does not report quickfix diagnostics + for(auto &diagnostic : diagnostics) { + if(edit.range.start.line == diagnostic.range.start.line) { + auto pair = diagnostic.quickfixes.emplace(title, std::set{}); + pair.first->second.emplace( + edit.new_text, + text_document_edit.file, + std::make_pair(Offset(edit.range.start.line, edit.range.start.character), + Offset(edit.range.end.line, edit.range.end.character))); + break; + } + } + } } } - if(!is_token_char(*iter)) - break; - start = iter; - } - if(is_token_char(*start)) - debug_value = Debug::LLDB::get().get_value(get_buffer()->get_text(start, token_iters.second)); - } - } - 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_at_cursor("\n\n" + value_type + ":\n"); - tooltip.insert_code(debug_value.substr(pos + 3, debug_value.size() - (pos + 3) - 1)); } } } } -#endif - }); - type_tooltips.show(); + catch(...) { + } + } + result_processed.set_value(); + }); + result_processed.get_future().get(); + dispatcher.post([this, diagnostics = std::move(diagnostics), last_count]() mutable { + if(last_count == update_diagnostics_async_count) { + last_diagnostics = diagnostics; + update_diagnostics(std::move(diagnostics)); + } }); - } - } - }); -} - -void Source::LanguageProtocolView::apply_similar_symbol_tag() { - 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, R"("textDocument":{"uri":")" + uri_escaped + R"("}, "position": {"line": )" + std::to_string(iter.get_line()) + ", \"character\": " + std::to_string(iter.get_line_offset()) + R"(}, "context": {"includeDeclaration": true})", [this, current_request](const boost::property_tree::ptree &result, bool error) { - if(!error) { - std::vector ranges; - for(auto it = result.begin(); it != result.end(); ++it) { - try { - if(capabilities.document_highlight || it->second.get("uri") == uri) - ranges.emplace_back(it->second.get_child("range")); - } - catch(...) { - } - } - dispatcher.post([this, ranges = std::move(ranges), current_request] { - if(current_request != request_count || !similar_symbol_tag_applied) - return; - get_buffer()->remove_tag(similar_symbol_tag, get_buffer()->begin(), get_buffer()->end()); - for(auto &range : ranges) { - auto start = get_iter_at_line_pos(range.start.line, range.start.character); - auto end = get_iter_at_line_pos(range.end.line, range.end.character); - get_buffer()->apply_tag(similar_symbol_tag, start, end); - } - }); - } - }); -} - -void Source::LanguageProtocolView::apply_clickable_tag(const Gtk::TextIter &iter) { - static int request_count = 0; - request_count++; - auto current_request = request_count; - auto line = iter.get_line(); - auto offset = iter.get_line_offset(); - client->write_request(this, "textDocument/definition", R"("textDocument":{"uri":")" + uri_escaped + R"("}, "position": {"line": )" + std::to_string(line) + ", \"character\": " + std::to_string(offset) + "}", [this, current_request, line, offset](const boost::property_tree::ptree &result, bool error) { - if(!error && !result.empty()) { - dispatcher.post([this, current_request, line, offset] { - if(current_request != request_count || !clickable_tag_applied) - return; - get_buffer()->remove_tag(clickable_tag, get_buffer()->begin(), get_buffer()->end()); - auto range = get_token_iters(get_iter_at_line_pos(line, offset)); - get_buffer()->apply_tag(clickable_tag, range.first, range.second); }); - } - }); -} - -Source::Offset Source::LanguageProtocolView::get_declaration(const Gtk::TextIter &iter) { - auto offset = std::make_shared(); - std::promise result_processed; - client->write_request(this, "textDocument/definition", R"("textDocument":{"uri":")" + uri_escaped + R"("}, "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) { - try { - LanguageProtocol::Location location(it->second); - offset->file_path = std::move(location.file); - offset->line = location.range.start.line; - offset->index = location.range.start.character; - break; // TODO: can a language server return several definitions? - } - catch(...) { - } - } - } - result_processed.set_value(); - }); - result_processed.get_future().get(); - return *offset; -} - -void Source::LanguageProtocolView::setup_signals() { - if(capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::incremental) { - get_buffer()->signal_insert().connect( - [this](const Gtk::TextIter &start, const Glib::ustring &text, int bytes) { - client->write_notification("textDocument/didChange", R"("textDocument":{"uri":")" + uri_escaped + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + R"({"range":{"start":{"line": )" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(}},"text":")" + LanguageProtocol::escape_text(text) + "\"}" + "]"); - }, - false); - - get_buffer()->signal_erase().connect( - [this](const Gtk::TextIter &start, const Gtk::TextIter &end) { - client->write_notification("textDocument/didChange", R"("textDocument":{"uri":")" + uri_escaped + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + R"({"range":{"start":{"line": )" + std::to_string(start.get_line()) + ",\"character\":" + std::to_string(start.get_line_offset()) + R"(},"end":{"line":)" + std::to_string(end.get_line()) + ",\"character\":" + std::to_string(end.get_line_offset()) + R"(}},"text":""})" + "]"); - }, - false); + }); } - else if(capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::full) { - get_buffer()->signal_changed().connect([this]() { - client->write_notification("textDocument/didChange", R"("textDocument":{"uri":")" + uri_escaped + R"(","version":)" + std::to_string(document_version++) + "},\"contentChanges\":[" + R"({"text":")" + LanguageProtocol::escape_text(get_buffer()->get_text()) + "\"}" + "]"); + else { + dispatcher.post([this, diagnostics = std::move(diagnostics), last_count]() mutable { + if(last_count == update_diagnostics_async_count) { + last_diagnostics = diagnostics; + update_diagnostics(std::move(diagnostics)); + } }); } } -void Source::LanguageProtocolView::setup_autocomplete() { - autocomplete = std::make_unique(this, interactive_completion, last_keyval, false); - - if(!capabilities.completion) - return; - - non_interactive_completion = [this] { - if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) - return; - autocomplete->run(); - }; - - autocomplete->reparse = [this] { - autocomplete_rows.clear(); - }; - - if(capabilities.signature_help) { - // 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(autocomplete_show_arguments) - autocomplete->stop(); - autocomplete_show_arguments = false; - autocomplete_delayed_show_arguments_connection.disconnect(); - autocomplete_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_argument()) { - autocomplete->stop(); - autocomplete->run(); - } - return false; - }, - 500); - }, - false); - - // Remove argument completions - signal_key_press_event().connect( - [this](GdkEventKey *event) { - if(autocomplete_show_arguments && CompletionDialog::get() && CompletionDialog::get()->is_visible() && - event->keyval != GDK_KEY_Down && event->keyval != GDK_KEY_Up && - event->keyval != GDK_KEY_Return && event->keyval != GDK_KEY_KP_Enter && - event->keyval != GDK_KEY_ISO_Left_Tab && event->keyval != GDK_KEY_Tab && - (event->keyval < GDK_KEY_Shift_L || event->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 == ':')) - return true; - return false; - }; - - std::function is_possible_xml_attribute = [](Gtk::TextIter) { return false; }; - if(is_js) { - autocomplete->is_restart_key = [](guint keyval) { - if(keyval == '.' || keyval == ' ') - return true; - return false; - }; +void Source::LanguageProtocolView::update_diagnostics(std::vector diagnostics) { + diagnostic_offsets.clear(); + diagnostic_tooltips.clear(); + fix_its.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()); + num_warnings = 0; + num_errors = 0; + num_fix_its = 0; - is_possible_xml_attribute = [this](Gtk::TextIter iter) { - return (*iter == ' ' || iter.ends_line() || *iter == '/' || (*iter == '>' && iter.backward_char())) && find_open_symbol_backward(iter, iter, '<', '>'); - }; - } + for(auto &diagnostic : diagnostics) { + auto start = get_iter_at_line_pos(diagnostic.range.start.line, diagnostic.range.start.character); + auto end = get_iter_at_line_pos(diagnostic.range.end.line, diagnostic.range.end.character); - autocomplete->run_check = [this, is_possible_xml_attribute]() { - auto prefix_start = get_buffer()->get_insert()->get_iter(); - auto prefix_end = prefix_start; + if(start == end) { + if(!end.ends_line()) + end.forward_char(); + else + while(start.ends_line() && start.backward_char()) { // Move start so that diagnostic underline is visible + } + } - auto prev = prefix_start; - prev.backward_char(); - if(!is_code_iter(prev)) - return false; + bool error = false; + if(diagnostic.severity >= 2) + num_warnings++; + else { + num_errors++; + error = true; + } + num_fix_its += diagnostic.quickfixes.size(); - size_t count = 0; - while(prefix_start.backward_char() && is_token_char(*prefix_start)) - ++count; + for(auto &quickfix : diagnostic.quickfixes) + fix_its.insert(fix_its.end(), quickfix.second.begin(), quickfix.second.end()); - autocomplete_enable_snippets = false; - autocomplete_show_arguments = false; + add_diagnostic_tooltip(start, end, error, [this, diagnostic = std::move(diagnostic)](Tooltip &tooltip) { + if(language_id == "python") { // Python might support markdown in the future + tooltip.insert_with_links_tagged(diagnostic.message); + return; + } + tooltip.insert_markdown(diagnostic.message); - if(prefix_start != prefix_end && !is_token_char(*prefix_start)) - prefix_start.forward_char(); + if(!diagnostic.related_informations.empty()) { + auto link_tag = tooltip.buffer->get_tag_table()->lookup("link"); + for(size_t i = 0; i < diagnostic.related_informations.size(); ++i) { + auto link = filesystem::get_relative_path(diagnostic.related_informations[i].location.file, file_path.parent_path()).string(); + link += ':' + std::to_string(diagnostic.related_informations[i].location.range.start.line + 1); + link += ':' + std::to_string(diagnostic.related_informations[i].location.range.start.character + 1); - prev = prefix_start; - prev.backward_char(); - auto prevprev = prev; - if(*prev == '.') { - auto iter = prev; - bool starts_with_num = false; - size_t count = 0; - while(iter.backward_char() && is_token_char(*iter)) { - ++count; - starts_with_num = Glib::Unicode::isdigit(*iter); - } - if((count >= 1 || *iter == ')' || *iter == ']' || *iter == '"' || *iter == '\'' || *iter == '?') && !starts_with_num) { - { - LockGuard lock(autocomplete->prefix_mutex); - autocomplete->prefix = get_buffer()->get_text(prefix_start, prefix_end); + if(i == 0) + tooltip.buffer->insert_at_cursor("\n\n"); + else + tooltip.buffer->insert_at_cursor("\n"); + tooltip.insert_markdown(diagnostic.related_informations[i].message); + tooltip.buffer->insert_at_cursor(": "); + tooltip.buffer->insert_with_tag(tooltip.buffer->get_insert()->get_iter(), link, link_tag); } - return true; - } - } - else if((prevprev.backward_char() && *prevprev == ':' && *prev == ':')) { - { - LockGuard lock(autocomplete->prefix_mutex); - autocomplete->prefix = get_buffer()->get_text(prefix_start, prefix_end); - } - return true; - } - else if(count >= 3) { // part of symbol - { - LockGuard lock(autocomplete->prefix_mutex); - autocomplete->prefix = get_buffer()->get_text(prefix_start, prefix_end); } - autocomplete_enable_snippets = true; - return true; - } - if(is_possible_argument()) { - autocomplete_show_arguments = true; - LockGuard lock(autocomplete->prefix_mutex); - autocomplete->prefix = ""; - return true; - } - if(is_possible_xml_attribute(prefix_start)) { - LockGuard lock(autocomplete->prefix_mutex); - autocomplete->prefix = ""; - return true; - } - if(!interactive_completion) { - { - LockGuard lock(autocomplete->prefix_mutex); - autocomplete->prefix = get_buffer()->get_text(prefix_start, prefix_end); + + if(!diagnostic.quickfixes.empty()) { + if(diagnostic.quickfixes.size() == 1) + tooltip.buffer->insert_at_cursor("\n\nFix-it:"); + else + tooltip.buffer->insert_at_cursor("\n\nFix-its:"); + for(auto &quickfix : diagnostic.quickfixes) { + tooltip.buffer->insert_at_cursor("\n"); + tooltip.insert_markdown(quickfix.first); + } } - auto prevprev = prev; - autocomplete_enable_snippets = !(*prev == '.' || (prevprev.backward_char() && *prevprev == ':' && *prev == ':')); - return true; - } + }); + } - return false; - }; + for(auto &mark : type_coverage_marks) { + add_diagnostic_tooltip(mark.first->get_iter(), mark.second->get_iter(), false, [](Tooltip &tooltip) { + tooltip.buffer->insert_at_cursor(type_coverage_message); + }); + num_warnings++; + } - autocomplete->before_add_rows = [this] { - status_state = "autocomplete..."; - if(update_status_state) - update_status_state(this); - }; + status_diagnostics = std::make_tuple(num_warnings, num_errors, num_fix_its); + if(update_status_diagnostics) + update_status_diagnostics(this); +} - autocomplete->after_add_rows = [this] { - status_state = ""; - if(update_status_state) - update_status_state(this); - }; +void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rectangle) { + if(!capabilities.hover) + return; - autocomplete->add_rows = [this](std::string &buffer, int line_number, int column) { - if(autocomplete->state == Autocomplete::State::starting) { - autocomplete_rows.clear(); - std::promise result_processed; - if(autocomplete_show_arguments) { - if(!capabilities.signature_help) - return true; - dispatcher.post([this, line_number, column, &result_processed] { - // Find current parameter number and previously used named parameters - unsigned current_parameter_position = 0; - auto named_parameter_symbol = get_named_parameter_symbol(); - std::set used_named_parameters; - auto iter = get_buffer()->get_insert()->get_iter(); - int para_count = 0; - int square_count = 0; - int angle_count = 0; - int curly_count = 0; - while(iter.backward_char() && backward_to_code(iter)) { - if(para_count == 0 && square_count == 0 && angle_count == 0 && curly_count == 0) { - if(named_parameter_symbol && (*iter == ',' || *iter == '(')) { - auto next = iter; - if(next.forward_char() && forward_to_code(next)) { - auto pair = get_token_iters(next); - if(pair.first != pair.second) { - auto symbol = pair.second; - if(forward_to_code(symbol) && *symbol == static_cast(*named_parameter_symbol)) - used_named_parameters.emplace(get_buffer()->get_text(pair.first, pair.second)); - } - } - } - if(*iter == ',') - ++current_parameter_position; - else if(*iter == '(') - break; - } - if(*iter == '(') - ++para_count; - else if(*iter == ')') - --para_count; - else if(*iter == '[') - ++square_count; - else if(*iter == ']') - --square_count; - else if(*iter == '<') - ++angle_count; - else if(*iter == '>') - --angle_count; - else if(*iter == '{') - ++curly_count; - else if(*iter == '}') - --curly_count; - } - bool using_named_parameters = named_parameter_symbol && !(current_parameter_position > 0 && used_named_parameters.empty()); + 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; - client->write_request(this, "textDocument/signatureHelp", R"("textDocument":{"uri":")" + uri_escaped + R"("}, "position": {"line": )" + std::to_string(line_number - 1) + ", \"character\": " + std::to_string(column - 1) + "}", [this, &result_processed, current_parameter_position, using_named_parameters, used_named_parameters = std::move(used_named_parameters)](const boost::property_tree::ptree &result, bool error) { - if(!error) { - auto signatures = result.get_child("signatures", boost::property_tree::ptree()); - for(auto signature_it = signatures.begin(); signature_it != signatures.end(); ++signature_it) { - auto parameters = signature_it->second.get_child("parameters", boost::property_tree::ptree()); - unsigned parameter_position = 0; - for(auto parameter_it = parameters.begin(); parameter_it != parameters.end(); ++parameter_it) { - if(parameter_position == current_parameter_position || using_named_parameters) { - auto label = parameter_it->second.get("label", ""); - auto insert = label; - auto documentation = parameter_it->second.get("documentation", ""); - std::string kind; - if(documentation.empty()) { - auto documentation_pt = parameter_it->second.get_child_optional("documentation"); - if(documentation_pt) { - documentation = documentation_pt->get("value", ""); - kind = documentation_pt->get("kind", ""); - } - } - if(documentation == "null") // Python erroneously returns "null" when a parameter is not documented - documentation.clear(); - if(!using_named_parameters || used_named_parameters.find(insert) == used_named_parameters.end()) { - autocomplete->rows.emplace_back(std::move(label)); - autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), {}, std::move(documentation), std::move(kind)}); - } - } - parameter_position++; - } - } - } - result_processed.set_value(); - }); - }); - } + auto offset = iter.get_offset(); + + static int request_count = 0; + request_count++; + auto current_request = request_count; + write_request("textDocument/hover", {make_position(iter.get_line(), get_line_pos(iter))}, [this, offset, current_request](const boost::property_tree::ptree &result, bool error) { + if(!error) { + // hover result structure vary significantly from the different language servers + struct Content { + std::string value; + std::string kind; + }; + std::list contents; + auto contents_pt = result.get_child_optional("contents"); + if(!contents_pt) + return; + auto value = contents_pt->get_value(""); + if(!value.empty()) + contents.emplace_back(Content{value, "markdown"}); else { - client->write_request(this, "textDocument/completion", R"("textDocument":{"uri":")" + uri_escaped + R"("}, "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) { - boost::property_tree::ptree::const_iterator begin, end; - if(auto items = result.get_child_optional("items")) { - begin = items->begin(); - end = items->end(); + auto value_pt = contents_pt->get_optional("value"); + if(value_pt) { + auto kind = contents_pt->get("kind", ""); + if(kind.empty()) + kind = contents_pt->get("language", ""); + contents.emplace_back(Content{*value_pt, kind}); + } + else { + bool first_value = true; + for(auto it = contents_pt->begin(); it != contents_pt->end(); ++it) { + auto value = it->second.get("value", ""); + if(!value.empty()) { + auto kind = it->second.get("kind", ""); + if(kind.empty()) + kind = it->second.get("language", ""); + if(first_value) // Place first value, which most likely is type information, to front (workaround for flow-bin's language server) + contents.emplace_front(Content{value, kind}); + else + contents.emplace_back(Content{value, kind}); + first_value = false; } else { - begin = result.begin(); - end = result.end(); + value = it->second.get_value(""); + if(!value.empty()) + contents.emplace_back(Content{value, "markdown"}); } - std::string prefix; - { - LockGuard lock(autocomplete->prefix_mutex); - prefix = autocomplete->prefix; + } + } + } + if(!contents.empty()) { + dispatcher.post([this, offset, contents = std::move(contents), current_request]() mutable { + if(current_request != request_count) + return; + if(Notebook::get().get_current_view() != this) + return; + if(offset >= get_buffer()->get_char_count()) + return; + type_tooltips.clear(); + + auto token_iters = get_token_iters(get_buffer()->get_iter_at_offset(offset)); + type_tooltips.emplace_back(this, token_iters.first, token_iters.second, [this, offset, contents = std::move(contents)](Tooltip &tooltip) mutable { + bool first = true; + if(language_id == "python") { // Python might support markdown in the future + for(auto &content : contents) { + if(!first) + tooltip.buffer->insert_at_cursor("\n\n"); + first = false; + if(content.kind == "python") + tooltip.insert_code(content.value, content.kind); + else + tooltip.insert_docstring(content.value); + } } - for(auto it = begin; it != end; ++it) { - auto label = it->second.get("label", ""); - if(starts_with(label, prefix)) { - auto detail = it->second.get("detail", ""); - auto documentation = it->second.get("documentation", ""); - std::string documentation_kind; - if(documentation.empty()) { - if(auto documentation_pt = it->second.get_child_optional("documentation")) { - documentation = documentation_pt->get("value", ""); - documentation_kind = documentation_pt->get("kind", ""); - } - } - auto insert = it->second.get("insertText", ""); - if(insert.empty()) - insert = it->second.get("textEdit.newText", ""); - if(insert.empty()) - insert = label; - if(!insert.empty()) { - auto kind = it->second.get("kind", 0); - if(kind >= 2 && kind <= 4 && insert.find('(') == std::string::npos) // If kind is method, function or constructor, but parentheses are missing - insert += "(${1:})"; - autocomplete->rows.emplace_back(std::move(label)); - autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), std::move(detail), std::move(documentation), std::move(documentation_kind)}); - } + else { + for(auto &content : contents) { + if(!first) + tooltip.buffer->insert_at_cursor("\n\n"); + first = false; + if(content.kind == "plaintext" || content.kind.empty()) + tooltip.insert_with_links_tagged(content.value); + else if(content.kind == "markdown") + tooltip.insert_markdown(content.value); + else + tooltip.insert_code(content.value, content.kind); + tooltip.remove_trailing_newlines(); } } - if(autocomplete_enable_snippets) { - LockGuard lock(snippets_mutex); - if(snippets) { - for(auto &snippet : *snippets) { - if(starts_with(snippet.prefix, prefix)) { - autocomplete->rows.emplace_back(snippet.prefix); - autocomplete_rows.emplace_back(AutocompleteRow{snippet.body, {}, snippet.description, {}}); +#ifdef JUCI_ENABLE_DEBUG + if(language_id == "rust" && capabilities.definition) { + if(Debug::LLDB::get().is_stopped()) { + Glib::ustring value_type = "Value"; + + auto token_iters = get_token_iters(get_buffer()->get_iter_at_offset(offset)); + auto offset = get_declaration(token_iters.first); + + auto variable = get_buffer()->get_text(token_iters.first, token_iters.second); + Glib::ustring debug_value = Debug::LLDB::get().get_value(variable, offset.file_path, offset.line + 1, offset.index + 1); + if(debug_value.empty()) { + debug_value = Debug::LLDB::get().get_return_value(file_path, token_iters.first.get_line() + 1, token_iters.first.get_line_index() + 1); + if(!debug_value.empty()) + value_type = "Return value"; + } + if(debug_value.empty()) { + auto end = token_iters.second; + while((end.ends_line() || *end == ' ' || *end == '\t') && end.forward_char()) { + } + if(*end != '(') { + auto iter = token_iters.first; + auto start = iter; + while(iter.backward_char()) { + if(*iter == '.') { + while(iter.backward_char() && (*iter == ' ' || *iter == '\t' || iter.ends_line())) { + } + } + if(!is_token_char(*iter)) + break; + start = iter; + } + if(is_token_char(*start)) + debug_value = Debug::LLDB::get().get_value(get_buffer()->get_text(start, token_iters.second)); + } + } + 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_at_cursor("\n\n" + value_type + ":\n"); + tooltip.insert_code(debug_value.substr(pos + 3, debug_value.size() - (pos + 3) - 1)); } } } } - } - result_processed.set_value(); +#endif + }); + type_tooltips.show(); }); } - result_processed.get_future().get(); } - return true; - }; - - autocomplete->on_show = [this] { - hide_tooltips(); - }; - - autocomplete->on_hide = [this] { - autocomplete_rows.clear(); - }; - - autocomplete->on_select = [this](unsigned int index, const std::string &text, bool hide_window) { - auto insert = hide_window ? autocomplete_rows[index].insert : text; + }); +} - get_buffer()->erase(CompletionDialog::get()->start_mark->get_iter(), get_buffer()->get_insert()->get_iter()); +void Source::LanguageProtocolView::apply_similar_symbol_tag() { + if(!capabilities.document_highlight) + return; - // 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.erase(bracket_pos); + auto iter = get_buffer()->get_insert()->get_iter(); + static int request_count = 0; + request_count++; + auto current_request = request_count; + write_request("textDocument/documentHighlight", {make_position(iter.get_line(), get_line_pos(iter)), {"context", "{\"includeDeclaration\":true}"}}, [this, current_request](const boost::property_tree::ptree &result, bool error) { + if(!error) { + std::vector ranges; + for(auto it = result.begin(); it != result.end(); ++it) { + try { + if(capabilities.document_highlight || it->second.get("uri") == uri) + ranges.emplace_back(it->second.get_child("range")); + } + catch(...) { + } } + dispatcher.post([this, ranges = std::move(ranges), current_request] { + if(current_request != request_count || !similar_symbol_tag_applied) + return; + get_buffer()->remove_tag(similar_symbol_tag, get_buffer()->begin(), get_buffer()->end()); + for(auto &range : ranges) { + auto start = get_iter_at_line_pos(range.start.line, range.start.character); + auto end = get_iter_at_line_pos(range.end.line, range.end.character); + get_buffer()->apply_tag(similar_symbol_tag, start, end); + } + }); } + }); +} - // Do not instert ?. after ., instead replace . with ?. - if(1 < insert.size() && insert[0] == '?' && insert[1] == '.') { - auto iter = get_buffer()->get_insert()->get_iter(); - auto prev = iter; - if(prev.backward_char() && *prev == '.') { - get_buffer()->erase(prev, iter); - } +void Source::LanguageProtocolView::apply_clickable_tag(const Gtk::TextIter &iter) { + static int request_count = 0; + request_count++; + auto current_request = request_count; + write_request("textDocument/definition", {make_position(iter.get_line(), get_line_pos(iter))}, [this, current_request, line = iter.get_line(), line_offset = iter.get_line_offset()](const boost::property_tree::ptree &result, bool error) { + if(!error && !result.empty()) { + dispatcher.post([this, current_request, line, line_offset] { + if(current_request != request_count || !clickable_tag_applied) + return; + get_buffer()->remove_tag(clickable_tag, get_buffer()->begin(), get_buffer()->end()); + auto range = get_token_iters(get_iter_at_line_offset(line, line_offset)); + get_buffer()->apply_tag(clickable_tag, range.first, range.second); + }); } + }); +} - if(hide_window) { - if(autocomplete_show_arguments) { - if(auto symbol = get_named_parameter_symbol()) // Do not select named parameters in for instance Python - get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), insert + *symbol); - else { - get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), insert); - int start_offset = CompletionDialog::get()->start_mark->get_iter().get_offset(); - int end_offset = CompletionDialog::get()->start_mark->get_iter().get_offset() + insert.size(); - get_buffer()->select_range(get_buffer()->get_iter_at_offset(start_offset), get_buffer()->get_iter_at_offset(end_offset)); +Source::Offset Source::LanguageProtocolView::get_declaration(const Gtk::TextIter &iter) { + auto offset = std::make_shared(); + std::promise result_processed; + write_request("textDocument/definition", {make_position(iter.get_line(), get_line_pos(iter))}, [offset, &result_processed](const boost::property_tree::ptree &result, bool error) { + if(!error) { + for(auto it = result.begin(); it != result.end(); ++it) { + try { + LanguageProtocol::Location location(it->second); + offset->file_path = std::move(location.file); + offset->line = location.range.start.line; + offset->index = location.range.start.character; + break; // TODO: can a language server return several definitions? + } + catch(...) { } - return; } + } + result_processed.set_value(); + }); + result_processed.get_future().get(); + return *offset; +} - insert_snippet(CompletionDialog::get()->start_mark->get_iter(), insert); - auto iter = get_buffer()->get_insert()->get_iter(); - if(*iter == ')' && iter.backward_char() && *iter == '(') { // If no arguments, try signatureHelp - last_keyval = '('; - autocomplete->run(); +Source::Offset Source::LanguageProtocolView::get_type_declaration(const Gtk::TextIter &iter) { + auto offset = std::make_shared(); + std::promise result_processed; + write_request("textDocument/typeDefinition", {make_position(iter.get_line(), get_line_pos(iter))}, [offset, &result_processed](const boost::property_tree::ptree &result, bool error) { + if(!error) { + for(auto it = result.begin(); it != result.end(); ++it) { + try { + LanguageProtocol::Location location(it->second); + offset->file_path = std::move(location.file); + offset->line = location.range.start.line; + offset->index = location.range.start.character; + break; // TODO: can a language server return several type definitions? + } + catch(...) { + } } } - else - get_buffer()->insert(CompletionDialog::get()->start_mark->get_iter(), insert); - }; + result_processed.set_value(); + }); + result_processed.get_future().get(); + return *offset; +} - autocomplete->set_tooltip_buffer = [this](unsigned int index) -> std::function { - auto autocomplete = autocomplete_rows[index]; - if(autocomplete.detail.empty() && autocomplete.documentation.empty()) - return nullptr; - return [this, autocomplete = std::move(autocomplete)](Tooltip &tooltip) mutable { - if(language_id == "python") // Python might support markdown in the future - tooltip.insert_docstring(autocomplete.documentation); - else { - if(!autocomplete.detail.empty()) { - tooltip.insert_code(autocomplete.detail, language); - tooltip.remove_trailing_newlines(); +std::vector Source::LanguageProtocolView::get_implementations(const Gtk::TextIter &iter) { + auto offsets = std::make_shared>(); + std::promise result_processed; + write_request("textDocument/implementation", {make_position(iter.get_line(), get_line_pos(iter))}, [offsets, &result_processed](const boost::property_tree::ptree &result, bool error) { + if(!error) { + for(auto it = result.begin(); it != result.end(); ++it) { + try { + LanguageProtocol::Location location(it->second); + offsets->emplace_back(location.range.start.line, location.range.start.character, location.file); } - if(!autocomplete.documentation.empty()) { - if(tooltip.buffer->size() > 0) - tooltip.buffer->insert_at_cursor("\n\n"); - if(autocomplete.kind == "plaintext" || autocomplete.kind.empty()) - tooltip.insert_with_links_tagged(autocomplete.documentation); - else if(autocomplete.kind == "markdown") - tooltip.insert_markdown(autocomplete.documentation); - else - tooltip.insert_code(autocomplete.documentation, autocomplete.kind); + catch(...) { } } - }; - }; + } + result_processed.set_value(); + }); + result_processed.get_future().get(); + return *offsets; } boost::optional Source::LanguageProtocolView::get_named_parameter_symbol() { @@ -1969,7 +2104,7 @@ boost::optional Source::LanguageProtocolView::get_named_parameter_symbol() void Source::LanguageProtocolView::update_type_coverage() { if(capabilities.type_coverage) { - client->write_request(this, "textDocument/typeCoverage", R"("textDocument": {"uri":")" + uri_escaped + "\"}", [this](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/typeCoverage", {}, [this](const boost::property_tree::ptree &result, bool error) { if(error) { if(update_type_coverage_retries > 0) { // Retry typeCoverage request, since these requests can fail while waiting for language server to start dispatcher.post([this] { diff --git a/src/source_language_protocol.hpp b/src/source_language_protocol.hpp index fa50fe2..dd6e22e 100644 --- a/src/source_language_protocol.hpp +++ b/src/source_language_protocol.hpp @@ -101,6 +101,8 @@ namespace LanguageProtocol { bool completion = false; bool signature_help = false; bool definition = false; + bool type_definition = false; + bool implementation = false; bool references = false; bool document_highlight = false; bool workspace_symbol = false; @@ -155,7 +157,7 @@ namespace LanguageProtocol { void parse_server_message(); void write_request(Source::LanguageProtocolView *view, const std::string &method, const std::string ¶ms, std::function &&function = nullptr); void write_response(size_t id, const std::string &result); - 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_notification(const std::string &method, const boost::property_tree::ptree ¶ms); void handle_server_request(size_t id, const std::string &method, const boost::property_tree::ptree ¶ms); @@ -174,14 +176,31 @@ namespace Source { void rename(const boost::filesystem::path &path) override; bool save() override; - void update_diagnostics_async(std::vector &&diagnostics); - private: - std::atomic update_diagnostics_async_count = {0}; + /// Get line offset depending on if utf-8 byte offsets or utf-16 code units are used + int get_line_pos(const Gtk::TextIter &iter); + /// Get line offset depending on if utf-8 byte offsets or utf-16 code units are used + int get_line_pos(int line, int line_index); + + std::pair make_position(int line, int character); + std::pair make_range(const std::pair &start, const std::pair &end); + std::string to_string(const std::pair ¶m); + std::string to_string(const std::vector> ¶ms); + /// Helper method for calling client->write_request + void write_request(const std::string &method, const std::vector> ¶ms, std::function &&function); + /// Helper method for calling client->write_notification + void write_notification(const std::string &method); + /// Helper method for calling client->write_notification + void write_did_open_notification(); + /// Helper method for calling client->write_notification + void write_did_change_notification(const std::vector> ¶ms); + std::atomic update_diagnostics_async_count = {0}; void update_diagnostics(std::vector diagnostics); public: + void update_diagnostics_async(std::vector &&diagnostics); + Gtk::TextIter get_iter_at_line_pos(int line, int pos) override; std::string uri; @@ -208,14 +227,16 @@ namespace Source { Glib::ThreadPool thread_pool; void setup_navigation_and_refactoring(); + void setup_signals(); + void setup_autocomplete(); void tag_similar_symbols(); Offset get_declaration(const Gtk::TextIter &iter); + Offset get_type_declaration(const Gtk::TextIter &iter); + std::vector get_implementations(const Gtk::TextIter &iter); std::unique_ptr autocomplete; - void setup_signals(); - void setup_autocomplete(); struct AutocompleteRow { std::string insert; diff --git a/src/utility.cpp b/src/utility.cpp index e30c4f8..cc5938e 100644 --- a/src/utility.cpp +++ b/src/utility.cpp @@ -7,31 +7,31 @@ ScopeGuard::~ScopeGuard() { } size_t utf8_character_count(const std::string &text, size_t pos, size_t length) noexcept { - size_t count = 0; + size_t characters = 0; auto size = length == std::string::npos ? text.size() : std::min(pos + length, text.size()); for(; pos < size;) { if(static_cast(text[pos]) <= 0b01111111) { - ++count; + ++characters; ++pos; } else if(static_cast(text[pos]) >= 0b11111000) // Invalid UTF-8 byte ++pos; else if(static_cast(text[pos]) >= 0b11110000) { - ++count; + ++characters; pos += 4; } else if(static_cast(text[pos]) >= 0b11100000) { - ++count; + ++characters; pos += 3; } else if(static_cast(text[pos]) >= 0b11000000) { - ++count; + ++characters; pos += 2; } else // // Invalid start of UTF-8 character ++pos; } - return count; + return characters; } size_t utf16_code_units_byte_count(const std::string &text, size_t code_units, size_t start_pos) { @@ -73,6 +73,34 @@ size_t utf16_code_units_byte_count(const std::string &text, size_t code_units, s return pos - start_pos; } +size_t utf16_code_unit_count(const std::string &text, size_t pos, size_t length) { + size_t code_units = 0; + auto size = length == std::string::npos ? text.size() : std::min(pos + length, text.size()); + for(; pos < size;) { + if(static_cast(text[pos]) <= 0b01111111) { + ++code_units; + ++pos; + } + else if(static_cast(text[pos]) >= 0b11111000) // Invalid UTF-8 byte + ++pos; + else if(static_cast(text[pos]) >= 0b11110000) { + code_units += 2; + pos += 4; + } + else if(static_cast(text[pos]) >= 0b11100000) { + ++code_units; + pos += 3; + } + else if(static_cast(text[pos]) >= 0b11000000) { + ++code_units; + pos += 2; + } + else // // Invalid start of UTF-8 character + ++pos; + } + return code_units; +} + bool starts_with(const char *str, const std::string &test) noexcept { for(size_t i = 0; i < test.size(); ++i) { if(*str == '\0') diff --git a/src/utility.hpp b/src/utility.hpp index 798875f..6faa5d3 100644 --- a/src/utility.hpp +++ b/src/utility.hpp @@ -13,6 +13,8 @@ size_t utf8_character_count(const std::string &text, size_t pos = 0, size_t leng /// Returns number of bytes in the given utf16 code units in text size_t utf16_code_units_byte_count(const std::string &text, size_t code_units, size_t start_pos = 0); +/// Returns number of utf16 code units in the text +size_t utf16_code_unit_count(const std::string &text, size_t pos = 0, size_t length = std::string::npos); bool starts_with(const char *str, const std::string &test) noexcept; bool starts_with(const char *str, const char *test) noexcept; diff --git a/tests/utility_test.cpp b/tests/utility_test.cpp index 47c1553..1c26bcf 100644 --- a/tests/utility_test.cpp +++ b/tests/utility_test.cpp @@ -50,6 +50,25 @@ int main() { g_assert_cmpuint(utf16_code_units_byte_count("test🔥test", 10), ==, 12); // Fire emoji between test words g_assert_cmpuint(utf16_code_units_byte_count("test🔥test", 11), ==, 12); // Fire emoji between test words + g_assert_cmpuint(utf16_code_unit_count("", 0, 0), ==, 0); + g_assert_cmpuint(utf16_code_unit_count("", 0, 2), ==, 0); + g_assert_cmpuint(utf16_code_unit_count("", 2, 2), ==, 0); + g_assert_cmpuint(utf16_code_unit_count("test", 0, 1), ==, 1); + g_assert_cmpuint(utf16_code_unit_count("test", 0, 4), ==, 4); + g_assert_cmpuint(utf16_code_unit_count("test", 0, 10), ==, 4); + g_assert_cmpuint(utf16_code_unit_count("test", 2, 2), ==, 2); + g_assert_cmpuint(utf16_code_unit_count("æøå", 0, 0), ==, 0); + g_assert_cmpuint(utf16_code_unit_count("æøå", 0, 2), ==, 1); + g_assert_cmpuint(utf16_code_unit_count("æøå", 0, 4), ==, 2); + g_assert_cmpuint(utf16_code_unit_count("æøå", 0, 6), ==, 3); + g_assert_cmpuint(utf16_code_unit_count("æøå", 2, 6), ==, 2); + g_assert_cmpuint(utf16_code_unit_count("æøå", 4, 6), ==, 1); + g_assert_cmpuint(utf16_code_unit_count("æøå", 6, 6), ==, 0); + g_assert_cmpuint(utf16_code_unit_count("test🔥test", 0, 0), ==, 0); // Fire emoji between test words + g_assert_cmpuint(utf16_code_unit_count("test🔥test", 0, 4), ==, 4); // Fire emoji between test words + g_assert_cmpuint(utf16_code_unit_count("test🔥test", 0, 8), ==, 6); // Fire emoji between test words + g_assert_cmpuint(utf16_code_unit_count("test🔥test", 0, 12), ==, 10); // Fire emoji between test words + std::string empty; std::string test("test"); std::string testtest("testtest"); From b6e8e5d4417fd6fafde64c2a2319dc607c681570 Mon Sep 17 00:00:00 2001 From: eidheim Date: Thu, 17 Jun 2021 09:50:42 +0200 Subject: [PATCH 141/375] Language client: added completionItem/resolve request to fetch details and documentation of completion items --- src/source_language_protocol.cpp | 68 +++++++++++++++++++++++--------- src/source_language_protocol.hpp | 4 +- 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index ee1aeba..22b36d5 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -613,8 +613,8 @@ std::string Source::LanguageProtocolView::to_string(const std::vector> ¶ms, std::function &&function) { - client->write_request(this, method, "\"textDocument\":{\"uri\":\"" + uri_escaped + "\"}" + (params.empty() ? "" : "," + to_string(params)), std::move(function)); +void Source::LanguageProtocolView::write_request(const std::string &method, const std::string ¶ms, std::function &&function) { + client->write_request(this, method, "\"textDocument\":{\"uri\":\"" + uri_escaped + "\"}" + (params.empty() ? "" : "," + params), std::move(function)); } void Source::LanguageProtocolView::write_notification(const std::string &method) { @@ -702,7 +702,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { else method = "textDocument/formatting"; - write_request(method, params, [&text_edits, &result_processed](const boost::property_tree::ptree &result, bool error) { + write_request(method, to_string(params), [&text_edits, &result_processed](const boost::property_tree::ptree &result, bool error) { if(!error) { for(auto it = result.begin(); it != result.end(); ++it) { try { @@ -805,7 +805,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { else method = "textDocument/documentHighlight"; - write_request(method, {make_position(iter.get_line(), get_line_pos(iter)), {"context", "{\"includeDeclaration\":true}"}}, [this, &locations, &result_processed](const boost::property_tree::ptree &result, bool error) { + write_request(method, to_string({make_position(iter.get_line(), get_line_pos(iter)), {"context", "{\"includeDeclaration\":true}"}}), [this, &locations, &result_processed](const boost::property_tree::ptree &result, bool error) { if(!error) { try { for(auto it = result.begin(); it != result.end(); ++it) @@ -937,7 +937,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { std::vector changes_vec; std::promise result_processed; if(capabilities.rename) { - write_request("textDocument/rename", {make_position(iter.get_line(), get_line_pos(iter)), {"newName", '"' + text + '"'}}, [this, &changes_vec, &result_processed](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/rename", to_string({make_position(iter.get_line(), get_line_pos(iter)), {"newName", '"' + text + '"'}}), [this, &changes_vec, &result_processed](const boost::property_tree::ptree &result, bool error) { if(!error) { boost::filesystem::path project_path; auto build = Project::Build::create(file_path); @@ -974,7 +974,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { }); } else { - write_request("textDocument/documentHighlight", {make_position(iter.get_line(), get_line_pos(iter)), {"context", "{\"includeDeclaration\":true}"}}, [this, &changes_vec, &text, &result_processed](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/documentHighlight", to_string({make_position(iter.get_line(), get_line_pos(iter)), {"context", "{\"includeDeclaration\":true}"}}), [this, &changes_vec, &text, &result_processed](const boost::property_tree::ptree &result, bool error) { if(!error) { try { std::vector edits; @@ -1405,7 +1405,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { } bool using_named_parameters = named_parameter_symbol && !(current_parameter_position > 0 && used_named_parameters.empty()); - write_request("textDocument/signatureHelp", {make_position(line, get_line_pos(line, line_index))}, [this, &result_processed, current_parameter_position, using_named_parameters, used_named_parameters = std::move(used_named_parameters)](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/signatureHelp", to_string({make_position(line, get_line_pos(line, line_index))}), [this, &result_processed, current_parameter_position, using_named_parameters, used_named_parameters = std::move(used_named_parameters)](const boost::property_tree::ptree &result, bool error) { if(!error) { auto signatures = result.get_child("signatures", boost::property_tree::ptree()); for(auto signature_it = signatures.begin(); signature_it != signatures.end(); ++signature_it) { @@ -1428,7 +1428,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { documentation.clear(); if(!using_named_parameters || used_named_parameters.find(insert) == used_named_parameters.end()) { autocomplete->rows.emplace_back(std::move(label)); - autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), {}, std::move(documentation), std::move(kind)}); + autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), {}, std::move(documentation), std::move(kind), {}}); } } parameter_position++; @@ -1441,8 +1441,9 @@ void Source::LanguageProtocolView::setup_autocomplete() { } else { dispatcher.post([this, line, line_index, &result_processed] { - write_request("textDocument/completion", {make_position(line, get_line_pos(line, line_index))}, [this, &result_processed](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/completion", to_string({make_position(line, get_line_pos(line, line_index))}), [this, &result_processed](const boost::property_tree::ptree &result, bool error) { if(!error) { + bool is_incomplete = result.get("isIncomplete", false); boost::property_tree::ptree::const_iterator begin, end; if(auto items = result.get_child_optional("items")) { begin = items->begin(); @@ -1469,6 +1470,9 @@ void Source::LanguageProtocolView::setup_autocomplete() { documentation_kind = documentation_pt->get("kind", ""); } } + boost::property_tree::ptree ptree; + if(detail.empty() && documentation.empty() && (is_incomplete || is_js)) // Workaround for typescript-language-server (is_js) + ptree = it->second; auto insert = it->second.get("insertText", ""); if(insert.empty()) insert = it->second.get("textEdit.newText", ""); @@ -1479,7 +1483,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { if(kind >= 2 && kind <= 4 && insert.find('(') == std::string::npos) // If kind is method, function or constructor, but parentheses are missing insert += "(${1:})"; autocomplete->rows.emplace_back(std::move(label)); - autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), std::move(detail), std::move(documentation), std::move(documentation_kind)}); + autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), std::move(detail), std::move(documentation), std::move(documentation_kind), std::move(ptree)}); } } } @@ -1490,7 +1494,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { for(auto &snippet : *snippets) { if(starts_with(snippet.prefix, prefix)) { autocomplete->rows.emplace_back(snippet.prefix); - autocomplete_rows.emplace_back(AutocompleteRow{snippet.body, {}, snippet.description, {}}); + autocomplete_rows.emplace_back(AutocompleteRow{snippet.body, {}, snippet.description, {}, {}}); } } } @@ -1563,8 +1567,36 @@ void Source::LanguageProtocolView::setup_autocomplete() { autocomplete->set_tooltip_buffer = [this](unsigned int index) -> std::function { auto autocomplete = autocomplete_rows[index]; + if(autocomplete.detail.empty() && autocomplete.documentation.empty() && !autocomplete.ptree.empty()) { + try { + std::stringstream ss; + boost::property_tree::write_json(ss, autocomplete.ptree, false); + // ss.str() is enclosed in {}, and has ending newline + auto str = ss.str(); + if(str.size() <= 3) + return nullptr; + std::promise result_processed; + write_request("completionItem/resolve", str.substr(1, str.size() - 3), [&result_processed, &autocomplete](const boost::property_tree::ptree &result, bool error) { + if(!error) { + autocomplete.detail = result.get("detail", ""); + autocomplete.documentation = result.get("documentation", ""); + if(autocomplete.documentation.empty()) { + if(auto documentation = result.get_child_optional("documentation")) { + autocomplete.kind = documentation->get("kind", ""); + autocomplete.documentation = documentation->get("value", ""); + } + } + } + result_processed.set_value(); + }); + result_processed.get_future().get(); + } + catch(...) { + } + } if(autocomplete.detail.empty() && autocomplete.documentation.empty()) return nullptr; + return [this, autocomplete = std::move(autocomplete)](Tooltip &tooltip) mutable { if(language_id == "python") // Python might support markdown in the future tooltip.insert_docstring(autocomplete.documentation); @@ -1617,7 +1649,7 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vector result_processed; - write_request("textDocument/codeAction", params, [this, &result_processed, &diagnostics, last_count](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/codeAction", to_string(params), [this, &result_processed, &diagnostics, last_count](const boost::property_tree::ptree &result, bool error) { if(!error && last_count == update_diagnostics_async_count) { try { for(auto it = result.begin(); it != result.end(); ++it) { @@ -1838,7 +1870,7 @@ void Source::LanguageProtocolView::show_type_tooltips(const Gdk::Rectangle &rect static int request_count = 0; request_count++; auto current_request = request_count; - write_request("textDocument/hover", {make_position(iter.get_line(), get_line_pos(iter))}, [this, offset, current_request](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/hover", to_string({make_position(iter.get_line(), get_line_pos(iter))}), [this, offset, current_request](const boost::property_tree::ptree &result, bool error) { if(!error) { // hover result structure vary significantly from the different language servers struct Content { @@ -1988,7 +2020,7 @@ void Source::LanguageProtocolView::apply_similar_symbol_tag() { static int request_count = 0; request_count++; auto current_request = request_count; - write_request("textDocument/documentHighlight", {make_position(iter.get_line(), get_line_pos(iter)), {"context", "{\"includeDeclaration\":true}"}}, [this, current_request](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/documentHighlight", to_string({make_position(iter.get_line(), get_line_pos(iter)), {"context", "{\"includeDeclaration\":true}"}}), [this, current_request](const boost::property_tree::ptree &result, bool error) { if(!error) { std::vector ranges; for(auto it = result.begin(); it != result.end(); ++it) { @@ -2017,7 +2049,7 @@ void Source::LanguageProtocolView::apply_clickable_tag(const Gtk::TextIter &iter static int request_count = 0; request_count++; auto current_request = request_count; - write_request("textDocument/definition", {make_position(iter.get_line(), get_line_pos(iter))}, [this, current_request, line = iter.get_line(), line_offset = iter.get_line_offset()](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/definition", to_string({make_position(iter.get_line(), get_line_pos(iter))}), [this, current_request, line = iter.get_line(), line_offset = iter.get_line_offset()](const boost::property_tree::ptree &result, bool error) { if(!error && !result.empty()) { dispatcher.post([this, current_request, line, line_offset] { if(current_request != request_count || !clickable_tag_applied) @@ -2033,7 +2065,7 @@ void Source::LanguageProtocolView::apply_clickable_tag(const Gtk::TextIter &iter Source::Offset Source::LanguageProtocolView::get_declaration(const Gtk::TextIter &iter) { auto offset = std::make_shared(); std::promise result_processed; - write_request("textDocument/definition", {make_position(iter.get_line(), get_line_pos(iter))}, [offset, &result_processed](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/definition", to_string({make_position(iter.get_line(), get_line_pos(iter))}), [offset, &result_processed](const boost::property_tree::ptree &result, bool error) { if(!error) { for(auto it = result.begin(); it != result.end(); ++it) { try { @@ -2056,7 +2088,7 @@ Source::Offset Source::LanguageProtocolView::get_declaration(const Gtk::TextIter Source::Offset Source::LanguageProtocolView::get_type_declaration(const Gtk::TextIter &iter) { auto offset = std::make_shared(); std::promise result_processed; - write_request("textDocument/typeDefinition", {make_position(iter.get_line(), get_line_pos(iter))}, [offset, &result_processed](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/typeDefinition", to_string({make_position(iter.get_line(), get_line_pos(iter))}), [offset, &result_processed](const boost::property_tree::ptree &result, bool error) { if(!error) { for(auto it = result.begin(); it != result.end(); ++it) { try { @@ -2079,7 +2111,7 @@ Source::Offset Source::LanguageProtocolView::get_type_declaration(const Gtk::Tex std::vector Source::LanguageProtocolView::get_implementations(const Gtk::TextIter &iter) { auto offsets = std::make_shared>(); std::promise result_processed; - write_request("textDocument/implementation", {make_position(iter.get_line(), get_line_pos(iter))}, [offsets, &result_processed](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/implementation", to_string({make_position(iter.get_line(), get_line_pos(iter))}), [offsets, &result_processed](const boost::property_tree::ptree &result, bool error) { if(!error) { for(auto it = result.begin(); it != result.end(); ++it) { try { diff --git a/src/source_language_protocol.hpp b/src/source_language_protocol.hpp index dd6e22e..7088c3b 100644 --- a/src/source_language_protocol.hpp +++ b/src/source_language_protocol.hpp @@ -187,7 +187,7 @@ namespace Source { std::string to_string(const std::pair ¶m); std::string to_string(const std::vector> ¶ms); /// Helper method for calling client->write_request - void write_request(const std::string &method, const std::vector> ¶ms, std::function &&function); + void write_request(const std::string &method, const std::string ¶ms, std::function &&function); /// Helper method for calling client->write_notification void write_notification(const std::string &method); /// Helper method for calling client->write_notification @@ -243,6 +243,8 @@ namespace Source { std::string detail; std::string documentation; std::string kind; + /// CompletionItem for completionItem/resolve + boost::property_tree::ptree ptree; }; std::vector autocomplete_rows; From a95cb94b79c235f1b82bab647f5410fe0acc535b Mon Sep 17 00:00:00 2001 From: eidheim Date: Thu, 17 Jun 2021 17:53:27 +0200 Subject: [PATCH 142/375] Language client: move cursor forward if no arguments in completed function and if cursor is still inside () --- src/source_language_protocol.cpp | 13 +++++++++++++ src/source_language_protocol.hpp | 2 ++ 2 files changed, 15 insertions(+) diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 22b36d5..0a1b192 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -1197,6 +1197,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { // Activate argument completions get_buffer()->signal_changed().connect( [this] { + autocompete_possibly_no_arguments = false; if(!interactive_completion) return; if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) @@ -1434,6 +1435,17 @@ void Source::LanguageProtocolView::setup_autocomplete() { parameter_position++; } } + if(autocomplete_rows.empty()) { + dispatcher.post([this] { + // Move cursor forward if no arguments in completed function and if cursor is still inside () + if(autocompete_possibly_no_arguments) { + auto iter = get_buffer()->get_insert()->get_iter(); + auto prev = iter; + if(prev.backward_char() && *prev == '(' && *iter == ')' && iter.forward_char()) + get_buffer()->place_cursor(iter); + } + }); + } } result_processed.set_value(); }); @@ -1558,6 +1570,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { auto iter = get_buffer()->get_insert()->get_iter(); if(*iter == ')' && iter.backward_char() && *iter == '(') { // If no arguments, try signatureHelp last_keyval = '('; + autocompete_possibly_no_arguments = true; autocomplete->run(); } } diff --git a/src/source_language_protocol.hpp b/src/source_language_protocol.hpp index 7088c3b..cc3514f 100644 --- a/src/source_language_protocol.hpp +++ b/src/source_language_protocol.hpp @@ -251,6 +251,8 @@ namespace Source { std::atomic autocomplete_enable_snippets = {false}; bool autocomplete_show_arguments = false; sigc::connection autocomplete_delayed_show_arguments_connection; + /// Used to move cursor forward if no arguments in completed function and if cursor is still inside () + bool autocompete_possibly_no_arguments = false; /// If language supports named parameters, returns the symbol separating the named parameter and value, /// for instance '=' for Python From 4e121ffd00336d0b9262b0321b9eb9ec4d372323 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 18 Jun 2021 14:33:45 +0200 Subject: [PATCH 143/375] Improved C/C++ include fixits --- src/source_clang.cpp | 60 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/src/source_clang.cpp b/src/source_clang.cpp index 3f4e32a..c77596d 100644 --- a/src/source_clang.cpp +++ b/src/source_clang.cpp @@ -370,9 +370,11 @@ void Source::ClangViewParse::update_diagnostics() { } return false; }; - auto add_include_fixit = [this, &diagnostic, &get_new_include_offsets](bool has_std, bool has_using_std, const std::string &token) { - auto headers = Documentation::CppReference::get_headers(has_std || has_using_std ? "std::" + token : token); - if(headers.empty() && !has_std && has_using_std) + auto add_include_fixit = [this, &diagnostic, &get_new_include_offsets](std::string ns, bool has_using_std, const std::string &token) { + auto headers = Documentation::CppReference::get_headers(!ns.empty() ? ns + "::" + token : (has_using_std ? "std::" + token : token)); + if(headers.empty() && !ns.empty() && ns != "std" && !starts_with(ns, "std::") && has_using_std) + headers = Documentation::CppReference::get_headers("std::" + ns + "::" + token); + if(headers.empty() && ns.empty() && has_using_std) headers = Documentation::CppReference::get_headers(token); if(!headers.empty()) { std::string *c_header = nullptr; @@ -403,20 +405,37 @@ void Source::ClangViewParse::update_diagnostics() { end = diagnostic.spelling.find('\'', start); if(end != std::string::npos) { auto type = diagnostic.spelling.substr(start, end - start); - bool has_std = false; + std::string ns; if(is_cpp) { if(starts_with(type, "std::")) { - has_std = true; + ns = "std"; type.erase(0, 5); } if(starts_with(type, "__1::")) type.erase(0, 5); } - add_include_fixit(has_std, is_cpp && has_using_namespace_std(c), type); + add_include_fixit(ns, is_cpp && has_using_namespace_std(c), type); break; } } } + if(diagnostic.severity >= clangmm::Diagnostic::Severity::Error && + starts_with(diagnostic.spelling, "variable has incomplete type '")) { + size_t start = 30; + auto end = diagnostic.spelling.find('\'', start); + if(end != std::string::npos) { + auto type = diagnostic.spelling.substr(start, end - start); + std::string ns; + if(is_cpp) { + if(starts_with(type, "std::")) { + ns = "std"; + type.erase(0, 5); + } + } + add_include_fixit(ns, is_cpp && has_using_namespace_std(c), type); + break; + } + } if(is_token_char(*start) && token.get_kind() == clangmm::Token::Kind::Identifier) { if(diagnostic.severity >= clangmm::Diagnostic::Severity::Error && (starts_with(diagnostic.spelling, "unknown type name") || @@ -427,24 +446,41 @@ void Source::ClangViewParse::update_diagnostics() { starts_with(diagnostic.spelling, "implicit instantiation of undefined template") || starts_with(diagnostic.spelling, "no viable constructor or deduction guide for deduction of template arguments of"))) { auto token_string = get_token(start); - bool has_std = false; + std::string ns; if(is_cpp) { - if(token_string == "std" && c + 2 < clang_tokens->size() && (*clang_tokens)[c + 2].get_kind() == clangmm::Token::Kind::Identifier) { + if(token_string == "std" && c + 2 < clang_tokens->size() && + (*clang_tokens)[c + 1].get_kind() == clangmm::Token::Punctuation && + (*clang_tokens)[c + 2].get_kind() == clangmm::Token::Identifier && + (*clang_tokens)[c + 1].get_spelling() == "::") { token_string = (*clang_tokens)[c + 2].get_spelling(); - has_std = true; + ns = "std"; } else if(c >= 2 && (*clang_tokens)[c - 1].get_kind() == clangmm::Token::Punctuation && (*clang_tokens)[c - 2].get_kind() == clangmm::Token::Identifier && (*clang_tokens)[c - 1].get_spelling() == "::" && (*clang_tokens)[c - 2].get_spelling() == "std") - has_std = true; + ns = "std"; + else if(c >= 4 && + (*clang_tokens)[c - 1].get_kind() == clangmm::Token::Punctuation && + (*clang_tokens)[c - 2].get_kind() == clangmm::Token::Identifier && + (*clang_tokens)[c - 3].get_kind() == clangmm::Token::Punctuation && + (*clang_tokens)[c - 4].get_kind() == clangmm::Token::Identifier && + (*clang_tokens)[c - 1].get_spelling() == "::" && + (*clang_tokens)[c - 3].get_spelling() == "::" && + (*clang_tokens)[c - 4].get_spelling() == "std") + ns = "std::" + (*clang_tokens)[c - 2].get_spelling(); + else if(c >= 2 && + (*clang_tokens)[c - 1].get_kind() == clangmm::Token::Punctuation && + (*clang_tokens)[c - 2].get_kind() == clangmm::Token::Identifier && + (*clang_tokens)[c - 1].get_spelling() == "::") + ns = (*clang_tokens)[c - 2].get_spelling(); } - add_include_fixit(has_std, is_cpp && has_using_namespace_std(c), token_string); + add_include_fixit(ns, is_cpp && has_using_namespace_std(c), token_string); } else if(diagnostic.severity >= clangmm::Diagnostic::Severity::Warning && is_c && starts_with(diagnostic.spelling, "implicitly declaring library function")) - add_include_fixit(false, false, get_token(start)); + add_include_fixit("", false, get_token(start)); } break; } From c89238b9e55267c5fbb6c571c9b4d3758f2ef692 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 18 Jun 2021 15:42:13 +0200 Subject: [PATCH 144/375] Improvements to indentaion detection --- src/source_base.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/source_base.cpp b/src/source_base.cpp index b905780..948513d 100644 --- a/src/source_base.cpp +++ b/src/source_base.cpp @@ -591,7 +591,7 @@ std::pair Source::BaseView::find_tab_char_and_size() { single_quoted = false; double_quoted = false; tab_count = 0; - if(last_char == '{') + if(last_char == '{' || (!is_c && !is_cpp && last_char == '[')) bracket_last_line = true; else bracket_last_line = false; @@ -648,14 +648,13 @@ std::pair Source::BaseView::find_tab_char_and_size() { else if(*iter == '/' && *next_iter == '*') comment = true; else if(*iter == '*' && *next_iter == '/') { - iter.forward_char(); - iter.forward_char(); + iter.forward_chars(2); 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) + if(!single_quoted && !double_quoted && !comment && !line_comment && (*iter == '}' || (!is_c && !is_cpp && last_char == ']')) && tab_count != -1 && last_tab_diff != -1) last_tab_count -= last_tab_diff; if(*iter != ' ' && *iter != '\t') tab_count = -1; From e6069f00bc2ddacebd5219ce01704fe6e1efd487 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 18 Jun 2021 16:36:45 +0200 Subject: [PATCH 145/375] Minor improvement to extend_selection(), and correction of cursor movement on functions with no arguments --- src/source.cpp | 2 +- src/source_language_protocol.cpp | 3 ++- src/source_language_protocol.hpp | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/source.cpp b/src/source.cpp index 9935ed7..6586568 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -1553,7 +1553,7 @@ void Source::View::extend_selection() { // Attempt to select a sentence, for instance: int a = 2; if(!is_bracket_language) { // If for instance cmake, meson or python if(!select_matching_brackets) { - bool select_end_block = is_language({"cmake", "meson", "julia"}); + bool select_end_block = is_language({"cmake", "meson", "julia", "xml"}); auto get_tabs = [this](Gtk::TextIter iter) -> boost::optional { iter = get_buffer()->get_iter_at_line(iter.get_line()); diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 0a1b192..19ae65d 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -1570,7 +1570,8 @@ void Source::LanguageProtocolView::setup_autocomplete() { auto iter = get_buffer()->get_insert()->get_iter(); if(*iter == ')' && iter.backward_char() && *iter == '(') { // If no arguments, try signatureHelp last_keyval = '('; - autocompete_possibly_no_arguments = true; + if(is_js) // Workaround for typescript-language-server + autocompete_possibly_no_arguments = true; autocomplete->run(); } } diff --git a/src/source_language_protocol.hpp b/src/source_language_protocol.hpp index cc3514f..0f33f37 100644 --- a/src/source_language_protocol.hpp +++ b/src/source_language_protocol.hpp @@ -251,6 +251,7 @@ namespace Source { std::atomic autocomplete_enable_snippets = {false}; bool autocomplete_show_arguments = false; sigc::connection autocomplete_delayed_show_arguments_connection; + /// Workaround for typescript-language-server. /// Used to move cursor forward if no arguments in completed function and if cursor is still inside () bool autocompete_possibly_no_arguments = false; From 149e82a2799daea262bc02e2b1e67dc4b4ae3228 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 18 Jun 2021 18:24:50 +0200 Subject: [PATCH 146/375] Additional language client tests --- tests/CMakeLists.txt | 1 + tests/language_protocol_client_test.cpp | 50 +- tests/language_protocol_server_test.cpp | 622 ++++++++++++++------- tests/language_protocol_test_files/main.rs | 3 +- 4 files changed, 469 insertions(+), 207 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c07a884..59a3867 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -90,6 +90,7 @@ if(BUILD_TESTING) add_test(language_protocol_client_test language_protocol_client_test) add_executable(language_protocol_server_test language_protocol_server_test.cpp) + target_link_libraries(language_protocol_server_test Boost::filesystem) endif() if(BUILD_FUZZING) diff --git a/tests/language_protocol_client_test.cpp b/tests/language_protocol_client_test.cpp index a5bb7dc..de0fcc1 100644 --- a/tests/language_protocol_client_test.cpp +++ b/tests/language_protocol_client_test.cpp @@ -1,4 +1,3 @@ -#include "config.hpp" #include "source_language_protocol.hpp" #include @@ -31,16 +30,61 @@ int main() { view->get_buffer()->insert_at_cursor(" "); g_assert(view->get_buffer()->get_text() == R"( fn main() { - println!("Hello, world!"); + let a = 2; + println!("{}", a); } )"); view->format_style(false); g_assert(view->get_buffer()->get_text() == R"(fn main() { - println!("Hello, world!"); + let a = 2; + println!("{}", a); } )"); + g_assert(view->get_declaration_location); + auto location = view->get_declaration_location(); + g_assert(location); + g_assert(location.file_path == "main.rs"); + g_assert_cmpuint(location.line, ==, 0); + g_assert_cmpuint(location.index, ==, 3); + + g_assert(view->get_type_declaration_location); + location = view->get_type_declaration_location(); + g_assert(location); + g_assert(location.file_path == "main.rs"); + g_assert_cmpuint(location.line, ==, 0); + g_assert_cmpuint(location.index, ==, 4); + + g_assert(view->get_implementation_locations); + auto locations = view->get_implementation_locations(); + g_assert(locations.size() == 2); + g_assert(locations[0].file_path == "main.rs"); + g_assert(locations[1].file_path == "main.rs"); + g_assert_cmpuint(locations[0].line, ==, 0); + g_assert_cmpuint(locations[0].index, ==, 0); + g_assert_cmpuint(locations[1].line, ==, 1); + g_assert_cmpuint(locations[1].index, ==, 0); + + g_assert(view->get_usages); + auto usages = view->get_usages(); + g_assert(usages.size() == 2); + g_assert(usages[0].first.file_path == view->file_path); + g_assert(usages[1].first.file_path == view->file_path); + g_assert_cmpuint(usages[0].first.line, ==, 1); + g_assert_cmpuint(usages[0].first.index, ==, 8); + g_assert_cmpuint(usages[1].first.line, ==, 2); + g_assert_cmpuint(usages[1].first.index, ==, 19); + g_assert(usages[0].second == "let a = 2;"); + g_assert(usages[1].second == "println!("{}", a);"); + + g_assert(view->get_methods); + auto methods = view->get_methods(); + g_assert(methods.size() == 1); + g_assert_cmpuint(methods[0].first.line, ==, 0); + g_assert_cmpuint(methods[0].first.index, ==, 0); + g_assert(methods[0].second == "1: main"); + std::atomic exit_status(-1); view->client->on_exit_status = [&exit_status](int exit_status_) { exit_status = exit_status_; diff --git a/tests/language_protocol_server_test.cpp b/tests/language_protocol_server_test.cpp index ac3aeba..7a73795 100644 --- a/tests/language_protocol_server_test.cpp +++ b/tests/language_protocol_server_test.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -12,6 +13,9 @@ int main() { _setmode(_fileno(stdout), _O_BINARY); #endif + auto tests_path = boost::filesystem::canonical(JUCI_TESTS_PATH); + auto file_path = boost::filesystem::canonical(tests_path / "language_protocol_test_files" / "main.rs"); + std::string line; try { // Read initialize and respond @@ -29,185 +33,172 @@ int main() { return 1; std::string result = R"({ - "jsonrpc": "2.0", - "id": "0", - "result": { - "capabilities": { - "textDocumentSync": { - "openClose": "true", - "change": "2", - "save": "" - }, - "selectionRangeProvider": "true", - "hoverProvider": "true", - "completionProvider": { - "triggerCharacters": [ - ":", - ".", - "'" - ] - }, - "signatureHelpProvider": { - "triggerCharacters": [ - "(", - "," - ] - }, - "definitionProvider": "true", - "typeDefinitionProvider": "true", - "implementationProvider": "true", - "referencesProvider": "true", - "documentHighlightProvider": "true", - "documentSymbolProvider": "true", - "workspaceSymbolProvider": "true", - "codeActionProvider": { - "codeActionKinds": [ - "", - "quickfix", - "refactor", - "refactor.extract", - "refactor.inline", - "refactor.rewrite" - ], - "resolveProvider": "true" - }, - "codeLensProvider": { - "resolveProvider": "true" - }, - "documentFormattingProvider": "true", - "documentOnTypeFormattingProvider": { - "firstTriggerCharacter": "=", - "moreTriggerCharacter": [ - ".", - ">", - "{" - ] - }, - "renameProvider": { - "prepareProvider": "true" - }, - "foldingRangeProvider": "true", - "workspace": { - "fileOperations": { - "willRename": { - "filters": [ - { - "scheme": "file", - "pattern": { - "glob": "**\/*.rs", - "matches": "file" - } - }, - { - "scheme": "file", - "pattern": { - "glob": "**", - "matches": "folder" - } - } - ] - } + "jsonrpc": "2.0", + "id": "0", + "result": { + "capabilities": { + "textDocumentSync": { + "openClose": "true", + "change": "2", + "save": "" + }, + "selectionRangeProvider": "true", + "hoverProvider": "true", + "completionProvider": { + "triggerCharacters": [":", ".", "'"] + }, + "signatureHelpProvider": { + "triggerCharacters": ["(", ","] + }, + "definitionProvider": "true", + "typeDefinitionProvider": "true", + "implementationProvider": "true", + "referencesProvider": "true", + "documentHighlightProvider": "true", + "documentSymbolProvider": "true", + "workspaceSymbolProvider": "true", + "codeActionProvider": { + "codeActionKinds": [ + "", + "quickfix", + "refactor", + "refactor.extract", + "refactor.inline", + "refactor.rewrite" + ], + "resolveProvider": "true" + }, + "codeLensProvider": { + "resolveProvider": "true" + }, + "documentFormattingProvider": "true", + "documentOnTypeFormattingProvider": { + "firstTriggerCharacter": "=", + "moreTriggerCharacter": [".", ">", "{"] + }, + "renameProvider": { + "prepareProvider": "true" + }, + "foldingRangeProvider": "true", + "workspace": { + "fileOperations": { + "willRename": { + "filters": [ + { + "scheme": "file", + "pattern": { + "glob": "**/*.rs", + "matches": "file" } - }, - "callHierarchyProvider": "true", - "semanticTokensProvider": { - "legend": { - "tokenTypes": [ - "comment", - "keyword", - "string", - "number", - "regexp", - "operator", - "namespace", - "type", - "struct", - "class", - "interface", - "enum", - "enumMember", - "typeParameter", - "function", - "method", - "property", - "macro", - "variable", - "parameter", - "angle", - "arithmetic", - "attribute", - "bitwise", - "boolean", - "brace", - "bracket", - "builtinType", - "characterLiteral", - "colon", - "comma", - "comparison", - "constParameter", - "dot", - "escapeSequence", - "formatSpecifier", - "generic", - "label", - "lifetime", - "logical", - "operator", - "parenthesis", - "punctuation", - "selfKeyword", - "semicolon", - "typeAlias", - "union", - "unresolvedReference" - ], - "tokenModifiers": [ - "documentation", - "declaration", - "definition", - "static", - "abstract", - "deprecated", - "readonly", - "constant", - "controlFlow", - "injected", - "mutable", - "consuming", - "async", - "unsafe", - "attribute", - "trait", - "callable", - "intraDocLink" - ] - }, - "range": "true", - "full": { - "delta": "true" + }, + { + "scheme": "file", + "pattern": { + "glob": "**", + "matches": "folder" } - }, - "experimental": { - "joinLines": "true", - "ssr": "true", - "onEnter": "true", - "parentModule": "true", - "runnables": { - "kinds": [ - "cargo" - ] - }, - "workspaceSymbolScopeKindFiltering": "true" - } + } + ] + } + } + }, + "callHierarchyProvider": "true", + "semanticTokensProvider": { + "legend": { + "tokenTypes": [ + "comment", + "keyword", + "string", + "number", + "regexp", + "operator", + "namespace", + "type", + "struct", + "class", + "interface", + "enum", + "enumMember", + "typeParameter", + "function", + "method", + "property", + "macro", + "variable", + "parameter", + "angle", + "arithmetic", + "attribute", + "bitwise", + "boolean", + "brace", + "bracket", + "builtinType", + "characterLiteral", + "colon", + "comma", + "comparison", + "constParameter", + "dot", + "escapeSequence", + "formatSpecifier", + "generic", + "label", + "lifetime", + "logical", + "operator", + "parenthesis", + "punctuation", + "selfKeyword", + "semicolon", + "typeAlias", + "union", + "unresolvedReference" + ], + "tokenModifiers": [ + "documentation", + "declaration", + "definition", + "static", + "abstract", + "deprecated", + "readonly", + "constant", + "controlFlow", + "injected", + "mutable", + "consuming", + "async", + "unsafe", + "attribute", + "trait", + "callable", + "intraDocLink" + ] }, - "serverInfo": { - "name": "rust-analyzer", - "version": "3022a2c3a 2021-05-25 dev" + "range": "true", + "full": { + "delta": "true" + } + }, + "experimental": { + "joinLines": "true", + "ssr": "true", + "onEnter": "true", + "parentModule": "true", + "runnables": { + "kinds": ["cargo"] }, - "offsetEncoding": "utf-8" + "workspaceSymbolScopeKindFiltering": "true" + } + }, + "serverInfo": { + "name": "rust-analyzer", + "version": "3022a2c3a 2021-05-25 dev" } -})"; + } +} +)"; std::cout << "Content-Length: " << result.size() << "\r\n\r\n" << result; } @@ -224,7 +215,7 @@ int main() { boost::property_tree::ptree pt; boost::property_tree::json_parser::read_json(ss, pt); if(pt.get("method") != "initialized") - return 2; + return 1; } // Read textDocument/didOpen @@ -239,7 +230,7 @@ int main() { boost::property_tree::ptree pt; boost::property_tree::json_parser::read_json(ss, pt); if(pt.get("method") != "textDocument/didOpen") - return 3; + return 1; } // Read textDocument/didChange @@ -254,7 +245,7 @@ int main() { boost::property_tree::ptree pt; boost::property_tree::json_parser::read_json(ss, pt); if(pt.get("method") != "textDocument/didChange") - return 4; + return 1; } // Read and write textDocument/formatting @@ -269,27 +260,28 @@ int main() { boost::property_tree::ptree pt; boost::property_tree::json_parser::read_json(ss, pt); if(pt.get("method") != "textDocument/formatting") - return 5; + return 1; std::string result = R"({ - "jsonrpc": "2.0", - "id": "1", - "result": [ - { - "range": { - "start": { - "line": "0", - "character": "0" - }, - "end": { - "line": "0", - "character": "1" - } - }, - "newText": "" + "jsonrpc": "2.0", + "id": "1", + "result": [ + { + "range": { + "start": { + "line": "0", + "character": "0" + }, + "end": { + "line": "0", + "character": "1" } - ] -})"; + }, + "newText": "" + } + ] +} +)"; std::cout << "Content-Length: " << result.size() << "\r\n\r\n" << result; } @@ -306,7 +298,231 @@ int main() { boost::property_tree::ptree pt; boost::property_tree::json_parser::read_json(ss, pt); if(pt.get("method") != "textDocument/didChange") - return 6; + return 1; + } + + // Read and write textDocument/definition + { + std::getline(std::cin, line); + auto size = std::atoi(line.substr(16).c_str()); + std::getline(std::cin, line); + std::string buffer; + buffer.resize(size); + std::cin.read(&buffer[0], size); + std::stringstream ss(buffer); + boost::property_tree::ptree pt; + boost::property_tree::json_parser::read_json(ss, pt); + if(pt.get("method") != "textDocument/definition") + return 1; + + std::string result = R"({ + "jsonrpc": "2.0", + "id": "2", + "result": [ + { + "uri": "file://main.rs", + "range": { + "start": { + "line": "0", + "character": "3" + }, + "end": { + "line": "0", + "character": "7" + } + } + } + ] +} +)"; + std::cout << "Content-Length: " << result.size() << "\r\n\r\n" + << result; + } + + // Read and write textDocument/typeDefinition + { + std::getline(std::cin, line); + auto size = std::atoi(line.substr(16).c_str()); + std::getline(std::cin, line); + std::string buffer; + buffer.resize(size); + std::cin.read(&buffer[0], size); + std::stringstream ss(buffer); + boost::property_tree::ptree pt; + boost::property_tree::json_parser::read_json(ss, pt); + if(pt.get("method") != "textDocument/typeDefinition") + return 1; + + std::string result = R"({ + "jsonrpc": "2.0", + "id": "3", + "result": [ + { + "uri": "file://main.rs", + "range": { + "start": { + "line": "0", + "character": "4" + }, + "end": { + "line": "0", + "character": "7" + } + } + } + ] +} +)"; + std::cout << "Content-Length: " << result.size() << "\r\n\r\n" + << result; + } + + // Read and write textDocument/implementation + { + std::getline(std::cin, line); + auto size = std::atoi(line.substr(16).c_str()); + std::getline(std::cin, line); + std::string buffer; + buffer.resize(size); + std::cin.read(&buffer[0], size); + std::stringstream ss(buffer); + boost::property_tree::ptree pt; + boost::property_tree::json_parser::read_json(ss, pt); + if(pt.get("method") != "textDocument/implementation") + return 1; + + std::string result = R"({ + "jsonrpc": "2.0", + "id": "4", + "result": [ + { + "uri": "file://main.rs", + "range": { + "start": { + "line": "0", + "character": "0" + }, + "end": { + "line": "0", + "character": "1" + } + } + }, + { + "uri": "file://main.rs", + "range": { + "start": { + "line": "1", + "character": "0" + }, + "end": { + "line": "1", + "character": "1" + } + } + } + ] +} +)"; + std::cout << "Content-Length: " << result.size() << "\r\n\r\n" + << result; + } + + // Read and write textDocument/references + { + std::getline(std::cin, line); + auto size = std::atoi(line.substr(16).c_str()); + std::getline(std::cin, line); + std::string buffer; + buffer.resize(size); + std::cin.read(&buffer[0], size); + std::stringstream ss(buffer); + boost::property_tree::ptree pt; + boost::property_tree::json_parser::read_json(ss, pt); + if(pt.get("method") != "textDocument/references") + return 1; + + std::string result = R"({ + "jsonrpc": "2.0", + "id": "5", + "result": [ + { + "uri": "file://)" + file_path.string() + + R"(", + "range": { + "start": { + "line": "2", + "character": "19" + }, + "end": { + "line": "2", + "character": "20" + } + } + }, + { + "uri": "file://)" + file_path.string() + + R"(", + "range": { + "start": { + "line": "1", + "character": "8" + }, + "end": { + "line": "1", + "character": "9" + } + } + } + ] +} +)"; + std::cout << "Content-Length: " << result.size() << "\r\n\r\n" + << result; + } + + // Read and write textDocument/references + { + std::getline(std::cin, line); + auto size = std::atoi(line.substr(16).c_str()); + std::getline(std::cin, line); + std::string buffer; + buffer.resize(size); + std::cin.read(&buffer[0], size); + std::stringstream ss(buffer); + boost::property_tree::ptree pt; + boost::property_tree::json_parser::read_json(ss, pt); + if(pt.get("method") != "textDocument/documentSymbol") + return 1; + + std::string result = R"({ + "jsonrpc": "2.0", + "id": "6", + "result": [ + { + "name": "main", + "kind": "12", + "tags": "", + "deprecated": "false", + "location": { + "uri": "file://main.rs", + "range": { + "start": { + "line": "0", + "character": "0" + }, + "end": { + "line": "3", + "character": "1" + } + } + } + } + ] +} +)"; + std::cout << "Content-Length: " << result.size() << "\r\n\r\n" + << result; } // Read textDocument/didClose @@ -321,7 +537,7 @@ int main() { boost::property_tree::ptree pt; boost::property_tree::json_parser::read_json(ss, pt); if(pt.get("method") != "textDocument/didClose") - return 7; + return 1; } // Read shutdown and respond @@ -336,12 +552,12 @@ int main() { boost::property_tree::ptree pt; boost::property_tree::json_parser::read_json(ss, pt); if(pt.get("method") != "shutdown") - return 8; + return 1; std::string result = R"({ - "jsonrpc": "2.0", - "id": "2", - "result": {} + "jsonrpc": "2.0", + "id": "7", + "result": {} })"; std::cout << "Content-Length: " << result.size() << "\r\n\r\n" << result; @@ -359,11 +575,11 @@ int main() { boost::property_tree::ptree pt; boost::property_tree::json_parser::read_json(ss, pt); if(pt.get("method") != "exit") - return 9; + return 1; } } catch(const std::exception &e) { std::cerr << e.what() << std::endl; - return 100; + return 2; } } diff --git a/tests/language_protocol_test_files/main.rs b/tests/language_protocol_test_files/main.rs index e7a11a9..5c62ef2 100644 --- a/tests/language_protocol_test_files/main.rs +++ b/tests/language_protocol_test_files/main.rs @@ -1,3 +1,4 @@ fn main() { - println!("Hello, world!"); + let a = 2; + println!("{}", a); } From 66abb09262785a36909e9365894739ab181b8970 Mon Sep 17 00:00:00 2001 From: eidheim Date: Sat, 19 Jun 2021 12:25:02 +0200 Subject: [PATCH 147/375] Language client: added support for refactor code actions at cursor position using Apply Fix-its menu item. Availability of refactorings is not shown to keep visual noise to a minimum. --- src/source_language_protocol.cpp | 462 ++++++++++++++++++++++--------- src/source_language_protocol.hpp | 16 +- 2 files changed, 344 insertions(+), 134 deletions(-) diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 19ae65d..41b4512 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -52,7 +52,41 @@ LanguageProtocol::TextEdit::TextEdit(const boost::property_tree::ptree &pt, std: LanguageProtocol::TextDocumentEdit::TextDocumentEdit(const boost::property_tree::ptree &pt) : file(filesystem::get_path_from_uri(pt.get("textDocument.uri", "")).string()) { auto edits_it = pt.get_child("edits", boost::property_tree::ptree()); for(auto it = edits_it.begin(); it != edits_it.end(); ++it) - edits.emplace_back(it->second); + text_edits.emplace_back(it->second); +} + +LanguageProtocol::TextDocumentEdit::TextDocumentEdit(std::string file, std::vector text_edits) : file(std::move(file)), text_edits(std::move(text_edits)) {} + +LanguageProtocol::WorkspaceEdit::WorkspaceEdit(const boost::property_tree::ptree &pt, boost::filesystem::path file_path) { + 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 { + if(auto changes_pt = pt.get_child_optional("changes")) { + for(auto file_it = changes_pt->begin(); file_it != changes_pt->end(); ++file_it) { + auto file = filesystem::get_path_from_uri(file_it->first); + if(filesystem::file_in_path(file, project_path)) { + std::vector text_edits; + for(auto edit_it = file_it->second.begin(); edit_it != file_it->second.end(); ++edit_it) + text_edits.emplace_back(edit_it->second); + document_edits.emplace_back(file.string(), std::move(text_edits)); + } + } + } + else if(auto changes_pt = pt.get_child_optional("documentChanges")) { + for(auto change_it = changes_pt->begin(); change_it != changes_pt->end(); ++change_it) { + LanguageProtocol::TextDocumentEdit document_edit(change_it->second); + if(filesystem::file_in_path(document_edit.file, project_path)) + document_edits.emplace_back(std::move(document_edit.file), std::move(document_edit.text_edits)); + } + } + } + catch(...) { + document_edits.clear(); + } } std::string LanguageProtocol::escape_text(std::string text) { @@ -114,6 +148,8 @@ std::shared_ptr LanguageProtocol::Client::get(const bo auto instance = it->second.lock(); if(!instance) it->second = instance = std::shared_ptr(new Client(root_path, language_id, language_server), [](Client *client_ptr) { + client_ptr->dispatcher = nullptr; // Dispatcher must be destroyed in main thread + std::thread delete_thread([client_ptr] { // Delete client in the background delete client_ptr; }); @@ -218,9 +254,21 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang "codeAction": { "dynamicRegistration": false, "codeActionLiteralSupport": { - "codeActionKind": { "valueSet": ["quickfix"] } + "codeActionKind": { + "valueSet": [ + "", + "quickfix", + "refactor", + "refactor.extract", + "refactor.inline", + "refactor.rewrite", + "source", + "source.organizeImports" + ] + } } - } + }, + "executeCommand": { "dynamicRegistration": false } } }, "initializationOptions": { @@ -254,6 +302,7 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang capabilities.code_action = capabilities_pt->get("codeActionProvider", false); if(!capabilities.code_action) capabilities.code_action = static_cast(capabilities_pt->get_child_optional("codeActionProvider.codeActionKinds")); + capabilities.execute_command = static_cast(capabilities_pt->get_child_optional("executeCommandProvider")); capabilities.type_coverage = capabilities_pt->get("typeCoverageProvider", false); } @@ -480,8 +529,83 @@ void LanguageProtocol::Client::handle_server_notification(const std::string &met } void LanguageProtocol::Client::handle_server_request(size_t id, const std::string &method, const boost::property_tree::ptree ¶ms) { - // TODO: respond to requests from server here - // write_response(*id, ""); + if(method == "workspace/applyEdit") { + std::promise result_processed; + dispatcher->post([&result_processed, params] { + ScopeGuard guard({[&result_processed] { + result_processed.set_value(); + }}); + if(auto current_view = dynamic_cast(Notebook::get().get_current_view())) { + LanguageProtocol::WorkspaceEdit workspace_edit; + try { + workspace_edit = LanguageProtocol::WorkspaceEdit(params.get_child("edit"), current_view->file_path); + } + catch(...) { + return; + } + + struct DocumentEditAndView { + LanguageProtocol::TextDocumentEdit *document_edit; + Source::View *view; + }; + std::vector document_edits_and_views; + + for(auto &document_edit : workspace_edit.document_edits) { + Source::View *view = nullptr; + for(auto it = Source::View::views.begin(); it != Source::View::views.end(); ++it) { + if((*it)->file_path == document_edit.file) { + view = *it; + break; + } + } + if(!view) { + if(!Notebook::get().open(document_edit.file)) + return; + view = Notebook::get().get_current_view(); + document_edits_and_views.emplace_back(DocumentEditAndView{&document_edit, view}); + } + else + document_edits_and_views.emplace_back(DocumentEditAndView{&document_edit, view}); + } + + if(current_view) + Notebook::get().open(current_view); + + for(auto &document_edit_and_view : document_edits_and_views) { + auto document_edit = document_edit_and_view.document_edit; + auto view = document_edit_and_view.view; + auto buffer = view->get_buffer(); + buffer->begin_user_action(); + + auto end_iter = buffer->end(); + // If entire buffer is replaced + if(document_edit->text_edits.size() == 1 && + document_edit->text_edits[0].range.start.line == 0 && document_edit->text_edits[0].range.start.character == 0 && + (document_edit->text_edits[0].range.end.line > end_iter.get_line() || + (document_edit->text_edits[0].range.end.line == end_iter.get_line() && document_edit->text_edits[0].range.end.character >= current_view->get_line_pos(end_iter)))) { + view->replace_text(document_edit->text_edits[0].new_text); + } + else { + for(auto text_edit_it = document_edit->text_edits.rbegin(); text_edit_it != document_edit->text_edits.rend(); ++text_edit_it) { + auto start_iter = view->get_iter_at_line_pos(text_edit_it->range.start.line, text_edit_it->range.start.character); + auto end_iter = view->get_iter_at_line_pos(text_edit_it->range.end.line, text_edit_it->range.end.character); + if(view != current_view) + view->get_buffer()->place_cursor(start_iter); + buffer->erase(start_iter, end_iter); + start_iter = view->get_iter_at_line_pos(text_edit_it->range.start.line, text_edit_it->range.start.character); + buffer->insert(start_iter, text_edit_it->new_text); + } + } + + buffer->end_user_action(); + if(!view->save()) + return; + } + } + }); + result_processed.get_future().get(); + } + write_response(id, ""); } Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path &file_path, const Glib::RefPtr &language, std::string language_id_, std::string language_server_) @@ -924,66 +1048,32 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { if(capabilities.rename || capabilities.document_highlight) { rename_similar_tokens = [this](const std::string &text) { - struct Changes { - std::string file; - std::vector text_edits; - }; - auto previous_text = get_token(get_buffer()->get_insert()->get_iter()); if(previous_text.empty()) return; auto iter = get_buffer()->get_insert()->get_iter(); - std::vector changes_vec; + LanguageProtocol::WorkspaceEdit workspace_edit; std::promise result_processed; if(capabilities.rename) { - write_request("textDocument/rename", to_string({make_position(iter.get_line(), get_line_pos(iter)), {"newName", '"' + text + '"'}}), [this, &changes_vec, &result_processed](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/rename", to_string({make_position(iter.get_line(), get_line_pos(iter)), {"newName", '"' + text + '"'}}), [this, &workspace_edit, &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 { - if(auto changes_pt = result.get_child_optional("changes")) { - for(auto file_it = changes_pt->begin(); file_it != changes_pt->end(); ++file_it) { - auto file = file_it->first; - file.erase(0, 7); - if(filesystem::file_in_path(file, project_path)) { - std::vector edits; - for(auto edit_it = file_it->second.begin(); edit_it != file_it->second.end(); ++edit_it) - edits.emplace_back(edit_it->second); - changes_vec.emplace_back(Changes{std::move(file), std::move(edits)}); - } - } - } - else if(auto changes_pt = result.get_child_optional("documentChanges")) { - for(auto change_it = changes_pt->begin(); change_it != changes_pt->end(); ++change_it) { - LanguageProtocol::TextDocumentEdit text_document_edit(change_it->second); - if(filesystem::file_in_path(text_document_edit.file, project_path)) - changes_vec.emplace_back(Changes{std::move(text_document_edit.file), std::move(text_document_edit.edits)}); - } - } - } - catch(...) { - changes_vec.clear(); - } + workspace_edit = LanguageProtocol::WorkspaceEdit(result, file_path); } result_processed.set_value(); }); } else { - write_request("textDocument/documentHighlight", to_string({make_position(iter.get_line(), get_line_pos(iter)), {"context", "{\"includeDeclaration\":true}"}}), [this, &changes_vec, &text, &result_processed](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/documentHighlight", to_string({make_position(iter.get_line(), get_line_pos(iter)), {"context", "{\"includeDeclaration\":true}"}}), [this, &workspace_edit, &text, &result_processed](const boost::property_tree::ptree &result, bool error) { if(!error) { try { std::vector edits; for(auto it = result.begin(); it != result.end(); ++it) edits.emplace_back(it->second, text); - changes_vec.emplace_back(Changes{file_path.string(), std::move(edits)}); + workspace_edit.document_edits.emplace_back(file_path.string(), std::move(edits)); } catch(...) { - changes_vec.clear(); + workspace_edit.document_edits.clear(); } } result_processed.set_value(); @@ -993,35 +1083,35 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { auto current_view = Notebook::get().get_current_view(); - struct ChangesAndView { - Changes *changes; + struct DocumentEditAndView { + LanguageProtocol::TextDocumentEdit *document_edit; Source::View *view; bool close; }; - std::vector changes_and_views; + std::vector document_edits_and_views; - for(auto &changes : changes_vec) { + for(auto &document_edit : workspace_edit.document_edits) { Source::View *view = nullptr; for(auto it = views.begin(); it != views.end(); ++it) { - if((*it)->file_path == changes.file) { + if((*it)->file_path == document_edit.file) { view = *it; break; } } if(!view) { - if(!Notebook::get().open(changes.file)) + if(!Notebook::get().open(document_edit.file)) return; view = Notebook::get().get_current_view(); - changes_and_views.emplace_back(ChangesAndView{&changes, view, true}); + document_edits_and_views.emplace_back(DocumentEditAndView{&document_edit, view, true}); } else - changes_and_views.emplace_back(ChangesAndView{&changes, view, false}); + document_edits_and_views.emplace_back(DocumentEditAndView{&document_edit, view, false}); } if(current_view) Notebook::get().open(current_view); - if(!changes_and_views.empty()) { + if(!document_edits_and_views.empty()) { Terminal::get().print("Renamed "); Terminal::get().print(previous_text, true); Terminal::get().print(" to "); @@ -1029,19 +1119,19 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { Terminal::get().print(" at:\n"); } - for(auto &changes_and_view : changes_and_views) { - auto changes = changes_and_view.changes; - auto view = changes_and_view.view; + for(auto &document_edit_and_view : document_edits_and_views) { + auto document_edit = document_edit_and_view.document_edit; + auto view = document_edit_and_view.view; auto buffer = view->get_buffer(); buffer->begin_user_action(); auto end_iter = buffer->end(); // If entire buffer is replaced - if(changes->text_edits.size() == 1 && - changes->text_edits[0].range.start.line == 0 && changes->text_edits[0].range.start.character == 0 && - (changes->text_edits[0].range.end.line > end_iter.get_line() || - (changes->text_edits[0].range.end.line == end_iter.get_line() && changes->text_edits[0].range.end.character >= get_line_pos(end_iter)))) { - view->replace_text(changes->text_edits[0].new_text); + if(document_edit->text_edits.size() == 1 && + document_edit->text_edits[0].range.start.line == 0 && document_edit->text_edits[0].range.start.character == 0 && + (document_edit->text_edits[0].range.end.line > end_iter.get_line() || + (document_edit->text_edits[0].range.end.line == end_iter.get_line() && document_edit->text_edits[0].range.end.character >= get_line_pos(end_iter)))) { + view->replace_text(document_edit->text_edits[0].new_text); Terminal::get().print(filesystem::get_short_path(view->file_path).string() + ":1:1\n"); } @@ -1053,13 +1143,13 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { }; std::list terminal_output_list; - for(auto edit_it = changes->text_edits.rbegin(); edit_it != changes->text_edits.rend(); ++edit_it) { - auto start_iter = view->get_iter_at_line_pos(edit_it->range.start.line, edit_it->range.start.character); - auto end_iter = view->get_iter_at_line_pos(edit_it->range.end.line, edit_it->range.end.character); + for(auto text_edit_it = document_edit->text_edits.rbegin(); text_edit_it != document_edit->text_edits.rend(); ++text_edit_it) { + auto start_iter = view->get_iter_at_line_pos(text_edit_it->range.start.line, text_edit_it->range.start.character); + auto end_iter = view->get_iter_at_line_pos(text_edit_it->range.end.line, text_edit_it->range.end.character); if(view != current_view) view->get_buffer()->place_cursor(start_iter); buffer->erase(start_iter, end_iter); - start_iter = view->get_iter_at_line_pos(edit_it->range.start.line, edit_it->range.start.character); + start_iter = view->get_iter_at_line_pos(text_edit_it->range.start.line, text_edit_it->range.start.character); auto start_line_iter = buffer->get_iter_at_line(start_iter.get_line()); while((*start_line_iter == ' ' || *start_line_iter == '\t') && start_line_iter < start_iter && start_line_iter.forward_char()) { @@ -1067,11 +1157,11 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { auto end_line_iter = start_iter; while(!end_line_iter.ends_line() && end_line_iter.forward_char()) { } - terminal_output_list.emplace_front(TerminalOutput{filesystem::get_short_path(view->file_path).string() + ':' + std::to_string(edit_it->range.start.line + 1) + ':' + std::to_string(edit_it->range.start.character + 1) + ": " + buffer->get_text(start_line_iter, start_iter), - edit_it->new_text, + terminal_output_list.emplace_front(TerminalOutput{filesystem::get_short_path(view->file_path).string() + ':' + std::to_string(text_edit_it->range.start.line + 1) + ':' + std::to_string(text_edit_it->range.start.character + 1) + ": " + buffer->get_text(start_line_iter, start_iter), + text_edit_it->new_text, buffer->get_text(start_iter, end_line_iter) + '\n'}); - buffer->insert(start_iter, edit_it->new_text); + buffer->insert(start_iter, text_edit_it->new_text); } for(auto &output : terminal_output_list) { @@ -1086,10 +1176,10 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { return; } - for(auto &changes_and_view : changes_and_views) { - if(changes_and_view.close) - Notebook::get().close(changes_and_view.view); - changes_and_view.close = false; + for(auto &document_edit_and_view : document_edits_and_views) { + if(document_edit_and_view.close) + Notebook::get().close(document_edit_and_view.view); + document_edit_and_view.close = false; } }; } @@ -1149,9 +1239,155 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { }; get_fix_its = [this]() { - if(fix_its.empty()) + if(!fix_its.empty()) + return fix_its; + + if(!capabilities.code_action) { Info::get().print("No fix-its found in current buffer"); - return fix_its; + return std::vector{}; + } + + // Fetch code actions if no fix-its are available + std::promise result_processed; + Gtk::TextIter start, end; + get_buffer()->get_selection_bounds(start, end); + std::vector> results; + write_request("textDocument/codeAction", to_string({make_range({start.get_line(), get_line_pos(start)}, {end.get_line(), get_line_pos(end)}), {"context", "{\"diagnostics\":[]}"}}), [&result_processed, &results](const boost::property_tree::ptree &result, bool error) { + if(!error) { + for(auto it = result.begin(); it != result.end(); ++it) { + auto title = it->second.get("title", ""); + if(!title.empty()) + results.emplace_back(title, it->second); + } + } + result_processed.set_value(); + }); + result_processed.get_future().get(); + + if(results.empty()) { + Info::get().print("No code actions found at cursor"); + return std::vector{}; + } + + SelectionDialog::create(this, true, true); + for(auto &result : results) + SelectionDialog::get()->add_row(result.first); + SelectionDialog::get()->on_select = [this, results = std::move(results)](unsigned int index, const std::string &text, bool hide_window) { + if(index >= results.size()) + return; + + auto edit_pt = results[index].second.get_child_optional("edit"); + if(!edit_pt) { + + if(capabilities.execute_command) { + auto command_pt = results[index].second.get_child_optional("command"); + if(command_pt) { + std::stringstream ss; + boost::property_tree::write_json(ss, *command_pt, false); + // ss.str() is enclosed in {}, and has ending newline + auto str = ss.str(); + if(str.size() <= 3) + return; + write_request("workspace/executeCommand", str.substr(1, str.size() - 3), [](const boost::property_tree::ptree &result, bool error) { + }); + return; + } + } + + // TODO: disabled since rust-analyzer does not yet support numbers inside "" (boost::property_tree::write_json outputs all values with "") + // std::promise result_processed; + // std::stringstream ss; + // boost::property_tree::write_json(ss, results[index].second, false); + // // ss.str() is enclosed in {}, and has ending newline + // auto str = ss.str(); + // if(str.size() <= 3) + // return; + // write_request("codeAction/resolve", str.substr(1, str.size() - 3), [&result_processed, &edit_pt](const boost::property_tree::ptree &result, bool error) { + // if(!error) { + // auto child = result.get_child_optional("edit"); + // if(child) + // edit_pt = *child; // Make copy, since result will be destroyed at end of callback + // } + // result_processed.set_value(); + // }); + // result_processed.get_future().get(); + + if(!edit_pt) + return; + } + + LanguageProtocol::WorkspaceEdit workspace_edit; + try { + workspace_edit = LanguageProtocol::WorkspaceEdit(*edit_pt, file_path); + } + catch(...) { + return; + } + + auto current_view = Notebook::get().get_current_view(); + + struct DocumentEditAndView { + LanguageProtocol::TextDocumentEdit *document_edit; + Source::View *view; + }; + std::vector document_edits_and_views; + + for(auto &document_edit : workspace_edit.document_edits) { + Source::View *view = nullptr; + for(auto it = views.begin(); it != views.end(); ++it) { + if((*it)->file_path == document_edit.file) { + view = *it; + break; + } + } + if(!view) { + if(!Notebook::get().open(document_edit.file)) + return; + view = Notebook::get().get_current_view(); + document_edits_and_views.emplace_back(DocumentEditAndView{&document_edit, view}); + } + else + document_edits_and_views.emplace_back(DocumentEditAndView{&document_edit, view}); + } + + if(current_view) + Notebook::get().open(current_view); + + for(auto &document_edit_and_view : document_edits_and_views) { + auto document_edit = document_edit_and_view.document_edit; + auto view = document_edit_and_view.view; + auto buffer = view->get_buffer(); + buffer->begin_user_action(); + + auto end_iter = buffer->end(); + // If entire buffer is replaced + if(document_edit->text_edits.size() == 1 && + document_edit->text_edits[0].range.start.line == 0 && document_edit->text_edits[0].range.start.character == 0 && + (document_edit->text_edits[0].range.end.line > end_iter.get_line() || + (document_edit->text_edits[0].range.end.line == end_iter.get_line() && document_edit->text_edits[0].range.end.character >= get_line_pos(end_iter)))) { + view->replace_text(document_edit->text_edits[0].new_text); + } + else { + for(auto text_edit_it = document_edit->text_edits.rbegin(); text_edit_it != document_edit->text_edits.rend(); ++text_edit_it) { + auto start_iter = view->get_iter_at_line_pos(text_edit_it->range.start.line, text_edit_it->range.start.character); + auto end_iter = view->get_iter_at_line_pos(text_edit_it->range.end.line, text_edit_it->range.end.character); + if(view != current_view) + view->get_buffer()->place_cursor(start_iter); + buffer->erase(start_iter, end_iter); + start_iter = view->get_iter_at_line_pos(text_edit_it->range.start.line, text_edit_it->range.start.character); + buffer->insert(start_iter, text_edit_it->new_text); + } + } + + buffer->end_user_action(); + if(!view->save()) + return; + } + }; + hide_tooltips(); + SelectionDialog::get()->show(); + + return std::vector{}; }; } @@ -1675,20 +1911,26 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vectorbegin(); it != diagnostics_pt->end(); ++it) quickfix_diagnostics.emplace_back(it->second); } - if(auto changes = it->second.get_child_optional("edit.changes")) { - for(auto file_it = changes->begin(); file_it != changes->end(); ++file_it) { - for(auto edit_it = file_it->second.begin(); edit_it != file_it->second.end(); ++edit_it) { - LanguageProtocol::TextEdit edit(edit_it->second); + auto edit_pt = it->second.get_child_optional("edit"); + if(!edit_pt) { + auto arguments = it->second.get_child_optional("arguments"); // Workaround for typescript-language-server (arguments) + if(arguments && arguments->begin() != arguments->end()) + edit_pt = arguments->begin()->second; + } + if(edit_pt) { + LanguageProtocol::WorkspaceEdit workspace_edit(*edit_pt, file_path); + for(auto &document_edit : workspace_edit.document_edits) { + for(auto &text_edit : document_edit.text_edits) { if(!quickfix_diagnostics.empty()) { for(auto &diagnostic : diagnostics) { for(auto &quickfix_diagnostic : quickfix_diagnostics) { if(diagnostic.message == quickfix_diagnostic.message && diagnostic.range == quickfix_diagnostic.range) { auto pair = diagnostic.quickfixes.emplace(title, std::set{}); pair.first->second.emplace( - edit.new_text, - filesystem::get_path_from_uri(file_it->first).string(), - std::make_pair(Offset(edit.range.start.line, edit.range.start.character), - Offset(edit.range.end.line, edit.range.end.character))); + std::move(text_edit.new_text), + std::move(document_edit.file), + std::make_pair(Offset(text_edit.range.start.line, text_edit.range.start.character), + Offset(text_edit.range.end.line, text_edit.range.end.character))); break; } } @@ -1696,13 +1938,13 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vector{}); pair.first->second.emplace( - edit.new_text, - filesystem::get_path_from_uri(file_it->first).string(), - std::make_pair(Offset(edit.range.start.line, edit.range.start.character), - Offset(edit.range.end.line, edit.range.end.character))); + std::move(text_edit.new_text), + std::move(document_edit.file), + std::make_pair(Offset(text_edit.range.start.line, text_edit.range.start.character), + Offset(text_edit.range.end.line, text_edit.range.end.character))); break; } } @@ -1710,50 +1952,6 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vectorsecond.get_child_optional("edit.documentChanges"); - if(!changes_pt) { // Workaround for typescript-language-server - if(auto arguments_pt = it->second.get_child_optional("arguments")) { - if(!arguments_pt->empty()) - changes_pt = arguments_pt->begin()->second.get_child_optional("documentChanges"); - } - } - if(changes_pt) { - for(auto change_it = changes_pt->begin(); change_it != changes_pt->end(); ++change_it) { - LanguageProtocol::TextDocumentEdit text_document_edit(change_it->second); - for(auto &edit : text_document_edit.edits) { - if(!quickfix_diagnostics.empty()) { - for(auto &diagnostic : diagnostics) { - for(auto &quickfix_diagnostic : quickfix_diagnostics) { - if(diagnostic.message == quickfix_diagnostic.message && diagnostic.range == quickfix_diagnostic.range) { - auto pair = diagnostic.quickfixes.emplace(title, std::set{}); - pair.first->second.emplace( - edit.new_text, - text_document_edit.file, - std::make_pair(Offset(edit.range.start.line, edit.range.start.character), - Offset(edit.range.end.line, edit.range.end.character))); - break; - } - } - } - } - else { // Workaround for language server that does not report quickfix diagnostics - for(auto &diagnostic : diagnostics) { - if(edit.range.start.line == diagnostic.range.start.line) { - auto pair = diagnostic.quickfixes.emplace(title, std::set{}); - pair.first->second.emplace( - edit.new_text, - text_document_edit.file, - std::make_pair(Offset(edit.range.start.line, edit.range.start.character), - Offset(edit.range.end.line, edit.range.end.character))); - break; - } - } - } - } - } - } - } } } } diff --git a/src/source_language_protocol.hpp b/src/source_language_protocol.hpp index 0f33f37..c7d0f2c 100644 --- a/src/source_language_protocol.hpp +++ b/src/source_language_protocol.hpp @@ -86,9 +86,17 @@ namespace LanguageProtocol { class TextDocumentEdit { public: TextDocumentEdit(const boost::property_tree::ptree &pt); + TextDocumentEdit(std::string file, std::vector text_edits); std::string file; - std::vector edits; + std::vector text_edits; + }; + + class WorkspaceEdit { + public: + WorkspaceEdit() = default; + WorkspaceEdit(const boost::property_tree::ptree &pt, boost::filesystem::path file_path); + std::vector document_edits; }; class Capabilities { @@ -111,6 +119,7 @@ namespace LanguageProtocol { bool document_range_formatting = false; bool rename = false; bool code_action = false; + bool execute_command = false; bool type_coverage = false; bool use_line_index = false; }; @@ -124,6 +133,9 @@ namespace LanguageProtocol { Capabilities capabilities; + /// Dispatcher must be destroyed in main thread + std::unique_ptr dispatcher = std::make_unique(); + Mutex views_mutex; std::set views GUARDED_BY(views_mutex); @@ -176,12 +188,12 @@ namespace Source { void rename(const boost::filesystem::path &path) override; bool save() override; - private: /// Get line offset depending on if utf-8 byte offsets or utf-16 code units are used int get_line_pos(const Gtk::TextIter &iter); /// Get line offset depending on if utf-8 byte offsets or utf-16 code units are used int get_line_pos(int line, int line_index); + private: std::pair make_position(int line, int character); std::pair make_range(const std::pair &start, const std::pair &end); std::string to_string(const std::pair ¶m); From 2b9491565fa3126dc86b6bbbe511ac9f1d1e024e Mon Sep 17 00:00:00 2001 From: eidheim Date: Sun, 20 Jun 2021 10:32:14 +0200 Subject: [PATCH 148/375] Language client: now supports CompletionItem.additionalTextEdits --- src/source_language_protocol.cpp | 32 +++++++++++++++++++++++++++++--- src/source_language_protocol.hpp | 1 + 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 41b4512..b77adfa 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -1665,7 +1665,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { documentation.clear(); if(!using_named_parameters || used_named_parameters.find(insert) == used_named_parameters.end()) { autocomplete->rows.emplace_back(std::move(label)); - autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), {}, std::move(documentation), std::move(kind), {}}); + autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), {}, std::move(documentation), std::move(kind), {}, {}}); } } parameter_position++; @@ -1718,9 +1718,23 @@ void Source::LanguageProtocolView::setup_autocomplete() { documentation_kind = documentation_pt->get("kind", ""); } } + boost::property_tree::ptree ptree; if(detail.empty() && documentation.empty() && (is_incomplete || is_js)) // Workaround for typescript-language-server (is_js) ptree = it->second; + + std::vector additional_text_edits; + auto additional_text_edits_pt = it->second.get_child_optional("additionalTextEdits"); + if(additional_text_edits_pt) { + try { + for(auto text_edit_it = additional_text_edits_pt->begin(); text_edit_it != additional_text_edits_pt->end(); ++text_edit_it) + additional_text_edits.emplace_back(text_edit_it->second); + } + catch(...) { + additional_text_edits.clear(); + } + } + auto insert = it->second.get("insertText", ""); if(insert.empty()) insert = it->second.get("textEdit.newText", ""); @@ -1731,7 +1745,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { if(kind >= 2 && kind <= 4 && insert.find('(') == std::string::npos) // If kind is method, function or constructor, but parentheses are missing insert += "(${1:})"; autocomplete->rows.emplace_back(std::move(label)); - autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), std::move(detail), std::move(documentation), std::move(documentation_kind), std::move(ptree)}); + autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), std::move(detail), std::move(documentation), std::move(documentation_kind), std::move(ptree), std::move(additional_text_edits)}); } } } @@ -1742,7 +1756,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { for(auto &snippet : *snippets) { if(starts_with(snippet.prefix, prefix)) { autocomplete->rows.emplace_back(snippet.prefix); - autocomplete_rows.emplace_back(AutocompleteRow{snippet.body, {}, snippet.description, {}, {}}); + autocomplete_rows.emplace_back(AutocompleteRow{snippet.body, {}, snippet.description, {}, {}, {}}); } } } @@ -1802,7 +1816,19 @@ void Source::LanguageProtocolView::setup_autocomplete() { return; } + auto additional_text_edits = std::move(autocomplete_rows[index].additional_text_edits); // autocomplete_rows will be cleared after insert_snippet (see autocomplete->on_hide) + + get_buffer()->begin_user_action(); insert_snippet(CompletionDialog::get()->start_mark->get_iter(), insert); + for(auto it = additional_text_edits.rbegin(); it != additional_text_edits.rend(); ++it) { + auto start = get_iter_at_line_pos(it->range.start.line, it->range.start.character); + auto end = get_iter_at_line_pos(it->range.end.line, it->range.end.character); + get_buffer()->erase(start, end); + start = get_iter_at_line_pos(it->range.start.line, it->range.start.character); + get_buffer()->insert(start, it->new_text); + } + get_buffer()->end_user_action(); + auto iter = get_buffer()->get_insert()->get_iter(); if(*iter == ')' && iter.backward_char() && *iter == '(') { // If no arguments, try signatureHelp last_keyval = '('; diff --git a/src/source_language_protocol.hpp b/src/source_language_protocol.hpp index c7d0f2c..722af3b 100644 --- a/src/source_language_protocol.hpp +++ b/src/source_language_protocol.hpp @@ -257,6 +257,7 @@ namespace Source { std::string kind; /// CompletionItem for completionItem/resolve boost::property_tree::ptree ptree; + std::vector additional_text_edits; }; std::vector autocomplete_rows; From 30ce9ca3a4aef8e3df15f516506632b30b82e0f7 Mon Sep 17 00:00:00 2001 From: eidheim Date: Sun, 20 Jun 2021 13:35:45 +0200 Subject: [PATCH 149/375] Added file path escaping to language_protocol_server_test.cpp --- tests/language_protocol_server_test.cpp | 29 +++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/tests/language_protocol_server_test.cpp b/tests/language_protocol_server_test.cpp index 7a73795..b3535d2 100644 --- a/tests/language_protocol_server_test.cpp +++ b/tests/language_protocol_server_test.cpp @@ -7,6 +7,31 @@ #include #endif +std::string 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] == '\t') { + text.replace(c, 1, "\\t"); + ++c; + } + else if(text[c] == '"') { + text.replace(c, 1, "\\\""); + ++c; + } + else if(text[c] == '\\') { + text.replace(c, 1, "\\\\"); + ++c; + } + } + return text; +} int main() { #ifdef _WIN32 @@ -447,7 +472,7 @@ int main() { "id": "5", "result": [ { - "uri": "file://)" + file_path.string() + + "uri": "file://)" + escape_text(file_path.string()) + R"(", "range": { "start": { @@ -461,7 +486,7 @@ int main() { } }, { - "uri": "file://)" + file_path.string() + + "uri": "file://)" + escape_text(file_path.string()) + R"(", "range": { "start": { From 6d46110907b6f9190c91fb0e6f0afb539d58344c Mon Sep 17 00:00:00 2001 From: eidheim Date: Sun, 20 Jun 2021 20:50:49 +0200 Subject: [PATCH 150/375] Added support for Julia's end keywork in show_or_hide() --- src/source.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/source.cpp b/src/source.cpp index 6586568..00d4c0f 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -1858,7 +1858,7 @@ void Source::View::show_or_hide() { end = get_buffer()->get_iter_at_line(end.get_line()); break; } - static std::vector exact = {"}", ")", "]", ">", " exact = {"}", ")", "]", ">", " followed_by_non_token_char = {"elseif", "elif", "catch", "case", "default", "private", "public", "protected"}; if(text == "{") { // C/C++ sometimes starts a block with a standalone { if(!is_token_char(*last_tabs_end)) { From 32b6436fe894f70077bab1f16830d67152df9b7e Mon Sep 17 00:00:00 2001 From: eidheim Date: Mon, 21 Jun 2021 08:15:45 +0200 Subject: [PATCH 151/375] Minor cleanup of show_or_hide() --- src/source.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/source.cpp b/src/source.cpp index 00d4c0f..ecf6ec0 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -1858,8 +1858,8 @@ void Source::View::show_or_hide() { end = get_buffer()->get_iter_at_line(end.get_line()); break; } - static std::vector exact = {"}", ")", "]", ">", " followed_by_non_token_char = {"elseif", "elif", "catch", "case", "default", "private", "public", "protected"}; + static std::vector starts_with_symbols = {"}", ")", "]", ">", " exact_tokens = {"else", "end", "endif", "elseif", "elif", "catch", "case", "default", "private", "public", "protected"}; if(text == "{") { // C/C++ sometimes starts a block with a standalone { if(!is_token_char(*last_tabs_end)) { end = get_buffer()->get_iter_at_line(end.get_line()); @@ -1875,11 +1875,11 @@ void Source::View::show_or_hide() { } } } - else if(std::none_of(exact.begin(), exact.end(), [&text](const std::string &e) { - return starts_with(text, e); + else if(std::none_of(starts_with_symbols.begin(), starts_with_symbols.end(), [&text](const std::string &symbol) { + return starts_with(text, symbol); }) && - std::none_of(followed_by_non_token_char.begin(), followed_by_non_token_char.end(), [&text](const std::string &e) { - return starts_with(text, e) && text.size() > e.size() && !is_token_char(text[e.size()]); + std::none_of(exact_tokens.begin(), exact_tokens.end(), [&text](const std::string &token) { + return starts_with(text, token) && (text.size() <= token.size() || !is_token_char(text[token.size()])); })) { end = get_buffer()->get_iter_at_line(end.get_line()); break; From c7340b709da1b2b8d3eb2e686c7576c34d457a91 Mon Sep 17 00:00:00 2001 From: eidheim Date: Mon, 21 Jun 2021 12:07:37 +0200 Subject: [PATCH 152/375] Added JSON::write_json, and some various cleanup --- src/CMakeLists.txt | 1 + src/cmake.cpp | 2 +- src/config.cpp | 13 +- src/files.hpp | 437 ++++++++++++------------ src/filesystem.cpp | 4 +- src/json.cpp | 101 ++++++ src/json.hpp | 19 ++ src/meson.cpp | 2 +- src/project.cpp | 2 +- src/project_build.cpp | 2 +- src/source_base.cpp | 4 +- src/source_language_protocol.cpp | 367 +++++++++----------- src/source_language_protocol.hpp | 16 +- src/usages_clang.cpp | 2 +- src/window.cpp | 20 +- tests/CMakeLists.txt | 6 +- tests/json_test.cpp | 58 ++++ tests/language_protocol_server_test.cpp | 32 +- 18 files changed, 616 insertions(+), 472 deletions(-) create mode 100644 src/json.cpp create mode 100644 src/json.hpp create mode 100644 tests/json_test.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3585c7f..71cb1d9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,6 +10,7 @@ set(JUCI_SHARED_FILES filesystem.cpp git.cpp grep.cpp + json.cpp menu.cpp meson.cpp project_build.cpp diff --git a/src/cmake.cpp b/src/cmake.cpp index eb00c17..19fd522 100644 --- a/src/cmake.cpp +++ b/src/cmake.cpp @@ -12,7 +12,7 @@ CMake::CMake(const boost::filesystem::path &path) { const auto find_cmake_project = [](const boost::filesystem::path &file_path) { - std::ifstream input(file_path.string(), std::ofstream::binary); + std::ifstream input(file_path.string(), std::ios::binary); if(input) { std::string line; while(std::getline(input, line)) { diff --git a/src/config.cpp b/src/config.cpp index 5d725b5..fde9cec 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -1,6 +1,7 @@ #include "config.hpp" #include "files.hpp" #include "filesystem.hpp" +#include "json.hpp" #include "terminal.hpp" #include #include @@ -77,8 +78,16 @@ void Config::update(boost::property_tree::ptree &cfg) { 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); + if(!cfg_ok) { + auto path = home_juci_path / "config" / "config.json"; + std::ofstream output(path.string(), std::ios::binary); + if(output) { + JSON::write(output, cfg); + output << '\n'; + } + else + std::cerr << "Error writing config file: " << filesystem::get_short_path(path).string() << std::endl; + } } void Config::make_version_dependent_corrections(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, const std::string &version) { diff --git a/src/files.hpp b/src/files.hpp index 92cb638..249a6b9 100644 --- a/src/files.hpp +++ b/src/files.hpp @@ -4,244 +4,243 @@ /// 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(", - "gtk_theme": { - "name_comment": "Use \"\" for default theme, At least these two exist on all systems: Adwaita, Raleigh", - "name": "", - "variant_comment": "Use \"\" for default variant, and \"dark\" for dark theme variant. Note that not all themes support dark variant, but for instance Adwaita does", - "variant": "", - "font_comment": "Set to override theme font, for instance: \"Arial 12\"", - "font": "" - }, - "source": { - "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" +const std::string default_config_file = 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": "", + "variant_comment": "Use \"\" for default variant, and \"dark\" for dark theme variant. Note that not all themes support dark variant, but for instance Adwaita does", + "variant": "", + "font_comment": "Set to override theme font, for instance: \"Arial 12\"", + "font": "" + }, + "source": { + "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( - "font": "Menlo",)RAW" + R"RAW( + "font": "Menlo",)RAW" #else #ifdef _WIN32 - R"RAW( - "font": "Consolas",)RAW" + R"RAW( + "font": "Consolas",)RAW" #else - R"RAW( - "font": "Monospace",)RAW" + R"RAW( + "font": "Monospace",)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", - "show_whitespace_characters": "", - "format_style_on_save_comment": "Performs style format on save if supported on language in buffer", - "format_style_on_save": false, - "format_style_on_save_if_style_file_found_comment": "Format style if format file is found, even if format_style_on_save is false", - "format_style_on_save_if_style_file_found": true, - "smart_brackets_comment": "If smart_inserts is enabled, this option is automatically enabled. When inserting an already closed bracket, the cursor might instead be moved, avoiding the need of arrow keys after autocomplete", - "smart_brackets": true, - "smart_inserts_comment": "When for instance inserting (, () gets inserted. Applies to: (), [], \", '. Also enables pressing ; inside an expression before a final ) to insert ; at the end of line, and deletions of empty insertions", - "smart_inserts": true, - "show_map": true, - "map_font_size": "1", - "show_git_diff": true, - "show_background_pattern": true, - "show_right_margin": false, - "right_margin_position": 80, - "spellcheck_language_comment": "Use \"\" to set language from your locale settings", - "spellcheck_language": "en_US", - "auto_tab_char_and_size_comment": "Use false to always use default tab char and size", - "auto_tab_char_and_size": true, - "default_tab_char_comment": "Use \"\t\" for regular tab", - "default_tab_char": " ", - "default_tab_size": 2, - "tab_indents_line": true, - "word_wrap_comment": "Specify language ids that should enable word wrap, for instance: chdr, c, cpphdr, cpp, js, python, or all to enable word wrap for all languages", - "word_wrap": "markdown, latex", - "highlight_current_line": true, - "show_line_numbers": true, - "enable_multiple_cursors": false, - "auto_reload_changed_files": true, - "search_for_selection": true, - "clang_format_style_comment": "IndentWidth, AccessModifierOffset and UseTab are set automatically. See http://clang.llvm.org/docs/ClangFormatStyleOptions.html", - "clang_format_style": "ColumnLimit: 0, NamespaceIndentation: All", - "clang_tidy_enable_comment": "Enable clang-tidy in new C/C++ buffers", - "clang_tidy_enable": false, - "clang_tidy_checks_comment": "In new C/C++ buffers, these checks are appended to the value of 'Checks' in the .clang-tidy file, if any", - "clang_tidy_checks": "", - "clang_usages_threads_comment": "The number of threads used in finding usages in unparsed files. -1 corresponds to the number of cores available, and 0 disables the search", - "clang_usages_threads": -1, - "clang_detailed_preprocessing_record_comment": "Set to true to, at the cost of increased resource use, include all macro definitions and instantiations when parsing new C/C++ buffers. You should reopen buffers and delete build/.usages_clang after changing this option.", - "clang_detailed_preprocessing_record": false, - "debug_place_cursor_at_stop": false - }, - "terminal": { - "history_size": 10000, - "font_comment": "Use \"\" to use source.font with slightly smaller size", - "font": "", - "clear_on_compile": true, - "clear_on_run_command": false, - "hide_entry_on_run_command": true - }, - "project": { - "default_build_path_comment": "Use to insert the project top level directory name", - "default_build_path": "./build", - "debug_build_path_comment": "Use to insert the project top level directory name, and to insert your default_build_path setting.", - "debug_build_path": "/debug", - "cmake": {)RAW" + 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", + "show_whitespace_characters": "", + "format_style_on_save_comment": "Performs style format on save if supported on language in buffer", + "format_style_on_save": false, + "format_style_on_save_if_style_file_found_comment": "Format style if format file is found, even if format_style_on_save is false", + "format_style_on_save_if_style_file_found": true, + "smart_brackets_comment": "If smart_inserts is enabled, this option is automatically enabled. When inserting an already closed bracket, the cursor might instead be moved, avoiding the need of arrow keys after autocomplete", + "smart_brackets": true, + "smart_inserts_comment": "When for instance inserting (, () gets inserted. Applies to: (), [], \", '. Also enables pressing ; inside an expression before a final ) to insert ; at the end of line, and deletions of empty insertions", + "smart_inserts": true, + "show_map": true, + "map_font_size": 1, + "show_git_diff": true, + "show_background_pattern": true, + "show_right_margin": false, + "right_margin_position": 80, + "spellcheck_language_comment": "Use \"\" to set language from your locale settings", + "spellcheck_language": "en_US", + "auto_tab_char_and_size_comment": "Use false to always use default tab char and size", + "auto_tab_char_and_size": true, + "default_tab_char_comment": "Use \"\t\" for regular tab", + "default_tab_char": " ", + "default_tab_size": 2, + "tab_indents_line": true, + "word_wrap_comment": "Specify language ids that should enable word wrap, for instance: chdr, c, cpphdr, cpp, js, python, or all to enable word wrap for all languages", + "word_wrap": "markdown, latex", + "highlight_current_line": true, + "show_line_numbers": true, + "enable_multiple_cursors": false, + "auto_reload_changed_files": true, + "search_for_selection": true, + "clang_format_style_comment": "IndentWidth, AccessModifierOffset and UseTab are set automatically. See http://clang.llvm.org/docs/ClangFormatStyleOptions.html", + "clang_format_style": "ColumnLimit: 0, NamespaceIndentation: All", + "clang_tidy_enable_comment": "Enable clang-tidy in new C/C++ buffers", + "clang_tidy_enable": false, + "clang_tidy_checks_comment": "In new C/C++ buffers, these checks are appended to the value of 'Checks' in the .clang-tidy file, if any", + "clang_tidy_checks": "", + "clang_usages_threads_comment": "The number of threads used in finding usages in unparsed files. -1 corresponds to the number of cores available, and 0 disables the search", + "clang_usages_threads": -1, + "clang_detailed_preprocessing_record_comment": "Set to true to, at the cost of increased resource use, include all macro definitions and instantiations when parsing new C/C++ buffers. You should reopen buffers and delete build/.usages_clang after changing this option.", + "clang_detailed_preprocessing_record": false, + "debug_place_cursor_at_stop": false + }, + "terminal": { + "history_size": 10000, + "font_comment": "Use \"\" to use source.font with slightly smaller size", + "font": "", + "clear_on_compile": true, + "clear_on_run_command": false, + "hide_entry_on_run_command": true + }, + "project": { + "default_build_path_comment": "Use to insert the project top level directory name", + "default_build_path": "./build", + "debug_build_path_comment": "Use to insert the project top level directory name, and to insert your default_build_path setting.", + "debug_build_path": "/debug", + "cmake": {)RAW" #ifdef _WIN32 - R"RAW( - "command": "cmake -G\"MSYS Makefiles\"",)RAW" + R"RAW( + "command": "cmake -G\"MSYS Makefiles\"",)RAW" #else - R"RAW( - "command": "cmake",)RAW" + R"RAW( + "command": "cmake",)RAW" #endif - R"RAW( - "compile_command": "cmake --build ." - }, - "meson": { - "command": "meson", - "compile_command": "ninja" - }, - "default_build_management_system_comment": "Select which build management system to use when creating a new C or C++ project, for instance \"cmake\" or \"meson\"", - "default_build_management_system": "cmake", - "save_on_compile_or_run": true,)RAW" + R"RAW( + "compile_command": "cmake --build ." + }, + "meson": { + "command": "meson", + "compile_command": "ninja" + }, + "default_build_management_system_comment": "Select which build management system to use when creating a new C or C++ project, for instance \"cmake\" or \"meson\"", + "default_build_management_system": "cmake", + "save_on_compile_or_run": true,)RAW" #ifdef JUCI_USE_UCTAGS - R"RAW( - "ctags_command": "uctags",)RAW" + R"RAW( + "ctags_command": "uctags",)RAW" #else - R"RAW( - "ctags_command": "ctags",)RAW" + R"RAW( + "ctags_command": "ctags",)RAW" #endif - R"RAW( - "grep_command": "grep", - "cargo_command": "cargo", - "python_command": "python -u", - "markdown_command": "grip -b" - }, - "keybindings": { - "preferences": "comma", - "snippets": "", - "commands": "", - "quit": "q", - "file_new_file": "n", - "file_new_folder": "n", - "file_open_file": "o", - "file_open_folder": "o", - "file_reload_file": "", - "file_save": "s", - "file_save_as": "s", - "file_close_file": "w", - "file_close_folder": "", - "file_close_project": "", - "file_print": "", - "edit_undo": "z", - "edit_redo": "z", - "edit_cut": "x", - "edit_cut_lines": "x", - "edit_copy": "c", - "edit_copy_lines": "c", - "edit_paste": "v", - "edit_extend_selection": "a", - "edit_shrink_selection": "a", - "edit_show_or_hide": "", - "edit_find": "f", - "source_spellcheck": "", - "source_spellcheck_clear": "", - "source_spellcheck_next_error": "e", - "source_git_next_diff": "k", - "source_git_show_diff": "k", - "source_indentation_set_buffer_tab": "", - "source_indentation_auto_indent_buffer": "i", - "source_goto_line": "g", - "source_center_cursor": "l", - "source_cursor_history_back": "Left", - "source_cursor_history_forward": "Right", - "source_show_completion_comment": "Add completion keybinding to disable interactive autocompletion", - "source_show_completion": "", - "source_find_file": "p", - "source_find_symbol": "f", - "source_find_pattern": "f", - "source_comments_toggle": "slash", - "source_comments_add_documentation": "slash", - "source_find_documentation": "d", - "source_goto_declaration": "d", - "source_goto_type_declaration": "d", - "source_goto_implementation": "i", - "source_goto_usage": "u", - "source_goto_method": "m", - "source_rename": "r", - "source_implement_method": "m", - "source_goto_next_diagnostic": "e", - "source_apply_fix_its": "space", - "project_set_run_arguments": "", - "project_compile_and_run": "Return", - "project_compile": "Return", - "project_run_command": "Return", - "project_kill_last_running": "Escape", - "project_force_kill_last_running": "Escape", - "debug_set_run_arguments": "", - "debug_start_continue": "y", - "debug_stop": "y", - "debug_kill": "k", - "debug_step_over": "j", - "debug_step_into": "t", - "debug_step_out": "t", - "debug_backtrace": "j", - "debug_show_variables": "b", - "debug_run_command": "Return", - "debug_toggle_breakpoint": "b", - "debug_show_breakpoints": "b", - "debug_goto_stop": "l",)RAW" + R"RAW( + "grep_command": "grep", + "cargo_command": "cargo", + "python_command": "python -u", + "markdown_command": "grip -b" + }, + "keybindings": { + "preferences": "comma", + "snippets": "", + "commands": "", + "quit": "q", + "file_new_file": "n", + "file_new_folder": "n", + "file_open_file": "o", + "file_open_folder": "o", + "file_reload_file": "", + "file_save": "s", + "file_save_as": "s", + "file_close_file": "w", + "file_close_folder": "", + "file_close_project": "", + "file_print": "", + "edit_undo": "z", + "edit_redo": "z", + "edit_cut": "x", + "edit_cut_lines": "x", + "edit_copy": "c", + "edit_copy_lines": "c", + "edit_paste": "v", + "edit_extend_selection": "a", + "edit_shrink_selection": "a", + "edit_show_or_hide": "", + "edit_find": "f", + "source_spellcheck": "", + "source_spellcheck_clear": "", + "source_spellcheck_next_error": "e", + "source_git_next_diff": "k", + "source_git_show_diff": "k", + "source_indentation_set_buffer_tab": "", + "source_indentation_auto_indent_buffer": "i", + "source_goto_line": "g", + "source_center_cursor": "l", + "source_cursor_history_back": "Left", + "source_cursor_history_forward": "Right", + "source_show_completion_comment": "Add completion keybinding to disable interactive autocompletion", + "source_show_completion": "", + "source_find_file": "p", + "source_find_symbol": "f", + "source_find_pattern": "f", + "source_comments_toggle": "slash", + "source_comments_add_documentation": "slash", + "source_find_documentation": "d", + "source_goto_declaration": "d", + "source_goto_type_declaration": "d", + "source_goto_implementation": "i", + "source_goto_usage": "u", + "source_goto_method": "m", + "source_rename": "r", + "source_implement_method": "m", + "source_goto_next_diagnostic": "e", + "source_apply_fix_its": "space", + "project_set_run_arguments": "", + "project_compile_and_run": "Return", + "project_compile": "Return", + "project_run_command": "Return", + "project_kill_last_running": "Escape", + "project_force_kill_last_running": "Escape", + "debug_set_run_arguments": "", + "debug_start_continue": "y", + "debug_stop": "y", + "debug_kill": "k", + "debug_step_over": "j", + "debug_step_into": "t", + "debug_step_out": "t", + "debug_backtrace": "j", + "debug_show_variables": "b", + "debug_run_command": "Return", + "debug_toggle_breakpoint": "b", + "debug_show_breakpoints": "b", + "debug_goto_stop": "l",)RAW" #ifdef __linux - R"RAW( - "window_next_tab": "Tab", - "window_previous_tab": "Tab",)RAW" + R"RAW( + "window_next_tab": "Tab", + "window_previous_tab": "Tab",)RAW" #else - R"RAW( - "window_next_tab": "Right", - "window_previous_tab": "Left",)RAW" + R"RAW( + "window_next_tab": "Right", + "window_previous_tab": "Left",)RAW" #endif - R"RAW( - "window_goto_tab": "", - "window_toggle_split": "", - "window_split_source_buffer": "",)RAW" + R"RAW( + "window_goto_tab": "", + "window_toggle_split": "", + "window_split_source_buffer": "",)RAW" #ifdef __APPLE__ - R"RAW( - "window_toggle_full_screen": "f",)RAW" + R"RAW( + "window_toggle_full_screen": "f",)RAW" #else - R"RAW( - "window_toggle_full_screen": "F11",)RAW" + R"RAW( + "window_toggle_full_screen": "F11",)RAW" #endif - R"RAW( - "window_toggle_directories": "", - "window_toggle_terminal": "", - "window_toggle_menu": "", - "window_toggle_tabs": "", - "window_toggle_zen_mode": "", - "window_clear_terminal": "" - }, - "documentation_searches": { - "clang": { - "separator": "::", - "queries": { - "@empty": "https://www.google.com/search?q=c%2B%2B+", - "std": "https://www.google.com/search?q=site:http://www.cplusplus.com/reference/+", - "boost": "https://www.google.com/search?q=site:http://www.boost.org/doc/libs/1_59_0/+", - "Gtk": "https://www.google.com/search?q=site:https://developer.gnome.org/gtkmm/stable/+", - "@any": "https://www.google.com/search?q=" - } - } - }, - "log": { - "libclang_comment": "Outputs diagnostics for new C/C++ buffers", - "libclang": false, - "language_server": false + R"RAW( + "window_toggle_directories": "", + "window_toggle_terminal": "", + "window_toggle_menu": "", + "window_toggle_tabs": "", + "window_toggle_zen_mode": "", + "window_clear_terminal": "" + }, + "documentation_searches": { + "clang": { + "separator": "::", + "queries": { + "@empty": "https://www.google.com/search?q=c%2B%2B+", + "std": "https://www.google.com/search?q=site:http://www.cplusplus.com/reference/+", + "boost": "https://www.google.com/search?q=site:http://www.boost.org/doc/libs/1_59_0/+", + "Gtk": "https://www.google.com/search?q=site:https://developer.gnome.org/gtkmm/stable/+", + "@any": "https://www.google.com/search?q=" + } } + }, + "log": { + "libclang_comment": "Outputs diagnostics for new C/C++ buffers", + "libclang": false, + "language_server": false + } } )RAW"; diff --git a/src/filesystem.cpp b/src/filesystem.cpp index 76acacf..a1a75aa 100644 --- a/src/filesystem.cpp +++ b/src/filesystem.cpp @@ -9,7 +9,7 @@ //Only use on small files std::string filesystem::read(const std::string &path) { std::string str; - std::ifstream input(path, std::ofstream::binary); + std::ifstream input(path, std::ios::binary); if(input) { input.seekg(0, std::ios::end); auto size = input.tellg(); @@ -23,7 +23,7 @@ std::string filesystem::read(const std::string &path) { //Only use on small files bool filesystem::write(const std::string &path, const std::string &new_content) { - std::ofstream output(path, std::ofstream::binary); + std::ofstream output(path, std::ios::binary); if(output) output << new_content; else diff --git a/src/json.cpp b/src/json.cpp new file mode 100644 index 0000000..42a9bbc --- /dev/null +++ b/src/json.cpp @@ -0,0 +1,101 @@ +#include "json.hpp" +#include +#include +#include +#include + +void JSON::write_json_internal(std::ostream &stream, const boost::property_tree::ptree &pt, bool pretty, const std::vector ¬_string_keys, size_t column, const std::string &key) { + // Based on boost::property_tree::json_parser::write_json_helper() + + if(pt.empty()) { // Value + auto value = pt.get_value(); + if(std::any_of(not_string_keys.begin(), not_string_keys.end(), [&key](const std::string ¬_string_key) { + return key == not_string_key; + })) + stream << value; + else + stream << '"' << escape_string(value) << '"'; + } + else if(pt.count(std::string{}) == pt.size()) { // Array + stream << '['; + if(pretty) + stream << '\n'; + + for(auto it = pt.begin(); it != pt.end(); ++it) { + if(pretty) + stream << std::string((column + 1) * 2, ' '); + + write_json_internal(stream, it->second, pretty, not_string_keys, column + 1, key); + + if(std::next(it) != pt.end()) + stream << ','; + if(pretty) + stream << '\n'; + } + + if(pretty) + stream << std::string(column * 2, ' '); + stream << ']'; + } + else { // Object + stream << '{'; + if(pretty) + stream << '\n'; + + for(auto it = pt.begin(); it != pt.end(); ++it) { + if(pretty) + stream << std::string((column + 1) * 2, ' '); + + stream << '"' << escape_string(it->first) << "\":"; + if(pretty) + stream << ' '; + write_json_internal(stream, it->second, pretty, not_string_keys, column + 1, it->first); + + if(std::next(it) != pt.end()) + stream << ','; + if(pretty) + stream << '\n'; + } + + if(pretty) + stream << std::string(column * 2, ' '); + stream << '}'; + } +} +void JSON::write(std::ostream &stream, const boost::property_tree::ptree &pt, bool pretty, const std::vector ¬_string_keys) { + write_json_internal(stream, pt, pretty, not_string_keys, 0, ""); +} + +std::string JSON::escape_string(std::string string) { + for(size_t c = 0; c < string.size(); ++c) { + if(string[c] == '\b') { + string.replace(c, 1, "\\b"); + ++c; + } + else if(string[c] == '\f') { + string.replace(c, 1, "\\f"); + ++c; + } + else if(string[c] == '\n') { + string.replace(c, 1, "\\n"); + ++c; + } + else if(string[c] == '\r') { + string.replace(c, 1, "\\r"); + ++c; + } + else if(string[c] == '\t') { + string.replace(c, 1, "\\t"); + ++c; + } + else if(string[c] == '"') { + string.replace(c, 1, "\\\""); + ++c; + } + else if(string[c] == '\\') { + string.replace(c, 1, "\\\\"); + ++c; + } + } + return string; +} diff --git a/src/json.hpp b/src/json.hpp new file mode 100644 index 0000000..41b6d6b --- /dev/null +++ b/src/json.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include + +class JSON { + static void write_json_internal(std::ostream &stream, const boost::property_tree::ptree &pt, bool pretty, const std::vector ¬_string_keys, size_t column, const std::string &key); + +public: + /// A replacement of boost::property_tree::write_json() as it does not conform to the JSON standard (https://svn.boost.org/trac10/ticket/9721). + /// Some JSON parses expects, for instance, number types instead of numbers in strings. + /// Note that boost::property_tree::write_json() will always produce string values. + /// Use not_string_keys to specify which keys that should not have string values. + /// TODO: replace boost::property_tree with another JSON library. + static void write(std::ostream &stream, const boost::property_tree::ptree &pt, bool pretty = true, const std::vector ¬_string_keys = {}); + /// A replacement of boost::property_tree::escape_text() as it does not conform to the JSON standard. + static std::string escape_string(std::string string); +}; diff --git a/src/meson.cpp b/src/meson.cpp index 22de0cb..926db34 100644 --- a/src/meson.cpp +++ b/src/meson.cpp @@ -12,7 +12,7 @@ Meson::Meson(const boost::filesystem::path &path) { const auto find_project = [](const boost::filesystem::path &file_path) { - std::ifstream input(file_path.string(), std::ofstream::binary); + std::ifstream input(file_path.string(), std::ios::binary); if(input) { std::string line; while(std::getline(input, line)) { diff --git a/src/project.cpp b/src/project.cpp index b4db0b5..27d8200 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -487,7 +487,7 @@ void Project::LLDB::debug_start(const std::string &command, const boost::filesys auto sysroot = filesystem::get_rust_sysroot_path().string(); if(!sysroot.empty()) { std::string line; - std::ifstream input(sysroot + "/lib/rustlib/etc/lldb_commands", std::ofstream::binary); + std::ifstream input(sysroot + "/lib/rustlib/etc/lldb_commands", std::ios::binary); if(input) { startup_commands.emplace_back("command script import \"" + sysroot + "/lib/rustlib/etc/lldb_lookup.py\""); while(std::getline(input, line)) diff --git a/src/project_build.cpp b/src/project_build.cpp index 6bc8f1d..2c74e63 100644 --- a/src/project_build.cpp +++ b/src/project_build.cpp @@ -152,7 +152,7 @@ bool Project::CMakeBuild::is_valid() { auto default_path = get_default_path(); if(default_path.empty()) return true; - std::ifstream input((default_path / "CMakeCache.txt").string(), std::ofstream::binary); + std::ifstream input((default_path / "CMakeCache.txt").string(), std::ios::binary); if(!input) return true; std::string line; diff --git a/src/source_base.cpp b/src/source_base.cpp index 948513d..7ef914c 100644 --- a/src/source_base.cpp +++ b/src/source_base.cpp @@ -409,7 +409,7 @@ bool Source::BaseView::load(bool not_undoable_action) { } } catch(const Glib::Error &error) { - Terminal::get().print("\e[31mError\e[m: Could not read file " + filesystem::get_short_path(file_path).string() + ": " + error.what() + '\n', true); + Terminal::get().print("\e[31mError\e[m: could not read file " + filesystem::get_short_path(file_path).string() + ": " + error.what() + '\n', true); return false; } } @@ -479,7 +479,7 @@ void Source::BaseView::replace_text(const std::string &new_text) { } } catch(...) { - Terminal::get().print("\e[31mError\e[m: Could not replace text in buffer\n", true); + Terminal::get().print("\e[31mError\e[m: could not replace text in buffer\n", true); } get_buffer()->end_user_action(); diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index b77adfa..e8a99f0 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -9,6 +9,7 @@ #include "debug_lldb.hpp" #endif #include "config.hpp" +#include "json.hpp" #include "menu.hpp" #include "utility.hpp" #include @@ -16,6 +17,7 @@ #include #include +const std::vector not_string_keys = {"line", "character", "severity", "tags", "isPreferred", "deprecated", "preselect", "insertTextFormat", "insertTextMode", "version"}; const std::string type_coverage_message = "Un-type checked code. Consider adding type annotations."; LanguageProtocol::Offset::Offset(const boost::property_tree::ptree &pt) { @@ -39,9 +41,21 @@ LanguageProtocol::Location::Location(const boost::property_tree::ptree &pt, std: file = std::move(file_); } +LanguageProtocol::Documentation::Documentation(const boost::property_tree::ptree &pt) { + value = pt.get("documentation", ""); + if(value.empty()) { + if(auto documentation_pt = pt.get_child_optional("documentation")) { + value = documentation_pt->get("value", ""); + kind = documentation_pt->get("kind", ""); + } + } + if(value == "null") // Python erroneously returns "null" when a parameter is not documented + value.clear(); +} + LanguageProtocol::Diagnostic::RelatedInformation::RelatedInformation(const boost::property_tree::ptree &pt) : message(pt.get("message")), location(pt.get_child("location")) {} -LanguageProtocol::Diagnostic::Diagnostic(const boost::property_tree::ptree &pt) : message(pt.get("message")), range(pt.get_child("range")), severity(pt.get("severity", 0)), code(pt.get("code", "")) { +LanguageProtocol::Diagnostic::Diagnostic(const boost::property_tree::ptree &pt) : message(pt.get("message")), range(pt.get_child("range")), severity(pt.get("severity", 0)), code(pt.get("code", "")), ptree(pt) { auto related_information_it = pt.get_child("relatedInformation", boost::property_tree::ptree()); for(auto it = related_information_it.begin(); it != related_information_it.end(); ++it) related_informations.emplace_back(it->second); @@ -89,32 +103,6 @@ LanguageProtocol::WorkspaceEdit::WorkspaceEdit(const boost::property_tree::ptree } } -std::string LanguageProtocol::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] == '\t') { - text.replace(c, 1, "\\t"); - ++c; - } - else if(text[c] == '"') { - text.replace(c, 1, "\\\""); - ++c; - } - else if(text[c] == '\\') { - text.replace(c, 1, "\\\\"); - ++c; - } - } - return text; -} - LanguageProtocol::Client::Client(boost::filesystem::path root_path_, std::string language_id_, const std::string &language_server) : root_path(std::move(root_path_)), language_id(std::move(language_id_)) { process = std::make_unique( filesystem::escape_argument(language_server), root_path.string(), @@ -218,7 +206,7 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang process_id = process->get_id(); } write_request( - nullptr, "initialize", "\"processId\":" + std::to_string(process_id) + ",\"rootUri\":\"" + LanguageProtocol::escape_text(filesystem::get_uri_from_path(root_path)) + R"(","capabilities": { + nullptr, "initialize", "\"processId\":" + std::to_string(process_id) + ",\"rootUri\":\"" + JSON::escape_string(filesystem::get_uri_from_path(root_path)) + R"(","capabilities": { "workspace": { "symbol": { "dynamicRegistration": false } }, @@ -286,6 +274,7 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang } capabilities.hover = capabilities_pt->get("hoverProvider", false); capabilities.completion = static_cast(capabilities_pt->get_child_optional("completionProvider")); + capabilities.completion_resolve = capabilities_pt->get("completionProvider.resolveProvider", false); capabilities.signature_help = static_cast(capabilities_pt->get_child_optional("signatureHelpProvider")); capabilities.definition = capabilities_pt->get("definitionProvider", false); capabilities.type_definition = capabilities_pt->get("typeDefinitionProvider", false); @@ -302,6 +291,7 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang capabilities.code_action = capabilities_pt->get("codeActionProvider", false); if(!capabilities.code_action) capabilities.code_action = static_cast(capabilities_pt->get_child_optional("codeActionProvider.codeActionKinds")); + capabilities.code_action_resolve = capabilities_pt->get("codeActionProvider.resolveProvider", false); capabilities.execute_command = static_cast(capabilities_pt->get_child_optional("executeCommandProvider")); capabilities.type_coverage = capabilities_pt->get("typeCoverageProvider", false); } @@ -383,7 +373,8 @@ void LanguageProtocol::Client::parse_server_message() { if(Config::get().log.language_server) { std::cout << "language server: "; - boost::property_tree::write_json(std::cout, pt); + JSON::write(std::cout, pt); + std::cout << '\n'; } auto message_id = pt.get_optional("id"); @@ -402,8 +393,10 @@ void LanguageProtocol::Client::parse_server_message() { } } else if(auto error = pt.get_child_optional("error")) { - if(!Config::get().log.language_server) - boost::property_tree::write_json(std::cerr, pt); + if(!Config::get().log.language_server) { + JSON::write(std::cerr, pt); + std::cerr << '\n'; + } if(message_id) { auto id_it = handlers.find(*message_id); if(id_it != handlers.end()) { @@ -472,7 +465,7 @@ void LanguageProtocol::Client::write_request(Source::LanguageProtocolView *view, } }); } - std::string content("{\"jsonrpc\":\"2.0\",\"id\":" + std::to_string(message_id++) + ",\"method\":\"" + method + "\",\"params\":{" + params + "}}"); + std::string content("{\"jsonrpc\":\"2.0\",\"id\":" + std::to_string(message_id++) + ",\"method\":\"" + method + "\"" + (params.empty() ? "" : ",\"params\":{" + params + '}') + '}'); if(Config::get().log.language_server) std::cout << "Language client: " << content << std::endl; if(!process->write("Content-Length: " + std::to_string(content.size()) + "\r\n\r\n" + content)) { @@ -498,7 +491,7 @@ void LanguageProtocol::Client::write_response(size_t id, const std::string &resu void LanguageProtocol::Client::write_notification(const std::string &method, const std::string ¶ms) { LockGuard lock(read_write_mutex); - std::string content("{\"jsonrpc\":\"2.0\",\"method\":\"" + method + "\",\"params\":{" + params + "}}"); + std::string content("{\"jsonrpc\":\"2.0\",\"method\":\"" + method + "\"" + (params.empty() ? "" : ",\"params\":{" + params + '}') + '}'); if(Config::get().log.language_server) std::cout << "Language client: " << content << std::endl; process->write("Content-Length: " + std::to_string(content.size()) + "\r\n\r\n" + content); @@ -531,7 +524,8 @@ void LanguageProtocol::Client::handle_server_notification(const std::string &met void LanguageProtocol::Client::handle_server_request(size_t id, const std::string &method, const boost::property_tree::ptree ¶ms) { if(method == "workspace/applyEdit") { std::promise result_processed; - dispatcher->post([&result_processed, params] { + bool applied = true; + dispatcher->post([&result_processed, &applied, params] { ScopeGuard guard({[&result_processed] { result_processed.set_value(); }}); @@ -541,6 +535,7 @@ void LanguageProtocol::Client::handle_server_request(size_t id, const std::strin workspace_edit = LanguageProtocol::WorkspaceEdit(params.get_child("edit"), current_view->file_path); } catch(...) { + applied = false; return; } @@ -559,8 +554,10 @@ void LanguageProtocol::Client::handle_server_request(size_t id, const std::strin } } if(!view) { - if(!Notebook::get().open(document_edit.file)) + if(!Notebook::get().open(document_edit.file)) { + applied = false; return; + } view = Notebook::get().get_current_view(); document_edits_and_views.emplace_back(DocumentEditAndView{&document_edit, view}); } @@ -598,18 +595,22 @@ void LanguageProtocol::Client::handle_server_request(size_t id, const std::strin } buffer->end_user_action(); - if(!view->save()) + if(!view->save()) { + applied = false; return; + } } } }); result_processed.get_future().get(); + write_response(id, std::string("\"applied\":") + (applied ? "true" : "false")); } - write_response(id, ""); + else + write_response(id, ""); // TODO: write error instead on unsupported methods } Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path &file_path, const Glib::RefPtr &language, std::string language_id_, std::string language_server_) - : Source::BaseView(file_path, language), Source::View(file_path, language), uri(filesystem::get_uri_from_path(file_path)), uri_escaped(LanguageProtocol::escape_text(uri)), language_id(std::move(language_id_)), language_server(std::move(language_server_)), client(LanguageProtocol::Client::get(file_path, language_id, language_server)) { + : Source::BaseView(file_path, language), Source::View(file_path, language), uri(filesystem::get_uri_from_path(file_path)), uri_escaped(JSON::escape_string(uri)), language_id(std::move(language_id_)), language_server(std::move(language_server_)), client(LanguageProtocol::Client::get(file_path, language_id, language_server)) { initialize(); } @@ -746,7 +747,7 @@ void Source::LanguageProtocolView::write_notification(const std::string &method) } void Source::LanguageProtocolView::write_did_open_notification() { - client->write_notification("textDocument/didOpen", "\"textDocument\":{\"uri\":\"" + uri_escaped + "\",\"version\":" + std::to_string(document_version++) + ",\"languageId\":\"" + language_id + "\",\"text\":\"" + LanguageProtocol::escape_text(get_buffer()->get_text().raw()) + "\"}"); + client->write_notification("textDocument/didOpen", "\"textDocument\":{\"uri\":\"" + uri_escaped + "\",\"version\":" + std::to_string(document_version++) + ",\"languageId\":\"" + language_id + "\",\"text\":\"" + JSON::escape_string(get_buffer()->get_text().raw()) + "\"}"); } void Source::LanguageProtocolView::write_did_change_notification(const std::vector> ¶ms) { @@ -759,7 +760,7 @@ void Source::LanguageProtocolView::rename(const boost::filesystem::path &path) { dispatcher.reset(); Source::DiffView::rename(path); uri = filesystem::get_uri_from_path(path); - uri_escaped = LanguageProtocol::escape_text(uri); + uri_escaped = JSON::escape_string(uri); client = LanguageProtocol::Client::get(file_path, language_id, language_server); initialize(); } @@ -1277,111 +1278,105 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { return; auto edit_pt = results[index].second.get_child_optional("edit"); - if(!edit_pt) { - - if(capabilities.execute_command) { - auto command_pt = results[index].second.get_child_optional("command"); - if(command_pt) { - std::stringstream ss; - boost::property_tree::write_json(ss, *command_pt, false); - // ss.str() is enclosed in {}, and has ending newline - auto str = ss.str(); - if(str.size() <= 3) - return; - write_request("workspace/executeCommand", str.substr(1, str.size() - 3), [](const boost::property_tree::ptree &result, bool error) { - }); - return; - } + if(capabilities.code_action_resolve && !edit_pt) { + std::promise result_processed; + std::stringstream ss; + for(auto it = results[index].second.begin(); it != results[index].second.end(); ++it) { + ss << (it != results[index].second.begin() ? ",\"" : "\"") << JSON::escape_string(it->first) << "\":"; + JSON::write(ss, it->second, false, not_string_keys); } - - // TODO: disabled since rust-analyzer does not yet support numbers inside "" (boost::property_tree::write_json outputs all values with "") - // std::promise result_processed; - // std::stringstream ss; - // boost::property_tree::write_json(ss, results[index].second, false); - // // ss.str() is enclosed in {}, and has ending newline - // auto str = ss.str(); - // if(str.size() <= 3) - // return; - // write_request("codeAction/resolve", str.substr(1, str.size() - 3), [&result_processed, &edit_pt](const boost::property_tree::ptree &result, bool error) { - // if(!error) { - // auto child = result.get_child_optional("edit"); - // if(child) - // edit_pt = *child; // Make copy, since result will be destroyed at end of callback - // } - // result_processed.set_value(); - // }); - // result_processed.get_future().get(); - - if(!edit_pt) - return; + write_request("codeAction/resolve", ss.str(), [&result_processed, &edit_pt](const boost::property_tree::ptree &result, bool error) { + if(!error) { + auto child = result.get_child_optional("edit"); + if(child) + edit_pt = *child; // Make copy, since result will be destroyed at end of callback + } + result_processed.set_value(); + }); + result_processed.get_future().get(); } - LanguageProtocol::WorkspaceEdit workspace_edit; - try { - workspace_edit = LanguageProtocol::WorkspaceEdit(*edit_pt, file_path); - } - catch(...) { - return; - } + if(edit_pt) { + LanguageProtocol::WorkspaceEdit workspace_edit; + try { + workspace_edit = LanguageProtocol::WorkspaceEdit(*edit_pt, file_path); + } + catch(...) { + return; + } - auto current_view = Notebook::get().get_current_view(); + auto current_view = Notebook::get().get_current_view(); - struct DocumentEditAndView { - LanguageProtocol::TextDocumentEdit *document_edit; - Source::View *view; - }; - std::vector document_edits_and_views; + struct DocumentEditAndView { + LanguageProtocol::TextDocumentEdit *document_edit; + Source::View *view; + }; + std::vector document_edits_and_views; - for(auto &document_edit : workspace_edit.document_edits) { - Source::View *view = nullptr; - for(auto it = views.begin(); it != views.end(); ++it) { - if((*it)->file_path == document_edit.file) { - view = *it; - break; + for(auto &document_edit : workspace_edit.document_edits) { + Source::View *view = nullptr; + for(auto it = views.begin(); it != views.end(); ++it) { + if((*it)->file_path == document_edit.file) { + view = *it; + break; + } } + if(!view) { + if(!Notebook::get().open(document_edit.file)) + return; + view = Notebook::get().get_current_view(); + document_edits_and_views.emplace_back(DocumentEditAndView{&document_edit, view}); + } + else + document_edits_and_views.emplace_back(DocumentEditAndView{&document_edit, view}); } - if(!view) { - if(!Notebook::get().open(document_edit.file)) - return; - view = Notebook::get().get_current_view(); - document_edits_and_views.emplace_back(DocumentEditAndView{&document_edit, view}); - } - else - document_edits_and_views.emplace_back(DocumentEditAndView{&document_edit, view}); - } - if(current_view) - Notebook::get().open(current_view); + if(current_view) + Notebook::get().open(current_view); - for(auto &document_edit_and_view : document_edits_and_views) { - auto document_edit = document_edit_and_view.document_edit; - auto view = document_edit_and_view.view; - auto buffer = view->get_buffer(); - buffer->begin_user_action(); + for(auto &document_edit_and_view : document_edits_and_views) { + auto document_edit = document_edit_and_view.document_edit; + auto view = document_edit_and_view.view; + auto buffer = view->get_buffer(); + buffer->begin_user_action(); - auto end_iter = buffer->end(); - // If entire buffer is replaced - if(document_edit->text_edits.size() == 1 && - document_edit->text_edits[0].range.start.line == 0 && document_edit->text_edits[0].range.start.character == 0 && - (document_edit->text_edits[0].range.end.line > end_iter.get_line() || - (document_edit->text_edits[0].range.end.line == end_iter.get_line() && document_edit->text_edits[0].range.end.character >= get_line_pos(end_iter)))) { - view->replace_text(document_edit->text_edits[0].new_text); - } - else { - for(auto text_edit_it = document_edit->text_edits.rbegin(); text_edit_it != document_edit->text_edits.rend(); ++text_edit_it) { - auto start_iter = view->get_iter_at_line_pos(text_edit_it->range.start.line, text_edit_it->range.start.character); - auto end_iter = view->get_iter_at_line_pos(text_edit_it->range.end.line, text_edit_it->range.end.character); - if(view != current_view) - view->get_buffer()->place_cursor(start_iter); - buffer->erase(start_iter, end_iter); - start_iter = view->get_iter_at_line_pos(text_edit_it->range.start.line, text_edit_it->range.start.character); - buffer->insert(start_iter, text_edit_it->new_text); + auto end_iter = buffer->end(); + // If entire buffer is replaced + if(document_edit->text_edits.size() == 1 && + document_edit->text_edits[0].range.start.line == 0 && document_edit->text_edits[0].range.start.character == 0 && + (document_edit->text_edits[0].range.end.line > end_iter.get_line() || + (document_edit->text_edits[0].range.end.line == end_iter.get_line() && document_edit->text_edits[0].range.end.character >= get_line_pos(end_iter)))) { + view->replace_text(document_edit->text_edits[0].new_text); + } + else { + for(auto text_edit_it = document_edit->text_edits.rbegin(); text_edit_it != document_edit->text_edits.rend(); ++text_edit_it) { + auto start_iter = view->get_iter_at_line_pos(text_edit_it->range.start.line, text_edit_it->range.start.character); + auto end_iter = view->get_iter_at_line_pos(text_edit_it->range.end.line, text_edit_it->range.end.character); + if(view != current_view) + view->get_buffer()->place_cursor(start_iter); + buffer->erase(start_iter, end_iter); + start_iter = view->get_iter_at_line_pos(text_edit_it->range.start.line, text_edit_it->range.start.character); + buffer->insert(start_iter, text_edit_it->new_text); + } } + + buffer->end_user_action(); + if(!view->save()) + return; } + } - buffer->end_user_action(); - if(!view->save()) - return; + if(capabilities.execute_command) { + auto command_pt = results[index].second.get_child_optional("command"); + if(command_pt) { + std::stringstream ss; + for(auto it = command_pt->begin(); it != command_pt->end(); ++it) { + ss << (it != command_pt->begin() ? ",\"" : "\"") << JSON::escape_string(it->first) << "\":"; + JSON::write(ss, it->second, false, not_string_keys); + } + write_request("workspace/executeCommand", ss.str(), [](const boost::property_tree::ptree &result, bool error) { + }); + } } }; hide_tooltips(); @@ -1396,7 +1391,7 @@ void Source::LanguageProtocolView::setup_signals() { get_buffer()->signal_insert().connect( [this](const Gtk::TextIter &start, const Glib::ustring &text, int bytes) { std::pair location = {start.get_line(), get_line_pos(start)}; - write_did_change_notification({{"contentChanges", "[{" + to_string({make_range(location, location), {"text", '"' + LanguageProtocol::escape_text(text.raw()) + '"'}}) + "}]"}}); + write_did_change_notification({{"contentChanges", "[{" + to_string({make_range(location, location), {"text", '"' + JSON::escape_string(text.raw()) + '"'}}) + "}]"}}); }, false); @@ -1408,7 +1403,7 @@ void Source::LanguageProtocolView::setup_signals() { } else if(capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::full) { get_buffer()->signal_changed().connect([this]() { - write_did_change_notification({{"contentChanges", "[{" + to_string({"text", '"' + LanguageProtocol::escape_text(get_buffer()->get_text().raw()) + '"'}) + "}]"}}); + write_did_change_notification({{"contentChanges", "[{" + to_string({"text", '"' + JSON::escape_string(get_buffer()->get_text().raw()) + '"'}) + "}]"}}); }); } } @@ -1652,20 +1647,9 @@ void Source::LanguageProtocolView::setup_autocomplete() { if(parameter_position == current_parameter_position || using_named_parameters) { auto label = parameter_it->second.get("label", ""); auto insert = label; - auto documentation = parameter_it->second.get("documentation", ""); - std::string kind; - if(documentation.empty()) { - auto documentation_pt = parameter_it->second.get_child_optional("documentation"); - if(documentation_pt) { - documentation = documentation_pt->get("value", ""); - kind = documentation_pt->get("kind", ""); - } - } - if(documentation == "null") // Python erroneously returns "null" when a parameter is not documented - documentation.clear(); if(!using_named_parameters || used_named_parameters.find(insert) == used_named_parameters.end()) { autocomplete->rows.emplace_back(std::move(label)); - autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), {}, std::move(documentation), std::move(kind), {}, {}}); + autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), {}, LanguageProtocol::Documentation(parameter_it->second), {}, {}}); } } parameter_position++; @@ -1710,17 +1694,10 @@ void Source::LanguageProtocolView::setup_autocomplete() { auto label = it->second.get("label", ""); if(starts_with(label, prefix)) { auto detail = it->second.get("detail", ""); - auto documentation = it->second.get("documentation", ""); - std::string documentation_kind; - if(documentation.empty()) { - if(auto documentation_pt = it->second.get_child_optional("documentation")) { - documentation = documentation_pt->get("value", ""); - documentation_kind = documentation_pt->get("kind", ""); - } - } + LanguageProtocol::Documentation documentation(it->second); boost::property_tree::ptree ptree; - if(detail.empty() && documentation.empty() && (is_incomplete || is_js)) // Workaround for typescript-language-server (is_js) + if(detail.empty() && documentation.value.empty() && (is_incomplete || is_js)) // Workaround for typescript-language-server (is_js) ptree = it->second; std::vector additional_text_edits; @@ -1745,7 +1722,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { if(kind >= 2 && kind <= 4 && insert.find('(') == std::string::npos) // If kind is method, function or constructor, but parentheses are missing insert += "(${1:})"; autocomplete->rows.emplace_back(std::move(label)); - autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), std::move(detail), std::move(documentation), std::move(documentation_kind), std::move(ptree), std::move(additional_text_edits)}); + autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), std::move(detail), std::move(documentation), std::move(ptree), std::move(additional_text_edits)}); } } } @@ -1756,7 +1733,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { for(auto &snippet : *snippets) { if(starts_with(snippet.prefix, prefix)) { autocomplete->rows.emplace_back(snippet.prefix); - autocomplete_rows.emplace_back(AutocompleteRow{snippet.body, {}, snippet.description, {}, {}, {}}); + autocomplete_rows.emplace_back(AutocompleteRow{snippet.body, {}, LanguageProtocol::Documentation(snippet.description), {}, {}}); } } } @@ -1843,53 +1820,42 @@ void Source::LanguageProtocolView::setup_autocomplete() { autocomplete->set_tooltip_buffer = [this](unsigned int index) -> std::function { auto autocomplete = autocomplete_rows[index]; - if(autocomplete.detail.empty() && autocomplete.documentation.empty() && !autocomplete.ptree.empty()) { - try { - std::stringstream ss; - boost::property_tree::write_json(ss, autocomplete.ptree, false); - // ss.str() is enclosed in {}, and has ending newline - auto str = ss.str(); - if(str.size() <= 3) - return nullptr; - std::promise result_processed; - write_request("completionItem/resolve", str.substr(1, str.size() - 3), [&result_processed, &autocomplete](const boost::property_tree::ptree &result, bool error) { - if(!error) { - autocomplete.detail = result.get("detail", ""); - autocomplete.documentation = result.get("documentation", ""); - if(autocomplete.documentation.empty()) { - if(auto documentation = result.get_child_optional("documentation")) { - autocomplete.kind = documentation->get("kind", ""); - autocomplete.documentation = documentation->get("value", ""); - } - } - } - result_processed.set_value(); - }); - result_processed.get_future().get(); - } - catch(...) { + if(capabilities.completion_resolve && autocomplete.detail.empty() && autocomplete.documentation.value.empty() && !autocomplete.ptree.empty()) { + std::stringstream ss; + for(auto it = autocomplete.ptree.begin(); it != autocomplete.ptree.end(); ++it) { + ss << (it != autocomplete.ptree.begin() ? ",\"" : "\"") << JSON::escape_string(it->first) << "\":"; + JSON::write(ss, it->second, false, not_string_keys); } + std::promise result_processed; + write_request("completionItem/resolve", ss.str(), [&result_processed, &autocomplete](const boost::property_tree::ptree &result, bool error) { + if(!error) { + autocomplete.detail = result.get("detail", ""); + autocomplete.documentation = LanguageProtocol::Documentation(result); + } + result_processed.set_value(); + }); + result_processed.get_future().get(); } - if(autocomplete.detail.empty() && autocomplete.documentation.empty()) + if(autocomplete.detail.empty() && autocomplete.documentation.value.empty()) return nullptr; return [this, autocomplete = std::move(autocomplete)](Tooltip &tooltip) mutable { if(language_id == "python") // Python might support markdown in the future - tooltip.insert_docstring(autocomplete.documentation); + tooltip.insert_docstring(autocomplete.documentation.value); else { if(!autocomplete.detail.empty()) { tooltip.insert_code(autocomplete.detail, language); tooltip.remove_trailing_newlines(); } - if(!autocomplete.documentation.empty()) { + if(!autocomplete.documentation.value.empty()) { if(tooltip.buffer->size() > 0) tooltip.buffer->insert_at_cursor("\n\n"); - if(autocomplete.kind == "plaintext" || autocomplete.kind.empty()) - tooltip.insert_with_links_tagged(autocomplete.documentation); - else if(autocomplete.kind == "markdown") - tooltip.insert_markdown(autocomplete.documentation); + if(autocomplete.documentation.kind == "plaintext" || autocomplete.documentation.kind.empty()) + tooltip.insert_with_links_tagged(autocomplete.documentation.value); + else if(autocomplete.documentation.kind == "markdown") + tooltip.insert_markdown(autocomplete.documentation.value); else - tooltip.insert_code(autocomplete.documentation, autocomplete.kind); + tooltip.insert_code(autocomplete.documentation.value, autocomplete.documentation.kind); } } }; @@ -1903,24 +1869,21 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vector range; - std::string diagnostics_string; - for(auto &diagnostic : diagnostics) { - range = make_range({diagnostic.range.start.line, diagnostic.range.start.character}, {diagnostic.range.end.line, diagnostic.range.end.character}); - std::vector> diagnostic_params = {range}; - diagnostic_params.emplace_back("message", '"' + LanguageProtocol::escape_text(diagnostic.message) + '"'); - if(diagnostic.severity != 0) - diagnostic_params.emplace_back("severity", std::to_string(diagnostic.severity)); - if(!diagnostic.code.empty()) - diagnostic_params.emplace_back("code", '"' + diagnostic.code + '"'); - diagnostics_string += (diagnostics_string.empty() ? "{" : ",{") + to_string(diagnostic_params) + '}'; + std::stringstream diagnostics_ss; + for(auto it = diagnostics.begin(); it != diagnostics.end(); ++it) { + if(it != diagnostics.begin()) + diagnostics_ss << ","; + JSON::write(diagnostics_ss, it->ptree, false, not_string_keys); } - if(diagnostics.size() != 1) { // Use diagnostic range if only one diagnostic, otherwise use whole buffer + std::pair range; + if(diagnostics.size() == 1) // Use diagnostic range if only one diagnostic, otherwise use whole buffer + range = make_range({diagnostics[0].range.start.line, diagnostics[0].range.start.character}, {diagnostics[0].range.end.line, diagnostics[0].range.end.character}); + else { auto start = get_buffer()->begin(); auto end = get_buffer()->end(); range = make_range({start.get_line(), get_line_pos(start)}, {end.get_line(), get_line_pos(end)}); } - std::vector> params = {range, {"context", '{' + to_string({{"diagnostics", '[' + diagnostics_string + ']'}, {"only", "[\"quickfix\"]"}}) + '}'}}; + std::vector> params = {range, {"context", '{' + to_string({{"diagnostics", '[' + diagnostics_ss.str() + ']'}, {"only", "[\"quickfix\"]"}}) + '}'}}; thread_pool.push([this, diagnostics = std::move(diagnostics), params = std::move(params), last_count]() mutable { if(last_count != update_diagnostics_async_count) return; diff --git a/src/source_language_protocol.hpp b/src/source_language_protocol.hpp index 722af3b..135190b 100644 --- a/src/source_language_protocol.hpp +++ b/src/source_language_protocol.hpp @@ -57,6 +57,14 @@ namespace LanguageProtocol { } }; + class Documentation { + public: + Documentation(const boost::property_tree::ptree &pt); + Documentation(std::string value) : value(std::move(value)) {} + std::string value; + std::string kind; + }; + class Diagnostic { public: class RelatedInformation { @@ -74,6 +82,7 @@ namespace LanguageProtocol { std::string code; std::vector related_informations; std::map> quickfixes; + boost::property_tree::ptree ptree; }; class TextEdit { @@ -107,6 +116,7 @@ namespace LanguageProtocol { TextDocumentSync text_document_sync = TextDocumentSync::none; bool hover = false; bool completion = false; + bool completion_resolve = false; bool signature_help = false; bool definition = false; bool type_definition = false; @@ -119,13 +129,12 @@ namespace LanguageProtocol { bool document_range_formatting = false; bool rename = false; bool code_action = false; + bool code_action_resolve = false; bool execute_command = false; bool type_coverage = false; bool use_line_index = false; }; - std::string escape_text(std::string text); - class Client { Client(boost::filesystem::path root_path, std::string language_id, const std::string &language_server); boost::filesystem::path root_path; @@ -253,8 +262,7 @@ namespace Source { struct AutocompleteRow { std::string insert; std::string detail; - std::string documentation; - std::string kind; + LanguageProtocol::Documentation documentation; /// CompletionItem for completionItem/resolve boost::property_tree::ptree ptree; std::vector additional_text_edits; diff --git a/src/usages_clang.cpp b/src/usages_clang.cpp index 11150d3..c48e2e4 100644 --- a/src/usages_clang.cpp +++ b/src/usages_clang.cpp @@ -729,7 +729,7 @@ void Usages::Clang::write_cache(const boost::filesystem::path &path, const Clang return; tmp_file /= ("jucipp" + std::to_string(get_current_process_id()) + path_str); - std::ofstream stream(tmp_file.string()); + std::ofstream stream(tmp_file.string(), std::ios::binary); if(stream) { try { boost::archive::text_oarchive text_oarchive(stream); diff --git a/src/window.cpp b/src/window.cpp index 562e0be..03b64b1 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -11,6 +11,7 @@ #include "filesystem.hpp" #include "grep.hpp" #include "info.hpp" +#include "json.hpp" #include "menu.hpp" #include "notebook.hpp" #include "project.hpp" @@ -253,7 +254,7 @@ void Window::configure() { if(scheme) style = scheme->get_style("def:note"); else { - Terminal::get().print("\e[31mError\e[m: Could not find gtksourceview style: " + Config::get().source.style + '\n', true); + Terminal::get().print("\e[31mError\e[m: 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(); @@ -385,7 +386,7 @@ void Window::set_menu_actions() { Terminal::get().print(" \e[32mcreated\e[m\n"); } else - Terminal::get().print("\e[31mError\e[m: Could not create project " + filesystem::get_short_path(project_path).string() + "\n", true); + Terminal::get().print("\e[31mError\e[m: could not create project " + filesystem::get_short_path(project_path).string() + "\n", true); } }); menu.add_action("file_new_project_cpp", []() { @@ -442,7 +443,7 @@ void Window::set_menu_actions() { Terminal::get().print(" \e[32mcreated\e[m\n"); } else - Terminal::get().print("\e[31mError\e[m: Could not create project " + filesystem::get_short_path(project_path).string() + "\n", true); + Terminal::get().print("\e[31mError\e[m: could not create project " + filesystem::get_short_path(project_path).string() + "\n", true); } }); menu.add_action("file_new_project_rust", []() { @@ -471,7 +472,7 @@ void Window::set_menu_actions() { Terminal::get().print(" \e[32mcreated\e[m\n"); } else - Terminal::get().print("\e[31mError\e[m: Could not create project " + filesystem::get_short_path(project_path).string() + "\n", true); + Terminal::get().print("\e[31mError\e[m: could not create project " + filesystem::get_short_path(project_path).string() + "\n", true); } }); @@ -527,7 +528,7 @@ void Window::set_menu_actions() { if(auto view = Notebook::get().get_current_view()) { auto path = Dialog::save_file_as(view->file_path); if(!path.empty()) { - std::ofstream file(path, std::ofstream::binary); + std::ofstream file(path, std::ios::binary); if(file) { file << view->get_buffer()->get_text().raw(); file.close(); @@ -2318,7 +2319,14 @@ void Window::save_session() { 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); + auto path = Config::get().home_juci_path / "last_session.json"; + std::ofstream output(path.string(), std::ios::binary); + if(output) { + JSON::write(output, root_pt); + output << '\n'; + } + else + Terminal::get().print("\e[31mError\e[m: could not write session file: " + filesystem::get_short_path(path).string() + "\n", true); } catch(...) { } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 59a3867..08fcbf8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -90,7 +90,11 @@ if(BUILD_TESTING) add_test(language_protocol_client_test language_protocol_client_test) add_executable(language_protocol_server_test language_protocol_server_test.cpp) - target_link_libraries(language_protocol_server_test Boost::filesystem) + target_link_libraries(language_protocol_server_test juci_shared) + + add_executable(json_test json_test.cpp $) + target_link_libraries(json_test juci_shared) + add_test(json_test json_test) endif() if(BUILD_FUZZING) diff --git a/tests/json_test.cpp b/tests/json_test.cpp new file mode 100644 index 0000000..92057ef --- /dev/null +++ b/tests/json_test.cpp @@ -0,0 +1,58 @@ +#include "json.hpp" +#include +#include +#include + +int main() { + std::string json = R"({ + "integer": 3, + "integer_as_string": "3", + "string": "some\ntext", + "string2": "1test", + "array": [ + 1, + 3, + 3.14 + ], + "array_with_strings": [ + "a", + "b", + "c" + ], + "object": { + "integer": 3, + "string": "some\ntext", + "array": [ + 1, + 3, + 3.14 + ] + } +})"; + { + std::istringstream istream(json); + + boost::property_tree::ptree pt; + boost::property_tree::read_json(istream, pt); + + std::ostringstream ostream; + JSON::write(ostream, pt, true, {"integer", "array"}); + g_assert(ostream.str() == json); + } + { + std::istringstream istream(json); + + boost::property_tree::ptree pt; + boost::property_tree::read_json(istream, pt); + + std::ostringstream ostream; + JSON::write(ostream, pt, false, {"integer", "array"}); + + std::string non_pretty; + for(auto &chr : json) { + if(chr != ' ' && chr != '\n') + non_pretty += chr; + } + g_assert(ostream.str() == non_pretty); + } +} diff --git a/tests/language_protocol_server_test.cpp b/tests/language_protocol_server_test.cpp index b3535d2..ef3172d 100644 --- a/tests/language_protocol_server_test.cpp +++ b/tests/language_protocol_server_test.cpp @@ -1,5 +1,5 @@ +#include "json.hpp" #include -#include #include #ifdef _WIN32 @@ -7,32 +7,6 @@ #include #endif -std::string 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] == '\t') { - text.replace(c, 1, "\\t"); - ++c; - } - else if(text[c] == '"') { - text.replace(c, 1, "\\\""); - ++c; - } - else if(text[c] == '\\') { - text.replace(c, 1, "\\\\"); - ++c; - } - } - return text; -} - int main() { #ifdef _WIN32 _setmode(_fileno(stdout), _O_BINARY); @@ -472,7 +446,7 @@ int main() { "id": "5", "result": [ { - "uri": "file://)" + escape_text(file_path.string()) + + "uri": "file://)" + JSON::escape_string(file_path.string()) + R"(", "range": { "start": { @@ -486,7 +460,7 @@ int main() { } }, { - "uri": "file://)" + escape_text(file_path.string()) + + "uri": "file://)" + JSON::escape_string(file_path.string()) + R"(", "range": { "start": { From 3abc46c17b77daddfdcf4ec24e24745b2a0fd708 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 22 Jun 2021 21:10:27 +0200 Subject: [PATCH 153/375] Language client: always add params object when sending notifications (gopls requires this) --- src/source_language_protocol.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index e8a99f0..36b9c40 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -491,7 +491,7 @@ void LanguageProtocol::Client::write_response(size_t id, const std::string &resu void LanguageProtocol::Client::write_notification(const std::string &method, const std::string ¶ms) { LockGuard lock(read_write_mutex); - std::string content("{\"jsonrpc\":\"2.0\",\"method\":\"" + method + "\"" + (params.empty() ? "" : ",\"params\":{" + params + '}') + '}'); + std::string content("{\"jsonrpc\":\"2.0\",\"method\":\"" + method + "\",\"params\":{" + params + "}}"); if(Config::get().log.language_server) std::cout << "Language client: " << content << std::endl; process->write("Content-Length: " + std::to_string(content.size()) + "\r\n\r\n" + content); From 0cefe148fa5f9520e27cbda2d2276bab8459c9d8 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 25 Jun 2021 13:03:45 +0200 Subject: [PATCH 154/375] Made use of the nlohmann/json library due to lacking JSON capabilities in Boost::PropertyTree --- CMakeLists.txt | 1 + README.md | 2 + lib/json/.clang-format | 84 + lib/json/LICENSE.MIT | 21 + lib/json/include/nlohmann/adl_serializer.hpp | 73 + .../nlohmann/byte_container_with_subtype.hpp | 166 + .../nlohmann/detail/conversions/from_json.hpp | 453 + .../nlohmann/detail/conversions/to_chars.hpp | 1103 ++ .../nlohmann/detail/conversions/to_json.hpp | 382 + .../include/nlohmann/detail/exceptions.hpp | 421 + lib/json/include/nlohmann/detail/hash.hpp | 121 + .../nlohmann/detail/input/binary_reader.hpp | 2461 +++++ .../nlohmann/detail/input/input_adapters.hpp | 476 + .../nlohmann/detail/input/json_sax.hpp | 711 ++ .../include/nlohmann/detail/input/lexer.hpp | 1623 +++ .../include/nlohmann/detail/input/parser.hpp | 492 + .../nlohmann/detail/input/position_t.hpp | 27 + .../detail/iterators/internal_iterator.hpp | 25 + .../nlohmann/detail/iterators/iter_impl.hpp | 646 ++ .../detail/iterators/iteration_proxy.hpp | 181 + .../detail/iterators/iterator_traits.hpp | 51 + .../iterators/json_reverse_iterator.hpp | 119 + .../detail/iterators/primitive_iterator.hpp | 123 + .../include/nlohmann/detail/json_pointer.hpp | 934 ++ lib/json/include/nlohmann/detail/json_ref.hpp | 68 + .../include/nlohmann/detail/macro_scope.hpp | 302 + .../include/nlohmann/detail/macro_unscope.hpp | 23 + .../nlohmann/detail/meta/cpp_future.hpp | 154 + .../include/nlohmann/detail/meta/detected.hpp | 58 + .../nlohmann/detail/meta/identity_tag.hpp | 10 + .../include/nlohmann/detail/meta/is_sax.hpp | 149 + .../nlohmann/detail/meta/type_traits.hpp | 436 + .../include/nlohmann/detail/meta/void_t.hpp | 13 + .../nlohmann/detail/output/binary_writer.hpp | 1594 +++ .../detail/output/output_adapters.hpp | 129 + .../nlohmann/detail/output/serializer.hpp | 954 ++ .../include/nlohmann/detail/string_escape.hpp | 63 + lib/json/include/nlohmann/detail/value_t.hpp | 81 + lib/json/include/nlohmann/json.hpp | 8942 +++++++++++++++++ lib/json/include/nlohmann/json_fwd.hpp | 78 + lib/json/include/nlohmann/ordered_map.hpp | 190 + .../nlohmann/thirdparty/hedley/hedley.hpp | 2044 ++++ .../thirdparty/hedley/hedley_undef.hpp | 150 + src/commands.cpp | 15 +- src/compile_commands.cpp | 19 +- src/config.cpp | 260 +- src/config.hpp | 13 +- src/json.cpp | 520 +- src/json.hpp | 127 +- src/juci.cpp | 2 +- src/meson.cpp | 19 +- src/project_build.cpp | 7 +- src/snippets.cpp | 15 +- src/source.cpp | 8 +- src/source_language_protocol.cpp | 614 +- src/source_language_protocol.hpp | 49 +- src/window.cpp | 136 +- src/window.hpp | 2 +- tests/json_test.cpp | 295 +- tests/language_protocol_server_test.cpp | 332 +- 60 files changed, 27727 insertions(+), 840 deletions(-) create mode 100644 lib/json/.clang-format create mode 100644 lib/json/LICENSE.MIT create mode 100644 lib/json/include/nlohmann/adl_serializer.hpp create mode 100644 lib/json/include/nlohmann/byte_container_with_subtype.hpp create mode 100644 lib/json/include/nlohmann/detail/conversions/from_json.hpp create mode 100644 lib/json/include/nlohmann/detail/conversions/to_chars.hpp create mode 100644 lib/json/include/nlohmann/detail/conversions/to_json.hpp create mode 100644 lib/json/include/nlohmann/detail/exceptions.hpp create mode 100644 lib/json/include/nlohmann/detail/hash.hpp create mode 100644 lib/json/include/nlohmann/detail/input/binary_reader.hpp create mode 100644 lib/json/include/nlohmann/detail/input/input_adapters.hpp create mode 100644 lib/json/include/nlohmann/detail/input/json_sax.hpp create mode 100644 lib/json/include/nlohmann/detail/input/lexer.hpp create mode 100644 lib/json/include/nlohmann/detail/input/parser.hpp create mode 100644 lib/json/include/nlohmann/detail/input/position_t.hpp create mode 100644 lib/json/include/nlohmann/detail/iterators/internal_iterator.hpp create mode 100644 lib/json/include/nlohmann/detail/iterators/iter_impl.hpp create mode 100644 lib/json/include/nlohmann/detail/iterators/iteration_proxy.hpp create mode 100644 lib/json/include/nlohmann/detail/iterators/iterator_traits.hpp create mode 100644 lib/json/include/nlohmann/detail/iterators/json_reverse_iterator.hpp create mode 100644 lib/json/include/nlohmann/detail/iterators/primitive_iterator.hpp create mode 100644 lib/json/include/nlohmann/detail/json_pointer.hpp create mode 100644 lib/json/include/nlohmann/detail/json_ref.hpp create mode 100644 lib/json/include/nlohmann/detail/macro_scope.hpp create mode 100644 lib/json/include/nlohmann/detail/macro_unscope.hpp create mode 100644 lib/json/include/nlohmann/detail/meta/cpp_future.hpp create mode 100644 lib/json/include/nlohmann/detail/meta/detected.hpp create mode 100644 lib/json/include/nlohmann/detail/meta/identity_tag.hpp create mode 100644 lib/json/include/nlohmann/detail/meta/is_sax.hpp create mode 100644 lib/json/include/nlohmann/detail/meta/type_traits.hpp create mode 100644 lib/json/include/nlohmann/detail/meta/void_t.hpp create mode 100644 lib/json/include/nlohmann/detail/output/binary_writer.hpp create mode 100644 lib/json/include/nlohmann/detail/output/output_adapters.hpp create mode 100644 lib/json/include/nlohmann/detail/output/serializer.hpp create mode 100644 lib/json/include/nlohmann/detail/string_escape.hpp create mode 100644 lib/json/include/nlohmann/detail/value_t.hpp create mode 100644 lib/json/include/nlohmann/json.hpp create mode 100644 lib/json/include/nlohmann/json_fwd.hpp create mode 100644 lib/json/include/nlohmann/ordered_map.hpp create mode 100644 lib/json/include/nlohmann/thirdparty/hedley/hedley.hpp create mode 100644 lib/json/include/nlohmann/thirdparty/hedley/hedley_undef.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 782d6af..3fc22d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,6 +135,7 @@ include_directories( ${LIBCLANG_INCLUDE_DIRS} ${ASPELL_INCLUDE_DIR} ${LIBGIT2_INCLUDE_DIRS} + ${CMAKE_SOURCE_DIR}/lib/json/include ) add_subdirectory("src") diff --git a/README.md b/README.md index 7a42658..b007e99 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,8 @@ See [custom styling](docs/custom_styling.md). need to install) - [tiny-process-library](http://gitlab.com/eidheim/tiny-process-library/) (downloaded directly with git --recursive, no need to install) +- [JSON for Modern C++](https://github.com/nlohmann/json) (included in repository, no need to + install) ## Documentation diff --git a/lib/json/.clang-format b/lib/json/.clang-format new file mode 100644 index 0000000..5b9e3fd --- /dev/null +++ b/lib/json/.clang-format @@ -0,0 +1,84 @@ +#AccessModifierOffset: 2 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +#AlignConsecutiveBitFields: false +AlignConsecutiveDeclarations: false +AlignConsecutiveMacros: false +AlignEscapedNewlines: Right +#AlignOperands: AlignAfterOperator +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: false +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: false +#AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Empty +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: false +BinPackParameters: false +#BitFieldColonSpacing: Both +BreakBeforeBraces: Custom # or Allman +BraceWrapping: + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: Always + AfterEnum: true + AfterFunction: true + AfterNamespace: false + AfterStruct: true + AfterUnion: true + AfterExternBlock: false + BeforeCatch: true + BeforeElse: true + #BeforeLambdaBody: false + #BeforeWhile: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakStringLiterals: false +ColumnLimit: 0 +CompactNamespaces: false +ConstructorInitializerIndentWidth: 2 +Cpp11BracedListStyle: true +PointerAlignment: Left +FixNamespaceComments: true +IncludeBlocks: Preserve +#IndentCaseBlocks: false +IndentCaseLabels: true +IndentGotoLabels: false +IndentPPDirectives: BeforeHash +IndentWidth: 4 +KeepEmptyLinesAtTheStartOfBlocks: false +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ReflowComments: false +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: c++11 +TabWidth: 4 +UseTab: Never diff --git a/lib/json/LICENSE.MIT b/lib/json/LICENSE.MIT new file mode 100644 index 0000000..f0622d6 --- /dev/null +++ b/lib/json/LICENSE.MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2013-2021 Niels Lohmann + +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. diff --git a/lib/json/include/nlohmann/adl_serializer.hpp b/lib/json/include/nlohmann/adl_serializer.hpp new file mode 100644 index 0000000..f967612 --- /dev/null +++ b/lib/json/include/nlohmann/adl_serializer.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace nlohmann +{ + +template +struct adl_serializer +{ + /*! + @brief convert a JSON value to any value type + + This function is usually called by the `get()` function of the + @ref basic_json class (either explicit or via conversion operators). + + @note This function is chosen for default-constructible value types. + + @param[in] j JSON value to read from + @param[in,out] val value to write to + */ + template + static auto from_json(BasicJsonType && j, TargetType& val) noexcept( + noexcept(::nlohmann::from_json(std::forward(j), val))) + -> decltype(::nlohmann::from_json(std::forward(j), val), void()) + { + ::nlohmann::from_json(std::forward(j), val); + } + + /*! + @brief convert a JSON value to any value type + + This function is usually called by the `get()` function of the + @ref basic_json class (either explicit or via conversion operators). + + @note This function is chosen for value types which are not default-constructible. + + @param[in] j JSON value to read from + + @return copy of the JSON value, converted to @a ValueType + */ + template + static auto from_json(BasicJsonType && j) noexcept( + noexcept(::nlohmann::from_json(std::forward(j), detail::identity_tag {}))) + -> decltype(::nlohmann::from_json(std::forward(j), detail::identity_tag {})) + { + return ::nlohmann::from_json(std::forward(j), detail::identity_tag {}); + } + + /*! + @brief convert any value type to a JSON value + + This function is usually called by the constructors of the @ref basic_json + class. + + @param[in,out] j JSON value to write to + @param[in] val value to read from + */ + template + static auto to_json(BasicJsonType& j, TargetType && val) noexcept( + noexcept(::nlohmann::to_json(j, std::forward(val)))) + -> decltype(::nlohmann::to_json(j, std::forward(val)), void()) + { + ::nlohmann::to_json(j, std::forward(val)); + } +}; +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/byte_container_with_subtype.hpp b/lib/json/include/nlohmann/byte_container_with_subtype.hpp new file mode 100644 index 0000000..df68395 --- /dev/null +++ b/lib/json/include/nlohmann/byte_container_with_subtype.hpp @@ -0,0 +1,166 @@ +#pragma once + +#include // uint8_t +#include // tie +#include // move + +namespace nlohmann +{ + +/*! +@brief an internal type for a backed binary type + +This type extends the template parameter @a BinaryType provided to `basic_json` +with a subtype used by BSON and MessagePack. This type exists so that the user +does not have to specify a type themselves with a specific naming scheme in +order to override the binary type. + +@tparam BinaryType container to store bytes (`std::vector` by + default) + +@since version 3.8.0 +*/ +template +class byte_container_with_subtype : public BinaryType +{ + public: + /// the type of the underlying container + using container_type = BinaryType; + + byte_container_with_subtype() noexcept(noexcept(container_type())) + : container_type() + {} + + byte_container_with_subtype(const container_type& b) noexcept(noexcept(container_type(b))) + : container_type(b) + {} + + byte_container_with_subtype(container_type&& b) noexcept(noexcept(container_type(std::move(b)))) + : container_type(std::move(b)) + {} + + byte_container_with_subtype(const container_type& b, std::uint8_t subtype_) noexcept(noexcept(container_type(b))) + : container_type(b) + , m_subtype(subtype_) + , m_has_subtype(true) + {} + + byte_container_with_subtype(container_type&& b, std::uint8_t subtype_) noexcept(noexcept(container_type(std::move(b)))) + : container_type(std::move(b)) + , m_subtype(subtype_) + , m_has_subtype(true) + {} + + bool operator==(const byte_container_with_subtype& rhs) const + { + return std::tie(static_cast(*this), m_subtype, m_has_subtype) == + std::tie(static_cast(rhs), rhs.m_subtype, rhs.m_has_subtype); + } + + bool operator!=(const byte_container_with_subtype& rhs) const + { + return !(rhs == *this); + } + + /*! + @brief sets the binary subtype + + Sets the binary subtype of the value, also flags a binary JSON value as + having a subtype, which has implications for serialization. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @sa see @ref subtype() -- return the binary subtype + @sa see @ref clear_subtype() -- clears the binary subtype + @sa see @ref has_subtype() -- returns whether or not the binary value has a + subtype + + @since version 3.8.0 + */ + void set_subtype(std::uint8_t subtype_) noexcept + { + m_subtype = subtype_; + m_has_subtype = true; + } + + /*! + @brief return the binary subtype + + Returns the numerical subtype of the value if it has a subtype. If it does + not have a subtype, this function will return size_t(-1) as a sentinel + value. + + @return the numerical subtype of the binary value + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @sa see @ref set_subtype() -- sets the binary subtype + @sa see @ref clear_subtype() -- clears the binary subtype + @sa see @ref has_subtype() -- returns whether or not the binary value has a + subtype + + @since version 3.8.0 + */ + constexpr std::uint8_t subtype() const noexcept + { + return m_subtype; + } + + /*! + @brief return whether the value has a subtype + + @return whether the value has a subtype + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @sa see @ref subtype() -- return the binary subtype + @sa see @ref set_subtype() -- sets the binary subtype + @sa see @ref clear_subtype() -- clears the binary subtype + + @since version 3.8.0 + */ + constexpr bool has_subtype() const noexcept + { + return m_has_subtype; + } + + /*! + @brief clears the binary subtype + + Clears the binary subtype and flags the value as not having a subtype, which + has implications for serialization; for instance MessagePack will prefer the + bin family over the ext family. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @sa see @ref subtype() -- return the binary subtype + @sa see @ref set_subtype() -- sets the binary subtype + @sa see @ref has_subtype() -- returns whether or not the binary value has a + subtype + + @since version 3.8.0 + */ + void clear_subtype() noexcept + { + m_subtype = 0; + m_has_subtype = false; + } + + private: + std::uint8_t m_subtype = 0; + bool m_has_subtype = false; +}; + +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/conversions/from_json.hpp b/lib/json/include/nlohmann/detail/conversions/from_json.hpp new file mode 100644 index 0000000..4e4efd0 --- /dev/null +++ b/lib/json/include/nlohmann/detail/conversions/from_json.hpp @@ -0,0 +1,453 @@ +#pragma once + +#include // transform +#include // array +#include // forward_list +#include // inserter, front_inserter, end +#include // map +#include // string +#include // tuple, make_tuple +#include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible +#include // unordered_map +#include // pair, declval +#include // valarray + +#include +#include +#include +#include +#include +#include + +namespace nlohmann +{ +namespace detail +{ +template +void from_json(const BasicJsonType& j, typename std::nullptr_t& n) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_null())) + { + JSON_THROW(type_error::create(302, "type must be null, but is " + std::string(j.type_name()), j)); + } + n = nullptr; +} + +// overloads for basic_json template parameters +template < typename BasicJsonType, typename ArithmeticType, + enable_if_t < std::is_arithmetic::value&& + !std::is_same::value, + int > = 0 > +void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) + { + case value_t::number_unsigned: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_integer: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_float: + { + val = static_cast(*j.template get_ptr()); + break; + } + + default: + JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()), j)); + } +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) + { + JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name()), j)); + } + b = *j.template get_ptr(); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_string())) + { + JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()), j)); + } + s = *j.template get_ptr(); +} + +template < + typename BasicJsonType, typename ConstructibleStringType, + enable_if_t < + is_constructible_string_type::value&& + !std::is_same::value, + int > = 0 > +void from_json(const BasicJsonType& j, ConstructibleStringType& s) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_string())) + { + JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()), j)); + } + + s = *j.template get_ptr(); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) +{ + get_arithmetic_value(j, val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) +{ + get_arithmetic_value(j, val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) +{ + get_arithmetic_value(j, val); +} + +template::value, int> = 0> +void from_json(const BasicJsonType& j, EnumType& e) +{ + typename std::underlying_type::type val; + get_arithmetic_value(j, val); + e = static_cast(val); +} + +// forward_list doesn't have an insert method +template::value, int> = 0> +void from_json(const BasicJsonType& j, std::forward_list& l) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()), j)); + } + l.clear(); + std::transform(j.rbegin(), j.rend(), + std::front_inserter(l), [](const BasicJsonType & i) + { + return i.template get(); + }); +} + +// valarray doesn't have an insert method +template::value, int> = 0> +void from_json(const BasicJsonType& j, std::valarray& l) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()), j)); + } + l.resize(j.size()); + std::transform(j.begin(), j.end(), std::begin(l), + [](const BasicJsonType & elem) + { + return elem.template get(); + }); +} + +template +auto from_json(const BasicJsonType& j, T (&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +-> decltype(j.template get(), void()) +{ + for (std::size_t i = 0; i < N; ++i) + { + arr[i] = j.at(i).template get(); + } +} + +template +void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) +{ + arr = *j.template get_ptr(); +} + +template +auto from_json_array_impl(const BasicJsonType& j, std::array& arr, + priority_tag<2> /*unused*/) +-> decltype(j.template get(), void()) +{ + for (std::size_t i = 0; i < N; ++i) + { + arr[i] = j.at(i).template get(); + } +} + +template::value, + int> = 0> +auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<1> /*unused*/) +-> decltype( + arr.reserve(std::declval()), + j.template get(), + void()) +{ + using std::end; + + ConstructibleArrayType ret; + ret.reserve(j.size()); + std::transform(j.begin(), j.end(), + std::inserter(ret, end(ret)), [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get(); + }); + arr = std::move(ret); +} + +template::value, + int> = 0> +void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, + priority_tag<0> /*unused*/) +{ + using std::end; + + ConstructibleArrayType ret; + std::transform( + j.begin(), j.end(), std::inserter(ret, end(ret)), + [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get(); + }); + arr = std::move(ret); +} + +template < typename BasicJsonType, typename ConstructibleArrayType, + enable_if_t < + is_constructible_array_type::value&& + !is_constructible_object_type::value&& + !is_constructible_string_type::value&& + !std::is_same::value&& + !is_basic_json::value, + int > = 0 > +auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr) +-> decltype(from_json_array_impl(j, arr, priority_tag<3> {}), +j.template get(), +void()) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()), j)); + } + + from_json_array_impl(j, arr, priority_tag<3> {}); +} + +template < typename BasicJsonType, typename T, std::size_t... Idx > +std::array from_json_inplace_array_impl(BasicJsonType&& j, + identity_tag> /*unused*/, index_sequence /*unused*/) +{ + return { { std::forward(j).at(Idx).template get()... } }; +} + +template < typename BasicJsonType, typename T, std::size_t N > +auto from_json(BasicJsonType&& j, identity_tag> tag) +-> decltype(from_json_inplace_array_impl(std::forward(j), tag, make_index_sequence {})) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()), j)); + } + + return from_json_inplace_array_impl(std::forward(j), tag, make_index_sequence {}); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) + { + JSON_THROW(type_error::create(302, "type must be binary, but is " + std::string(j.type_name()), j)); + } + + bin = *j.template get_ptr(); +} + +template::value, int> = 0> +void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_object())) + { + JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name()), j)); + } + + ConstructibleObjectType ret; + const auto* inner_object = j.template get_ptr(); + using value_type = typename ConstructibleObjectType::value_type; + std::transform( + inner_object->begin(), inner_object->end(), + std::inserter(ret, ret.begin()), + [](typename BasicJsonType::object_t::value_type const & p) + { + return value_type(p.first, p.second.template get()); + }); + obj = std::move(ret); +} + +// overload for arithmetic types, not chosen for basic_json template arguments +// (BooleanType, etc..); note: Is it really necessary to provide explicit +// overloads for boolean_t etc. in case of a custom BooleanType which is not +// an arithmetic type? +template < typename BasicJsonType, typename ArithmeticType, + enable_if_t < + std::is_arithmetic::value&& + !std::is_same::value&& + !std::is_same::value&& + !std::is_same::value&& + !std::is_same::value, + int > = 0 > +void from_json(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) + { + case value_t::number_unsigned: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_integer: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_float: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::boolean: + { + val = static_cast(*j.template get_ptr()); + break; + } + + default: + JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()), j)); + } +} + +template +std::tuple from_json_tuple_impl_base(BasicJsonType&& j, index_sequence /*unused*/) +{ + return std::make_tuple(std::forward(j).at(Idx).template get()...); +} + +template < typename BasicJsonType, class A1, class A2 > +std::pair from_json_tuple_impl(BasicJsonType&& j, identity_tag> /*unused*/, priority_tag<0> /*unused*/) +{ + return {std::forward(j).at(0).template get(), + std::forward(j).at(1).template get()}; +} + +template +void from_json_tuple_impl(BasicJsonType&& j, std::pair& p, priority_tag<1> /*unused*/) +{ + p = from_json_tuple_impl(std::forward(j), identity_tag> {}, priority_tag<0> {}); +} + +template +std::tuple from_json_tuple_impl(BasicJsonType&& j, identity_tag> /*unused*/, priority_tag<2> /*unused*/) +{ + return from_json_tuple_impl_base(std::forward(j), index_sequence_for {}); +} + +template +void from_json_tuple_impl(BasicJsonType&& j, std::tuple& t, priority_tag<3> /*unused*/) +{ + t = from_json_tuple_impl_base(std::forward(j), index_sequence_for {}); +} + +template +auto from_json(BasicJsonType&& j, TupleRelated&& t) +-> decltype(from_json_tuple_impl(std::forward(j), std::forward(t), priority_tag<3> {})) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()), j)); + } + + return from_json_tuple_impl(std::forward(j), std::forward(t), priority_tag<3> {}); +} + +template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator, + typename = enable_if_t < !std::is_constructible < + typename BasicJsonType::string_t, Key >::value >> +void from_json(const BasicJsonType& j, std::map& m) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()), j)); + } + m.clear(); + for (const auto& p : j) + { + if (JSON_HEDLEY_UNLIKELY(!p.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name()), j)); + } + m.emplace(p.at(0).template get(), p.at(1).template get()); + } +} + +template < typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator, + typename = enable_if_t < !std::is_constructible < + typename BasicJsonType::string_t, Key >::value >> +void from_json(const BasicJsonType& j, std::unordered_map& m) +{ + if (JSON_HEDLEY_UNLIKELY(!j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()), j)); + } + m.clear(); + for (const auto& p : j) + { + if (JSON_HEDLEY_UNLIKELY(!p.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name()), j)); + } + m.emplace(p.at(0).template get(), p.at(1).template get()); + } +} + +struct from_json_fn +{ + template + auto operator()(const BasicJsonType& j, T&& val) const + noexcept(noexcept(from_json(j, std::forward(val)))) + -> decltype(from_json(j, std::forward(val))) + { + return from_json(j, std::forward(val)); + } +}; +} // namespace detail + +/// namespace to hold default `from_json` function +/// to see why this is required: +/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html +namespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces) +{ +constexpr const auto& from_json = detail::static_const::value; // NOLINT(misc-definitions-in-headers) +} // namespace +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/conversions/to_chars.hpp b/lib/json/include/nlohmann/detail/conversions/to_chars.hpp new file mode 100644 index 0000000..e904d10 --- /dev/null +++ b/lib/json/include/nlohmann/detail/conversions/to_chars.hpp @@ -0,0 +1,1103 @@ +#pragma once + +#include // array +#include // signbit, isfinite +#include // intN_t, uintN_t +#include // memcpy, memmove +#include // numeric_limits +#include // conditional + +#include + +namespace nlohmann +{ +namespace detail +{ + +/*! +@brief implements the Grisu2 algorithm for binary to decimal floating-point +conversion. + +This implementation is a slightly modified version of the reference +implementation which may be obtained from +http://florian.loitsch.com/publications (bench.tar.gz). + +The code is distributed under the MIT license, Copyright (c) 2009 Florian Loitsch. + +For a detailed description of the algorithm see: + +[1] Loitsch, "Printing Floating-Point Numbers Quickly and Accurately with + Integers", Proceedings of the ACM SIGPLAN 2010 Conference on Programming + Language Design and Implementation, PLDI 2010 +[2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and Accurately", + Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language + Design and Implementation, PLDI 1996 +*/ +namespace dtoa_impl +{ + +template +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 // f * 2^e +{ + static constexpr int kPrecision = 64; // = q + + std::uint64_t f = 0; + int e = 0; + + constexpr diyfp(std::uint64_t f_, int e_) noexcept : f(f_), e(e_) {} + + /*! + @brief returns x - y + @pre x.e == y.e and x.f >= y.f + */ + static diyfp sub(const diyfp& x, const diyfp& y) noexcept + { + JSON_ASSERT(x.e == y.e); + JSON_ASSERT(x.f >= y.f); + + return {x.f - y.f, x.e}; + } + + /*! + @brief returns x * y + @note The result is rounded. (Only the upper q bits are returned.) + */ + static diyfp mul(const diyfp& x, const diyfp& y) noexcept + { + static_assert(kPrecision == 64, "internal error"); + + // Computes: + // f = round((x.f * y.f) / 2^q) + // e = x.e + y.e + q + + // Emulate the 64-bit * 64-bit multiplication: + // + // p = u * v + // = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi) + // = (u_lo v_lo ) + 2^32 ((u_lo v_hi ) + (u_hi v_lo )) + 2^64 (u_hi v_hi ) + // = (p0 ) + 2^32 ((p1 ) + (p2 )) + 2^64 (p3 ) + // = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3 ) + // = (p0_lo ) + 2^32 (p0_hi + p1_lo + p2_lo ) + 2^64 (p1_hi + p2_hi + p3) + // = (p0_lo ) + 2^32 (Q ) + 2^64 (H ) + // = (p0_lo ) + 2^32 (Q_lo + 2^32 Q_hi ) + 2^64 (H ) + // + // (Since Q might be larger than 2^32 - 1) + // + // = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H) + // + // (Q_hi + H does not overflow a 64-bit int) + // + // = p_lo + 2^64 p_hi + + const std::uint64_t u_lo = x.f & 0xFFFFFFFFu; + const std::uint64_t u_hi = x.f >> 32u; + const std::uint64_t v_lo = y.f & 0xFFFFFFFFu; + const std::uint64_t v_hi = y.f >> 32u; + + const std::uint64_t p0 = u_lo * v_lo; + const std::uint64_t p1 = u_lo * v_hi; + const std::uint64_t p2 = u_hi * v_lo; + const std::uint64_t p3 = u_hi * v_hi; + + const std::uint64_t p0_hi = p0 >> 32u; + const std::uint64_t p1_lo = p1 & 0xFFFFFFFFu; + const std::uint64_t p1_hi = p1 >> 32u; + const std::uint64_t p2_lo = p2 & 0xFFFFFFFFu; + const std::uint64_t p2_hi = p2 >> 32u; + + std::uint64_t Q = p0_hi + p1_lo + p2_lo; + + // The full product might now be computed as + // + // p_hi = p3 + p2_hi + p1_hi + (Q >> 32) + // p_lo = p0_lo + (Q << 32) + // + // But in this particular case here, the full p_lo is not required. + // Effectively we only need to add the highest bit in p_lo to p_hi (and + // Q_hi + 1 does not overflow). + + Q += std::uint64_t{1} << (64u - 32u - 1u); // round, ties up + + const std::uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32u); + + return {h, x.e + y.e + 64}; + } + + /*! + @brief normalize x such that the significand is >= 2^(q-1) + @pre x.f != 0 + */ + static diyfp normalize(diyfp x) noexcept + { + JSON_ASSERT(x.f != 0); + + while ((x.f >> 63u) == 0) + { + x.f <<= 1u; + x.e--; + } + + return x; + } + + /*! + @brief normalize x such that the result has the exponent E + @pre e >= x.e and the upper e - x.e bits of x.f must be zero. + */ + static diyfp normalize_to(const diyfp& x, const int target_exponent) noexcept + { + const int delta = x.e - target_exponent; + + JSON_ASSERT(delta >= 0); + JSON_ASSERT(((x.f << delta) >> delta) == x.f); + + return {x.f << delta, target_exponent}; + } +}; + +struct boundaries +{ + diyfp w; + diyfp minus; + diyfp plus; +}; + +/*! +Compute the (normalized) diyfp representing the input number 'value' and its +boundaries. + +@pre value must be finite and positive +*/ +template +boundaries compute_boundaries(FloatType value) +{ + JSON_ASSERT(std::isfinite(value)); + JSON_ASSERT(value > 0); + + // Convert the IEEE representation into a diyfp. + // + // If v is denormal: + // value = 0.F * 2^(1 - bias) = ( F) * 2^(1 - bias - (p-1)) + // If v is normalized: + // value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1)) + + static_assert(std::numeric_limits::is_iec559, + "internal error: dtoa_short requires an IEEE-754 floating-point implementation"); + + constexpr int kPrecision = std::numeric_limits::digits; // = p (includes the hidden bit) + constexpr int kBias = std::numeric_limits::max_exponent - 1 + (kPrecision - 1); + constexpr int kMinExp = 1 - kBias; + constexpr std::uint64_t kHiddenBit = std::uint64_t{1} << (kPrecision - 1); // = 2^(p-1) + + using bits_type = typename std::conditional::type; + + const auto bits = static_cast(reinterpret_bits(value)); + const std::uint64_t E = bits >> (kPrecision - 1); + const std::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(E) - kBias); + + // Compute the boundaries m- and m+ of the floating-point value + // v = f * 2^e. + // + // Determine v- and v+, the floating-point predecessor and successor if v, + // respectively. + // + // v- = v - 2^e if f != 2^(p-1) or e == e_min (A) + // = v - 2^(e-1) if f == 2^(p-1) and e > e_min (B) + // + // v+ = v + 2^e + // + // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_ + // between m- and m+ round to v, regardless of how the input rounding + // algorithm breaks ties. + // + // ---+-------------+-------------+-------------+-------------+--- (A) + // v- m- v m+ v+ + // + // -----------------+------+------+-------------+-------------+--- (B) + // v- m- v m+ v+ + + const bool lower_boundary_is_closer = F == 0 && 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) // (B) + : diyfp(2 * v.f - 1, v.e - 1); // (A) + + // Determine the normalized w+ = m+. + const diyfp w_plus = diyfp::normalize(m_plus); + + // Determine w- = m- such that e_(w-) = e_(w+). + const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e); + + return {diyfp::normalize(v), w_minus, w_plus}; +} + +// Given normalized diyfp w, Grisu needs to find a (normalized) cached +// power-of-ten c, such that the exponent of the product c * w = f * 2^e lies +// within a certain range [alpha, gamma] (Definition 3.2 from [1]) +// +// alpha <= e = e_c + e_w + q <= gamma +// +// or +// +// f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q +// <= f_c * f_w * 2^gamma +// +// Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies +// +// 2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma +// +// or +// +// 2^(q - 2 + alpha) <= c * w < 2^(q + gamma) +// +// The choice of (alpha,gamma) determines the size of the table and the form of +// the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well +// in practice: +// +// The idea is to cut the number c * w = f * 2^e into two parts, which can be +// processed independently: An integral part p1, and a fractional part p2: +// +// f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e +// = (f div 2^-e) + (f mod 2^-e) * 2^e +// = p1 + p2 * 2^e +// +// The conversion of p1 into decimal form requires a series of divisions and +// modulos by (a power of) 10. These operations are faster for 32-bit than for +// 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be +// achieved by choosing +// +// -e >= 32 or e <= -32 := gamma +// +// In order to convert the fractional part +// +// p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ... +// +// into decimal form, the fraction is repeatedly multiplied by 10 and the digits +// d[-i] are extracted in order: +// +// (10 * p2) div 2^-e = d[-1] +// (10 * p2) mod 2^-e = d[-2] / 10^1 + ... +// +// The multiplication by 10 must not overflow. It is sufficient to choose +// +// 10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64. +// +// Since p2 = f mod 2^-e < 2^-e, +// +// -e <= 60 or e >= -60 := alpha + +constexpr int kAlpha = -60; +constexpr int kGamma = -32; + +struct cached_power // c = f * 2^e ~= 10^k +{ + std::uint64_t f; + int e; + int k; +}; + +/*! +For a normalized diyfp w = f * 2^e, this function returns a (normalized) cached +power-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c +satisfies (Definition 3.2 from [1]) + + alpha <= e_c + e + q <= gamma. +*/ +inline cached_power get_cached_power_for_binary_exponent(int e) +{ + // Now + // + // alpha <= e_c + e + q <= gamma (1) + // ==> f_c * 2^alpha <= c * 2^e * 2^q + // + // and since the c's are normalized, 2^(q-1) <= f_c, + // + // ==> 2^(q - 1 + alpha) <= c * 2^(e + q) + // ==> 2^(alpha - e - 1) <= c + // + // If c were an exact power of ten, i.e. c = 10^k, one may determine k as + // + // k = ceil( log_10( 2^(alpha - e - 1) ) ) + // = ceil( (alpha - e - 1) * log_10(2) ) + // + // From the paper: + // "In theory the result of the procedure could be wrong since c is rounded, + // and the computation itself is approximated [...]. In practice, however, + // this simple function is sufficient." + // + // For IEEE double precision floating-point numbers converted into + // normalized diyfp's w = f * 2^e, with q = 64, + // + // e >= -1022 (min IEEE exponent) + // -52 (p - 1) + // -52 (p - 1, possibly normalize denormal IEEE numbers) + // -11 (normalize the diyfp) + // = -1137 + // + // and + // + // e <= +1023 (max IEEE exponent) + // -52 (p - 1) + // -11 (normalize the diyfp) + // = 960 + // + // This binary exponent range [-1137,960] results in a decimal exponent + // range [-307,324]. One does not need to store a cached power for each + // k in this range. For each such k it suffices to find a cached power + // such that the exponent of the product lies in [alpha,gamma]. + // This implies that the difference of the decimal exponents of adjacent + // table entries must be less than or equal to + // + // floor( (gamma - alpha) * log_10(2) ) = 8. + // + // (A smaller distance gamma-alpha would require a larger table.) + + // NB: + // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34. + + constexpr int kCachedPowersMinDecExp = -300; + constexpr int kCachedPowersDecStep = 8; + + static constexpr std::array 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 }, + } + }; + + // This computation gives exactly the same results for k as + // k = ceil((kAlpha - e - 1) * 0.30102999566398114) + // for |e| <= 1500, but doesn't require floating-point operations. + // NB: log_10(2) ~= 78913 / 2^18 + JSON_ASSERT(e >= -1500); + JSON_ASSERT(e <= 1500); + const int f = kAlpha - e - 1; + const int k = (f * 78913) / (1 << 18) + static_cast(f > 0); + + const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep; + JSON_ASSERT(index >= 0); + JSON_ASSERT(static_cast(index) < kCachedPowers.size()); + + const cached_power cached = kCachedPowers[static_cast(index)]; + JSON_ASSERT(kAlpha <= cached.e + e + 64); + JSON_ASSERT(kGamma >= cached.e + e + 64); + + return cached; +} + +/*! +For n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k. +For n == 0, returns 1 and sets pow10 := 1. +*/ +inline int find_largest_pow10(const std::uint32_t n, std::uint32_t& pow10) +{ + // LCOV_EXCL_START + if (n >= 1000000000) + { + pow10 = 1000000000; + return 10; + } + // LCOV_EXCL_STOP + if (n >= 100000000) + { + pow10 = 100000000; + return 9; + } + if (n >= 10000000) + { + pow10 = 10000000; + return 8; + } + if (n >= 1000000) + { + pow10 = 1000000; + return 7; + } + if (n >= 100000) + { + pow10 = 100000; + return 6; + } + if (n >= 10000) + { + pow10 = 10000; + return 5; + } + if (n >= 1000) + { + pow10 = 1000; + return 4; + } + if (n >= 100) + { + pow10 = 100; + return 3; + } + if (n >= 10) + { + pow10 = 10; + return 2; + } + + pow10 = 1; + return 1; +} + +inline void grisu2_round(char* buf, int len, std::uint64_t dist, std::uint64_t delta, + std::uint64_t rest, std::uint64_t ten_k) +{ + JSON_ASSERT(len >= 1); + JSON_ASSERT(dist <= delta); + JSON_ASSERT(rest <= delta); + JSON_ASSERT(ten_k > 0); + + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // ten_k + // <------> + // <---- rest ----> + // --------------[------------------+----+--------------]-------------- + // w V + // = buf * 10^k + // + // ten_k represents a unit-in-the-last-place in the decimal representation + // stored in buf. + // Decrement buf by ten_k while this takes buf closer to w. + + // The tests are written in this order to avoid overflow in unsigned + // integer arithmetic. + + while (rest < dist + && delta - rest >= ten_k + && (rest + ten_k < dist || dist - rest > rest + ten_k - dist)) + { + JSON_ASSERT(buf[len - 1] != '0'); + buf[len - 1]--; + rest += ten_k; + } +} + +/*! +Generates V = buffer * 10^decimal_exponent, such that M- <= V <= M+. +M- and M+ must be normalized and share the same exponent -60 <= e <= -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"); + + // Generates the digits (and the exponent) of a decimal floating-point + // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's + // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= gamma. + // + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // Grisu2 generates the digits of M+ from left to right and stops as soon as + // V is in [M-,M+]. + + JSON_ASSERT(M_plus.e >= kAlpha); + JSON_ASSERT(M_plus.e <= kGamma); + + std::uint64_t delta = diyfp::sub(M_plus, M_minus).f; // (significand of (M+ - M-), implicit exponent is e) + std::uint64_t dist = diyfp::sub(M_plus, w ).f; // (significand of (M+ - w ), implicit exponent is e) + + // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0): + // + // M+ = f * 2^e + // = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e + // = ((p1 ) * 2^-e + (p2 )) * 2^e + // = p1 + p2 * 2^e + + const diyfp one(std::uint64_t{1} << -M_plus.e, M_plus.e); + + auto p1 = static_cast(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.) + std::uint64_t p2 = M_plus.f & (one.f - 1); // p2 = f mod 2^-e + + // 1) + // + // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0] + + JSON_ASSERT(p1 > 0); + + std::uint32_t pow10{}; + const int k = find_largest_pow10(p1, pow10); + + // 10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1) + // + // p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1)) + // = (d[k-1] ) * 10^(k-1) + (p1 mod 10^(k-1)) + // + // M+ = p1 + p2 * 2^e + // = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1)) + p2 * 2^e + // = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e + // = d[k-1] * 10^(k-1) + ( rest) * 2^e + // + // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0) + // + // p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0] + // + // but stop as soon as + // + // rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e + + int n = k; + while (n > 0) + { + // Invariants: + // M+ = buffer * 10^n + (p1 + p2 * 2^e) (buffer = 0 for n = k) + // pow10 = 10^(n-1) <= p1 < 10^n + // + const std::uint32_t d = p1 / pow10; // d = p1 div 10^(n-1) + const std::uint32_t r = p1 % pow10; // r = p1 mod 10^(n-1) + // + // M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e + // = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e) + // + JSON_ASSERT(d <= 9); + buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(n-1) + (r + p2 * 2^e) + // + p1 = r; + n--; + // + // M+ = buffer * 10^n + (p1 + p2 * 2^e) + // pow10 = 10^n + // + + // Now check if enough digits have been generated. + // Compute + // + // p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e + // + // Note: + // Since rest and delta share the same exponent e, it suffices to + // compare the significands. + const std::uint64_t rest = (std::uint64_t{p1} << -one.e) + p2; + if (rest <= delta) + { + // V = buffer * 10^n, with M- <= V <= M+. + + decimal_exponent += n; + + // We may now just stop. But instead look if the buffer could be + // decremented to bring V closer to w. + // + // pow10 = 10^n is now 1 ulp in the decimal representation V. + // The rounding procedure works with diyfp's with an implicit + // exponent of e. + // + // 10^n = (10^n * 2^-e) * 2^e = ulp * 2^e + // + const std::uint64_t ten_n = std::uint64_t{pow10} << -one.e; + grisu2_round(buffer, length, dist, delta, rest, ten_n); + + return; + } + + pow10 /= 10; + // + // pow10 = 10^(n-1) <= p1 < 10^n + // Invariants restored. + } + + // 2) + // + // The digits of the integral part have been generated: + // + // M+ = d[k-1]...d[1]d[0] + p2 * 2^e + // = buffer + p2 * 2^e + // + // Now generate the digits of the fractional part p2 * 2^e. + // + // Note: + // No decimal point is generated: the exponent is adjusted instead. + // + // p2 actually represents the fraction + // + // p2 * 2^e + // = p2 / 2^-e + // = d[-1] / 10^1 + d[-2] / 10^2 + ... + // + // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...) + // + // p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m + // + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...) + // + // using + // + // 10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e) + // = ( d) * 2^-e + ( r) + // + // or + // 10^m * p2 * 2^e = d + r * 2^e + // + // i.e. + // + // M+ = buffer + p2 * 2^e + // = buffer + 10^-m * (d + r * 2^e) + // = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e + // + // and stop as soon as 10^-m * r * 2^e <= delta * 2^e + + JSON_ASSERT(p2 > delta); + + int m = 0; + for (;;) + { + // Invariant: + // M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) * 2^e + // = buffer * 10^-m + 10^-m * (p2 ) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * (10 * p2) ) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + (10*p2 mod 2^-e)) * 2^e + // + JSON_ASSERT(p2 <= (std::numeric_limits::max)() / 10); + p2 *= 10; + const std::uint64_t d = p2 >> -one.e; // d = (10 * p2) div 2^-e + const std::uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e + // + // M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e)) + // = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + JSON_ASSERT(d <= 9); + buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + p2 = r; + m++; + // + // M+ = buffer * 10^-m + 10^-m * p2 * 2^e + // Invariant restored. + + // Check if enough digits have been generated. + // + // 10^-m * p2 * 2^e <= delta * 2^e + // p2 * 2^e <= 10^m * delta * 2^e + // p2 <= 10^m * delta + delta *= 10; + dist *= 10; + if (p2 <= delta) + { + break; + } + } + + // V = buffer * 10^-m, with M- <= V <= M+. + + decimal_exponent -= m; + + // 1 ulp in the decimal representation is now 10^-m. + // Since delta and dist are now scaled by 10^m, we need to do the + // same with ulp in order to keep the units in sync. + // + // 10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e + // + const std::uint64_t ten_m = one.f; + grisu2_round(buffer, length, dist, delta, p2, ten_m); + + // By construction this algorithm generates the shortest possible decimal + // number (Loitsch, Theorem 6.2) which rounds back to w. + // For an input number of precision p, at least + // + // N = 1 + ceil(p * log_10(2)) + // + // decimal digits are sufficient to identify all binary floating-point + // numbers (Matula, "In-and-Out conversions"). + // This implies that the algorithm does not produce more than N decimal + // digits. + // + // N = 17 for p = 53 (IEEE double precision) + // N = 9 for p = 24 (IEEE single precision) +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +JSON_HEDLEY_NON_NULL(1) +inline void grisu2(char* buf, int& len, int& decimal_exponent, + diyfp m_minus, diyfp v, diyfp m_plus) +{ + JSON_ASSERT(m_plus.e == m_minus.e); + JSON_ASSERT(m_plus.e == v.e); + + // --------(-----------------------+-----------------------)-------- (A) + // m- v m+ + // + // --------------------(-----------+-----------------------)-------- (B) + // m- v m+ + // + // First scale v (and m- and m+) such that the exponent is in the range + // [alpha, gamma]. + + const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e); + + const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k + + // The exponent of the products is = v.e + c_minus_k.e + q and is in the range [alpha,gamma] + 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); + + // ----(---+---)---------------(---+---)---------------(---+---)---- + // w- w w+ + // = c*m- = c*v = c*m+ + // + // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and + // w+ are now off by a small amount. + // In fact: + // + // w - v * 10^k < 1 ulp + // + // To account for this inaccuracy, add resp. subtract 1 ulp. + // + // --------+---[---------------(---+---)---------------]---+-------- + // w- M- w M+ w+ + // + // Now any number in [M-, M+] (bounds included) will round to w when input, + // regardless of how the input rounding algorithm breaks ties. + // + // And digit_gen generates the shortest possible such number in [M-, M+]. + // Note that this does not mean that Grisu2 always generates the shortest + // possible number in the interval (m-, m+). + 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; // = -(-k) = k + + grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus); +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +template +JSON_HEDLEY_NON_NULL(1) +void grisu2(char* buf, int& len, int& decimal_exponent, FloatType value) +{ + static_assert(diyfp::kPrecision >= std::numeric_limits::digits + 3, + "internal error: not enough precision"); + + JSON_ASSERT(std::isfinite(value)); + JSON_ASSERT(value > 0); + + // If the neighbors (and boundaries) of 'value' are always computed for double-precision + // numbers, all float's can be recovered using strtod (and strtof). However, the resulting + // decimal representations are not exactly "short". + // + // The documentation for 'std::to_chars' (https://en.cppreference.com/w/cpp/utility/to_chars) + // says "value is converted to a string as if by std::sprintf in the default ("C") locale" + // and since sprintf promotes float's to double's, I think this is exactly what 'std::to_chars' + // does. + // On the other hand, the documentation for 'std::to_chars' requires that "parsing the + // representation using the corresponding std::from_chars function recovers value exactly". That + // indicates that single precision floating-point numbers should be recovered using + // 'std::strtof'. + // + // NB: If the neighbors are computed for single-precision numbers, there is a single float + // (7.0385307e-26f) which can't be recovered using strtod. The resulting double precision + // value is off by 1 ulp. +#if 0 + const boundaries w = compute_boundaries(static_cast(value)); +#else + const boundaries w = compute_boundaries(value); +#endif + + grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus); +} + +/*! +@brief appends a decimal representation of e to buf +@return a pointer to the element following the exponent. +@pre -1000 < e < 1000 +*/ +JSON_HEDLEY_NON_NULL(1) +JSON_HEDLEY_RETURNS_NON_NULL +inline char* append_exponent(char* buf, int e) +{ + JSON_ASSERT(e > -1000); + JSON_ASSERT(e < 1000); + + if (e < 0) + { + e = -e; + *buf++ = '-'; + } + else + { + *buf++ = '+'; + } + + auto k = static_cast(e); + if (k < 10) + { + // Always print at least two digits in the exponent. + // This is for compatibility with printf("%g"). + *buf++ = '0'; + *buf++ = static_cast('0' + k); + } + else if (k < 100) + { + *buf++ = static_cast('0' + k / 10); + k %= 10; + *buf++ = static_cast('0' + k); + } + else + { + *buf++ = static_cast('0' + k / 100); + k %= 100; + *buf++ = static_cast('0' + k / 10); + k %= 10; + *buf++ = static_cast('0' + k); + } + + return buf; +} + +/*! +@brief prettify v = buf * 10^decimal_exponent + +If v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point +notation. Otherwise it will be printed in exponential notation. + +@pre min_exp < 0 +@pre max_exp > 0 +*/ +JSON_HEDLEY_NON_NULL(1) +JSON_HEDLEY_RETURNS_NON_NULL +inline char* format_buffer(char* buf, int len, int decimal_exponent, + int min_exp, int max_exp) +{ + JSON_ASSERT(min_exp < 0); + JSON_ASSERT(max_exp > 0); + + const int k = len; + const int n = len + decimal_exponent; + + // v = buf * 10^(n-k) + // k is the length of the buffer (number of decimal digits) + // n is the position of the decimal point relative to the start of the buffer. + + if (k <= n && n <= max_exp) + { + // digits[000] + // len <= max_exp + 2 + + std::memset(buf + k, '0', static_cast(n) - static_cast(k)); + // Make it look like a floating-point number (#362, #378) + buf[n + 0] = '.'; + buf[n + 1] = '0'; + return buf + (static_cast(n) + 2); + } + + if (0 < n && n <= max_exp) + { + // dig.its + // len <= max_digits10 + 1 + + JSON_ASSERT(k > n); + + std::memmove(buf + (static_cast(n) + 1), buf + n, static_cast(k) - static_cast(n)); + buf[n] = '.'; + return buf + (static_cast(k) + 1U); + } + + if (min_exp < n && n <= 0) + { + // 0.[000]digits + // len <= 2 + (-min_exp - 1) + max_digits10 + + std::memmove(buf + (2 + static_cast(-n)), buf, static_cast(k)); + buf[0] = '0'; + buf[1] = '.'; + std::memset(buf + 2, '0', static_cast(-n)); + return buf + (2U + static_cast(-n) + static_cast(k)); + } + + if (k == 1) + { + // dE+123 + // len <= 1 + 5 + + buf += 1; + } + else + { + // d.igitsE+123 + // len <= max_digits10 + 1 + 5 + + std::memmove(buf + 2, buf + 1, static_cast(k) - 1); + buf[1] = '.'; + buf += 1 + static_cast(k); + } + + *buf++ = 'e'; + return append_exponent(buf, n - 1); +} + +} // namespace dtoa_impl + +/*! +@brief generates a decimal representation of the floating-point number value in [first, last). + +The format of the resulting decimal representation is similar to printf's %g +format. Returns an iterator pointing past-the-end of the decimal representation. + +@note The input number must be finite, i.e. NaN's and Inf's are not supported. +@note The buffer must be large enough. +@note The result is NOT null-terminated. +*/ +template +JSON_HEDLEY_NON_NULL(1, 2) +JSON_HEDLEY_RETURNS_NON_NULL +char* to_chars(char* first, const char* last, FloatType value) +{ + static_cast(last); // maybe unused - fix warning + JSON_ASSERT(std::isfinite(value)); + + // Use signbit(value) instead of (value < 0) since signbit works for -0. + if (std::signbit(value)) + { + value = -value; + *first++ = '-'; + } + + if (value == 0) // +-0 + { + *first++ = '0'; + // Make it look like a floating-point number (#362, #378) + *first++ = '.'; + *first++ = '0'; + return first; + } + + JSON_ASSERT(last - first >= std::numeric_limits::max_digits10); + + // Compute v = buffer * 10^decimal_exponent. + // The decimal digits are stored in the buffer, which needs to be interpreted + // as an unsigned decimal integer. + // len is the length of the buffer, i.e. the number of decimal digits. + int len = 0; + int decimal_exponent = 0; + dtoa_impl::grisu2(first, len, decimal_exponent, value); + + JSON_ASSERT(len <= std::numeric_limits::max_digits10); + + // Format the buffer like printf("%.*g", prec, value) + constexpr int kMinExp = -4; + // Use digits10 here to increase compatibility with version 2. + constexpr int kMaxExp = std::numeric_limits::digits10; + + JSON_ASSERT(last - first >= kMaxExp + 2); + JSON_ASSERT(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits::max_digits10); + JSON_ASSERT(last - first >= std::numeric_limits::max_digits10 + 6); + + return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp); +} + +} // namespace detail +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/conversions/to_json.hpp b/lib/json/include/nlohmann/detail/conversions/to_json.hpp new file mode 100644 index 0000000..9d7f55f --- /dev/null +++ b/lib/json/include/nlohmann/detail/conversions/to_json.hpp @@ -0,0 +1,382 @@ +#pragma once + +#include // copy +#include // begin, end +#include // string +#include // tuple, get +#include // is_same, is_constructible, is_floating_point, is_enum, underlying_type +#include // move, forward, declval, pair +#include // valarray +#include // vector + +#include +#include +#include +#include + +namespace nlohmann +{ +namespace detail +{ +////////////////// +// constructors // +////////////////// + +template struct external_constructor; + +template<> +struct external_constructor +{ + template + 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 +{ + template + 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 + 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 < typename BasicJsonType, typename CompatibleStringType, + enable_if_t < !std::is_same::value, + int > = 0 > + static void construct(BasicJsonType& j, const CompatibleStringType& str) + { + j.m_type = value_t::string; + j.m_value.string = j.template create(str); + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b) + { + j.m_type = value_t::binary; + j.m_value = typename BasicJsonType::binary_t(b); + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b) + { + j.m_type = value_t::binary; + j.m_value = typename BasicJsonType::binary_t(std::move(b));; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + 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 +{ + template + 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 +{ + template + 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 +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr) + { + j.m_type = value_t::array; + j.m_value = arr; + j.set_parents(); + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr) + { + j.m_type = value_t::array; + j.m_value = std::move(arr); + j.set_parents(); + j.assert_invariant(); + } + + template < typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < !std::is_same::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(begin(arr), end(arr)); + j.set_parents(); + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, const std::vector& 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.set_parent(j.m_value.array->back()); + } + j.assert_invariant(); + } + + template::value, int> = 0> + static void construct(BasicJsonType& j, const std::valarray& arr) + { + j.m_type = value_t::array; + j.m_value = value_t::array; + j.m_value.array->resize(arr.size()); + if (arr.size() > 0) + { + std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin()); + } + j.set_parents(); + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) + { + j.m_type = value_t::object; + j.m_value = obj; + j.set_parents(); + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj) + { + j.m_type = value_t::object; + j.m_value = std::move(obj); + j.set_parents(); + j.assert_invariant(); + } + + template < typename BasicJsonType, typename CompatibleObjectType, + enable_if_t < !std::is_same::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(begin(obj), end(obj)); + j.set_parents(); + j.assert_invariant(); + } +}; + +///////////// +// to_json // +///////////// + +template::value, int> = 0> +void to_json(BasicJsonType& j, T b) noexcept +{ + external_constructor::construct(j, b); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, const CompatibleString& s) +{ + external_constructor::construct(j, s); +} + +template +void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) +{ + external_constructor::construct(j, std::move(s)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, FloatType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, EnumType e) noexcept +{ + using underlying_type = typename std::underlying_type::type; + external_constructor::construct(j, static_cast(e)); +} + +template +void to_json(BasicJsonType& j, const std::vector& e) +{ + external_constructor::construct(j, e); +} + +template < typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < is_compatible_array_type::value&& + !is_compatible_object_type::value&& + !is_compatible_string_type::value&& + !std::is_same::value&& + !is_basic_json::value, + int > = 0 > +void to_json(BasicJsonType& j, const CompatibleArrayType& arr) +{ + external_constructor::construct(j, arr); +} + +template +void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin) +{ + external_constructor::construct(j, bin); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, const std::valarray& arr) +{ + external_constructor::construct(j, std::move(arr)); +} + +template +void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) +{ + external_constructor::construct(j, std::move(arr)); +} + +template < typename BasicJsonType, typename CompatibleObjectType, + enable_if_t < is_compatible_object_type::value&& !is_basic_json::value, int > = 0 > +void to_json(BasicJsonType& j, const CompatibleObjectType& obj) +{ + external_constructor::construct(j, obj); +} + +template +void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) +{ + external_constructor::construct(j, std::move(obj)); +} + +template < + typename BasicJsonType, typename T, std::size_t N, + enable_if_t < !std::is_constructible::value, // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + int > = 0 > +void to_json(BasicJsonType& j, const T(&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +{ + external_constructor::construct(j, arr); +} + +template < typename BasicJsonType, typename T1, typename T2, enable_if_t < std::is_constructible::value&& std::is_constructible::value, int > = 0 > +void to_json(BasicJsonType& j, const std::pair& p) +{ + j = { p.first, p.second }; +} + +// for https://github.com/nlohmann/json/pull/1134 +template>::value, int> = 0> +void to_json(BasicJsonType& j, const T& b) +{ + j = { {b.key(), b.value()} }; +} + +template +void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence /*unused*/) +{ + j = { std::get(t)... }; +} + +template::value, int > = 0> +void to_json(BasicJsonType& j, const T& t) +{ + to_json_tuple_impl(j, t, make_index_sequence::value> {}); +} + +struct to_json_fn +{ + template + auto operator()(BasicJsonType& j, T&& val) const noexcept(noexcept(to_json(j, std::forward(val)))) + -> decltype(to_json(j, std::forward(val)), void()) + { + return to_json(j, std::forward(val)); + } +}; +} // namespace detail + +/// namespace to hold default `to_json` function +/// to see why this is required: +/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html +namespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces) +{ +constexpr const auto& to_json = detail::static_const::value; // NOLINT(misc-definitions-in-headers) +} // namespace +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/exceptions.hpp b/lib/json/include/nlohmann/detail/exceptions.hpp new file mode 100644 index 0000000..fc157a9 --- /dev/null +++ b/lib/json/include/nlohmann/detail/exceptions.hpp @@ -0,0 +1,421 @@ +#pragma once + +#include // exception +#include // runtime_error +#include // to_string +#include // vector + +#include +#include +#include +#include + +namespace nlohmann +{ +namespace detail +{ +//////////////// +// exceptions // +//////////////// + +/*! +@brief general exception of the @ref basic_json class + +This class is an extension of `std::exception` objects with a member @a id for +exception ids. It is used as the base class for all exceptions thrown by the +@ref basic_json class. This class can hence be used as "wildcard" to catch +exceptions. + +Subclasses: +- @ref parse_error for exceptions indicating a parse error +- @ref invalid_iterator for exceptions indicating errors with iterators +- @ref type_error for exceptions indicating executing a member function with + a wrong type +- @ref out_of_range for exceptions indicating access out of the defined range +- @ref other_error for exceptions indicating other library errors + +@internal +@note To have nothrow-copy-constructible exceptions, we internally use + `std::runtime_error` which can cope with arbitrary-length error messages. + Intermediate strings are built with static functions and then passed to + the actual constructor. +@endinternal + +@liveexample{The following code shows how arbitrary library exceptions can be +caught.,exception} + +@since version 3.0.0 +*/ +class exception : public std::exception +{ + public: + /// returns the explanatory string + const char* what() const noexcept override + { + return m.what(); + } + + /// the id of the exception + const int id; // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes) + + protected: + JSON_HEDLEY_NON_NULL(3) + 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_) + "] "; + } + + template + static std::string diagnostics(const BasicJsonType& leaf_element) + { +#if JSON_DIAGNOSTICS + std::vector tokens; + for (const auto* current = &leaf_element; current->m_parent != nullptr; current = current->m_parent) + { + switch (current->m_parent->type()) + { + case value_t::array: + { + for (std::size_t i = 0; i < current->m_parent->m_value.array->size(); ++i) + { + if (¤t->m_parent->m_value.array->operator[](i) == current) + { + tokens.emplace_back(std::to_string(i)); + break; + } + } + break; + } + + case value_t::object: + { + for (const auto& element : *current->m_parent->m_value.object) + { + if (&element.second == current) + { + tokens.emplace_back(element.first.c_str()); + break; + } + } + break; + } + + default: // LCOV_EXCL_LINE + break; // LCOV_EXCL_LINE + } + } + + if (tokens.empty()) + { + return ""; + } + + return "(" + std::accumulate(tokens.rbegin(), tokens.rend(), std::string{}, + [](const std::string & a, const std::string & b) + { + return a + "/" + detail::escape(b); + }) + ") "; +#else + static_cast(leaf_element); + return ""; +#endif + } + + private: + /// an exception object as storage for error messages + std::runtime_error m; +}; + +/*! +@brief exception indicating a parse error + +This exception is thrown by the library when a parse error occurs. Parse errors +can occur during the deserialization of JSON text, CBOR, MessagePack, as well +as when using JSON Patch. + +Member @a byte holds the byte index of the last read character in the input +file. + +Exceptions have ids 1xx. + +name / id | example message | description +------------------------------ | --------------- | ------------------------- +json.exception.parse_error.101 | parse error at 2: unexpected end of input; expected string literal | This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member @a byte indicates the error position. +json.exception.parse_error.102 | parse error at 14: missing or wrong low surrogate | JSON uses the `\uxxxx` format to describe Unicode characters. Code points above above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point. +json.exception.parse_error.103 | parse error: code points above 0x10FFFF are invalid | Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are invalid. +json.exception.parse_error.104 | parse error: JSON patch must be an array of objects | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON document that represents an array of objects. +json.exception.parse_error.105 | parse error: operation must have string member 'op' | An operation of a JSON Patch document must contain exactly one "op" member, whose value indicates the operation to perform. Its value must be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors. +json.exception.parse_error.106 | parse error: array index '01' must not begin with '0' | An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number without a leading `0`. +json.exception.parse_error.107 | parse error: JSON pointer must be empty or begin with '/' - was: 'foo' | A JSON Pointer must be a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a `/` character. +json.exception.parse_error.108 | parse error: escape character '~' must be followed with '0' or '1' | In a JSON Pointer, only `~0` and `~1` are valid escape sequences. +json.exception.parse_error.109 | parse error: array index 'one' is not a number | A JSON Pointer array index must be a number. +json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from vector | When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read. +json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xF8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read. +json.exception.parse_error.113 | parse error at 2: expected a CBOR string; last byte: 0x98 | While parsing a map key, a value that is not a string has been read. +json.exception.parse_error.114 | parse error: Unsupported BSON record type 0x0F | The parsing of the corresponding BSON record type is not implemented (yet). +json.exception.parse_error.115 | parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1A | A UBJSON high-precision number could not be parsed. + +@note For an input with n bytes, 1 is the index of the first character and n+1 + is the index of the terminating null byte or the end of file. This also + holds true when reading a byte vector (CBOR or MessagePack). + +@liveexample{The following code shows how a `parse_error` exception can be +caught.,parse_error} + +@sa - @ref exception for the base class of the library exceptions +@sa - @ref invalid_iterator for exceptions indicating errors with iterators +@sa - @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa - @ref out_of_range for exceptions indicating access out of the defined range +@sa - @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class parse_error : public exception +{ + public: + /*! + @brief create a parse error exception + @param[in] id_ the id of the exception + @param[in] pos the position where the error occurred (or with + chars_read_total=0 if the position cannot be + determined) + @param[in] what_arg the explanatory string + @return parse_error object + */ + template + static parse_error create(int id_, const position_t& pos, const std::string& what_arg, const BasicJsonType& context) + { + std::string w = exception::name("parse_error", id_) + "parse error" + + position_string(pos) + ": " + exception::diagnostics(context) + what_arg; + return parse_error(id_, pos.chars_read_total, w.c_str()); + } + + template + static parse_error create(int id_, std::size_t byte_, const std::string& what_arg, const BasicJsonType& context) + { + std::string w = exception::name("parse_error", id_) + "parse error" + + (byte_ != 0 ? (" at byte " + std::to_string(byte_)) : "") + + ": " + exception::diagnostics(context) + what_arg; + return parse_error(id_, byte_, w.c_str()); + } + + /*! + @brief byte index of the parse error + + The byte index of the last read character in the input file. + + @note For an input with n bytes, 1 is the index of the first character and + n+1 is the index of the terminating null byte or the end of file. + This also holds true when reading a byte vector (CBOR or MessagePack). + */ + const std::size_t byte; + + private: + parse_error(int id_, std::size_t byte_, const char* what_arg) + : exception(id_, what_arg), byte(byte_) {} + + static std::string position_string(const position_t& pos) + { + return " at line " + std::to_string(pos.lines_read + 1) + + ", column " + std::to_string(pos.chars_read_current_line); + } +}; + +/*! +@brief exception indicating errors with iterators + +This exception is thrown if iterators passed to a library function do not match +the expected semantics. + +Exceptions have ids 2xx. + +name / id | example message | description +----------------------------------- | --------------- | ------------------------- +json.exception.invalid_iterator.201 | iterators are not compatible | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. +json.exception.invalid_iterator.202 | iterator does not fit current value | In an erase or insert function, the passed iterator @a pos does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion. +json.exception.invalid_iterator.203 | iterators do not fit current value | Either iterator passed to function @ref erase(IteratorType first, IteratorType last) does not belong to the JSON value from which values shall be erased. It hence does not define a valid range to delete values from. +json.exception.invalid_iterator.204 | iterators out of range | When an iterator range for a primitive type (number, boolean, or string) is passed to a constructor or an erase function, this range has to be exactly (@ref begin(), @ref end()), because this is the only way the single stored value is expressed. All other ranges are invalid. +json.exception.invalid_iterator.205 | iterator out of range | When an iterator for a primitive type (number, boolean, or string) is passed to an erase function, the iterator has to be the @ref begin() iterator, because it is the only way to address the stored value. All other iterators are invalid. +json.exception.invalid_iterator.206 | cannot construct with iterators from null | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) belong to a JSON null value and hence to not define a valid range. +json.exception.invalid_iterator.207 | cannot use key() for non-object iterators | The key() member function can only be used on iterators belonging to a JSON object, because other types do not have a concept of a key. +json.exception.invalid_iterator.208 | cannot use operator[] for object iterators | The operator[] to specify a concrete offset cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. +json.exception.invalid_iterator.209 | cannot use offsets with object iterators | The offset operators (+, -, +=, -=) cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. +json.exception.invalid_iterator.210 | iterators do not fit | The iterator range passed to the insert function are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. +json.exception.invalid_iterator.211 | passed iterators may not belong to container | The iterator range passed to the insert function must not be a subrange of the container to insert to. +json.exception.invalid_iterator.212 | cannot compare iterators of different containers | When two iterators are compared, they must belong to the same container. +json.exception.invalid_iterator.213 | cannot compare order of object iterators | The order of object iterators cannot be compared, because JSON objects are unordered. +json.exception.invalid_iterator.214 | cannot get value | Cannot get value for iterator: Either the iterator belongs to a null value or it is an iterator to a primitive type (number, boolean, or string), but the iterator is different to @ref begin(). + +@liveexample{The following code shows how an `invalid_iterator` exception can be +caught.,invalid_iterator} + +@sa - @ref exception for the base class of the library exceptions +@sa - @ref parse_error for exceptions indicating a parse error +@sa - @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa - @ref out_of_range for exceptions indicating access out of the defined range +@sa - @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class invalid_iterator : public exception +{ + public: + template + static invalid_iterator create(int id_, const std::string& what_arg, const BasicJsonType& context) + { + std::string w = exception::name("invalid_iterator", id_) + exception::diagnostics(context) + what_arg; + return invalid_iterator(id_, w.c_str()); + } + + private: + JSON_HEDLEY_NON_NULL(3) + invalid_iterator(int id_, const char* what_arg) + : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating executing a member function with a wrong type + +This exception is thrown in case of a type error; that is, a library function is +executed on a JSON value whose type does not match the expected semantics. + +Exceptions have ids 3xx. + +name / id | example message | description +----------------------------- | --------------- | ------------------------- +json.exception.type_error.301 | cannot create object from initializer list | To create an object from an initializer list, the initializer list must consist only of a list of pairs whose first element is a string. When this constraint is violated, an array is created instead. +json.exception.type_error.302 | type must be object, but is array | During implicit or explicit value conversion, the JSON type must be compatible to the target type. For instance, a JSON string can only be converted into string types, but not into numbers or boolean types. +json.exception.type_error.303 | incompatible ReferenceType for get_ref, actual type is object | To retrieve a reference to a value stored in a @ref basic_json object with @ref get_ref, the type of the reference must match the value type. For instance, for a JSON array, the @a ReferenceType must be @ref array_t &. +json.exception.type_error.304 | cannot use at() with string | The @ref at() member functions can only be executed for certain JSON types. +json.exception.type_error.305 | cannot use operator[] with string | The @ref operator[] member functions can only be executed for certain JSON types. +json.exception.type_error.306 | cannot use value() with string | The @ref value() member functions can only be executed for certain JSON types. +json.exception.type_error.307 | cannot use erase() with string | The @ref erase() member functions can only be executed for certain JSON types. +json.exception.type_error.308 | cannot use push_back() with string | The @ref push_back() and @ref operator+= member functions can only be executed for certain JSON types. +json.exception.type_error.309 | cannot use insert() with | The @ref insert() member functions can only be executed for certain JSON types. +json.exception.type_error.310 | cannot use swap() with number | The @ref swap() member functions can only be executed for certain JSON types. +json.exception.type_error.311 | cannot use emplace_back() with string | The @ref emplace_back() member function can only be executed for certain JSON types. +json.exception.type_error.312 | cannot use update() with string | The @ref update() member functions can only be executed for certain JSON types. +json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well defined. +json.exception.type_error.314 | only objects can be unflattened | The @ref unflatten function only works for an object whose keys are JSON Pointers. +json.exception.type_error.315 | values in object must be primitive | The @ref unflatten function only works for an object whose keys are JSON Pointers and whose values are primitive. +json.exception.type_error.316 | invalid UTF-8 byte at index 10: 0x7E | The @ref dump function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. | +json.exception.type_error.317 | JSON value cannot be serialized to requested format | The dynamic type of the object cannot be represented in the requested serialization format (e.g. a raw `true` or `null` JSON object cannot be serialized to BSON) | + +@liveexample{The following code shows how a `type_error` exception can be +caught.,type_error} + +@sa - @ref exception for the base class of the library exceptions +@sa - @ref parse_error for exceptions indicating a parse error +@sa - @ref invalid_iterator for exceptions indicating errors with iterators +@sa - @ref out_of_range for exceptions indicating access out of the defined range +@sa - @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class type_error : public exception +{ + public: + template + static type_error create(int id_, const std::string& what_arg, const BasicJsonType& context) + { + std::string w = exception::name("type_error", id_) + exception::diagnostics(context) + what_arg; + return type_error(id_, w.c_str()); + } + + private: + JSON_HEDLEY_NON_NULL(3) + type_error(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating access out of the defined range + +This exception is thrown in case a library function is called on an input +parameter that exceeds the expected range, for instance in case of array +indices or nonexisting object keys. + +Exceptions have ids 4xx. + +name / id | example message | description +------------------------------- | --------------- | ------------------------- +json.exception.out_of_range.401 | array index 3 is out of range | The provided array index @a i is larger than @a size-1. +json.exception.out_of_range.402 | array index '-' (3) is out of range | The special array index `-` in a JSON Pointer never describes a valid element of the array, but the index past the end. That is, it can only be used to add elements at this position, but not to read it. +json.exception.out_of_range.403 | key 'foo' not found | The provided key was not found in the JSON object. +json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved. +json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value. +json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF. +json.exception.out_of_range.407 | number overflow serializing '9223372036854775808' | UBJSON and BSON only support integer numbers up to 9223372036854775807. (until version 3.8.0) | +json.exception.out_of_range.408 | excessive array size: 8658170730974374167 | The size (following `#`) of an UBJSON array or object exceeds the maximal capacity. | +json.exception.out_of_range.409 | BSON key cannot contain code point U+0000 (at byte 2) | Key identifiers to be serialized to BSON cannot contain code point U+0000, since the key is stored as zero-terminated c-string | + +@liveexample{The following code shows how an `out_of_range` exception can be +caught.,out_of_range} + +@sa - @ref exception for the base class of the library exceptions +@sa - @ref parse_error for exceptions indicating a parse error +@sa - @ref invalid_iterator for exceptions indicating errors with iterators +@sa - @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa - @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class out_of_range : public exception +{ + public: + template + static out_of_range create(int id_, const std::string& what_arg, const BasicJsonType& context) + { + std::string w = exception::name("out_of_range", id_) + exception::diagnostics(context) + what_arg; + return out_of_range(id_, w.c_str()); + } + + private: + JSON_HEDLEY_NON_NULL(3) + out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating other library errors + +This exception is thrown in case of errors that cannot be classified with the +other exception types. + +Exceptions have ids 5xx. + +name / id | example message | description +------------------------------ | --------------- | ------------------------- +json.exception.other_error.501 | unsuccessful: {"op":"test","path":"/baz", "value":"bar"} | A JSON Patch operation 'test' failed. The unsuccessful operation is also printed. + +@sa - @ref exception for the base class of the library exceptions +@sa - @ref parse_error for exceptions indicating a parse error +@sa - @ref invalid_iterator for exceptions indicating errors with iterators +@sa - @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa - @ref out_of_range for exceptions indicating access out of the defined range + +@liveexample{The following code shows how an `other_error` exception can be +caught.,other_error} + +@since version 3.0.0 +*/ +class other_error : public exception +{ + public: + template + static other_error create(int id_, const std::string& what_arg, const BasicJsonType& context) + { + std::string w = exception::name("other_error", id_) + exception::diagnostics(context) + what_arg; + return other_error(id_, w.c_str()); + } + + private: + JSON_HEDLEY_NON_NULL(3) + other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; +} // namespace detail +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/hash.hpp b/lib/json/include/nlohmann/detail/hash.hpp new file mode 100644 index 0000000..70c5daf --- /dev/null +++ b/lib/json/include/nlohmann/detail/hash.hpp @@ -0,0 +1,121 @@ +#pragma once + +#include // uint8_t +#include // size_t +#include // hash + +#include + +namespace nlohmann +{ +namespace detail +{ + +// boost::hash_combine +inline std::size_t combine(std::size_t seed, std::size_t h) noexcept +{ + seed ^= h + 0x9e3779b9 + (seed << 6U) + (seed >> 2U); + return seed; +} + +/*! +@brief hash a JSON value + +The hash function tries to rely on std::hash where possible. Furthermore, the +type of the JSON value is taken into account to have different hash values for +null, 0, 0U, and false, etc. + +@tparam BasicJsonType basic_json specialization +@param j JSON value to hash +@return hash value of j +*/ +template +std::size_t hash(const BasicJsonType& j) +{ + using string_t = typename BasicJsonType::string_t; + 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; + + const auto type = static_cast(j.type()); + switch (j.type()) + { + case BasicJsonType::value_t::null: + case BasicJsonType::value_t::discarded: + { + return combine(type, 0); + } + + case BasicJsonType::value_t::object: + { + auto seed = combine(type, j.size()); + for (const auto& element : j.items()) + { + const auto h = std::hash {}(element.key()); + seed = combine(seed, h); + seed = combine(seed, hash(element.value())); + } + return seed; + } + + case BasicJsonType::value_t::array: + { + auto seed = combine(type, j.size()); + for (const auto& element : j) + { + seed = combine(seed, hash(element)); + } + return seed; + } + + case BasicJsonType::value_t::string: + { + const auto h = std::hash {}(j.template get_ref()); + return combine(type, h); + } + + case BasicJsonType::value_t::boolean: + { + const auto h = std::hash {}(j.template get()); + return combine(type, h); + } + + case BasicJsonType::value_t::number_integer: + { + const auto h = std::hash {}(j.template get()); + return combine(type, h); + } + + case BasicJsonType::value_t::number_unsigned: + { + const auto h = std::hash {}(j.template get()); + return combine(type, h); + } + + case BasicJsonType::value_t::number_float: + { + const auto h = std::hash {}(j.template get()); + return combine(type, h); + } + + case BasicJsonType::value_t::binary: + { + auto seed = combine(type, j.get_binary().size()); + const auto h = std::hash {}(j.get_binary().has_subtype()); + seed = combine(seed, h); + seed = combine(seed, j.get_binary().subtype()); + for (const auto byte : j.get_binary()) + { + seed = combine(seed, std::hash {}(byte)); + } + return seed; + } + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + return 0; // LCOV_EXCL_LINE + } +} + +} // namespace detail +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/input/binary_reader.hpp b/lib/json/include/nlohmann/detail/input/binary_reader.hpp new file mode 100644 index 0000000..9b9fedf --- /dev/null +++ b/lib/json/include/nlohmann/detail/input/binary_reader.hpp @@ -0,0 +1,2461 @@ +#pragma once + +#include // generate_n +#include // array +#include // ldexp +#include // size_t +#include // uint8_t, uint16_t, uint32_t, uint64_t +#include // snprintf +#include // memcpy +#include // back_inserter +#include // numeric_limits +#include // char_traits, string +#include // make_pair, move +#include // vector + +#include +#include +#include +#include +#include +#include +#include + +namespace nlohmann +{ +namespace detail +{ + +/// how to treat CBOR tags +enum class cbor_tag_handler_t +{ + error, ///< throw a parse_error exception in case of a tag + ignore ///< ignore tags +}; + +/*! +@brief determine system byte order + +@return true if and only if system's byte order is little endian + +@note from https://stackoverflow.com/a/1001328/266378 +*/ +static inline bool little_endianess(int num = 1) noexcept +{ + return *reinterpret_cast(&num) == 1; +} + + +/////////////////// +// binary reader // +/////////////////// + +/*! +@brief deserialization of CBOR, MessagePack, and UBJSON values +*/ +template> +class binary_reader +{ + 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 binary_t = typename BasicJsonType::binary_t; + using json_sax_t = SAX; + using char_type = typename InputAdapterType::char_type; + using char_int_type = typename std::char_traits::int_type; + + public: + /*! + @brief create a binary reader + + @param[in] adapter input adapter to read from + */ + explicit binary_reader(InputAdapterType&& adapter) noexcept : ia(std::move(adapter)) + { + (void)detail::is_sax_static_asserts {}; + } + + // make class move-only + binary_reader(const binary_reader&) = delete; + binary_reader(binary_reader&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + binary_reader& operator=(const binary_reader&) = delete; + binary_reader& operator=(binary_reader&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + ~binary_reader() = default; + + /*! + @param[in] format the binary format to parse + @param[in] sax_ a SAX event processor + @param[in] strict whether to expect the input to be consumed completed + @param[in] tag_handler how to treat CBOR tags + + @return whether parsing was successful + */ + JSON_HEDLEY_NON_NULL(3) + bool sax_parse(const input_format_t format, + json_sax_t* sax_, + const bool strict = true, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + sax = sax_; + bool result = false; + + switch (format) + { + case input_format_t::bson: + result = parse_bson_internal(); + break; + + case input_format_t::cbor: + result = parse_cbor_internal(true, tag_handler); + break; + + case input_format_t::msgpack: + result = parse_msgpack_internal(); + break; + + case input_format_t::ubjson: + result = parse_ubjson_internal(); + break; + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } + + // strict mode: next byte must be EOF + if (result && strict) + { + if (format == input_format_t::ubjson) + { + get_ignore_noop(); + } + else + { + get(); + } + + if (JSON_HEDLEY_UNLIKELY(current != std::char_traits::eof())) + { + return sax->parse_error(chars_read, get_token_string(), + parse_error::create(110, chars_read, exception_message(format, "expected end of input; last byte: 0x" + get_token_string(), "value"), BasicJsonType())); + } + } + + return result; + } + + private: + ////////// + // BSON // + ////////// + + /*! + @brief Reads in a BSON-object and passes it to the SAX-parser. + @return whether a valid BSON-value was passed to the SAX parser + */ + bool parse_bson_internal() + { + std::int32_t document_size{}; + get_number(input_format_t::bson, document_size); + + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(std::size_t(-1)))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_list(/*is_array*/false))) + { + return false; + } + + return sax->end_object(); + } + + /*! + @brief Parses a C-style string from the BSON input. + @param[in,out] result A reference to the string variable where the read + string is to be stored. + @return `true` if the \x00-byte indicating the end of the string was + encountered before the EOF; false` indicates an unexpected EOF. + */ + bool get_bson_cstr(string_t& result) + { + auto out = std::back_inserter(result); + while (true) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bson, "cstring"))) + { + return false; + } + if (current == 0x00) + { + return true; + } + *out++ = static_cast(current); + } + } + + /*! + @brief Parses a zero-terminated string of length @a len from the BSON + input. + @param[in] len The length (including the zero-byte at the end) of the + string to be read. + @param[in,out] result A reference to the string variable where the read + string is to be stored. + @tparam NumberType The type of the length @a len + @pre len >= 1 + @return `true` if the string was successfully parsed + */ + template + bool get_bson_string(const NumberType len, string_t& result) + { + if (JSON_HEDLEY_UNLIKELY(len < 1)) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::bson, "string length must be at least 1, is " + std::to_string(len), "string"), BasicJsonType())); + } + + return get_string(input_format_t::bson, len - static_cast(1), result) && get() != std::char_traits::eof(); + } + + /*! + @brief Parses a byte array input of length @a len from the BSON input. + @param[in] len The length of the byte array to be read. + @param[in,out] result A reference to the binary variable where the read + array is to be stored. + @tparam NumberType The type of the length @a len + @pre len >= 0 + @return `true` if the byte array was successfully parsed + */ + template + bool get_bson_binary(const NumberType len, binary_t& result) + { + if (JSON_HEDLEY_UNLIKELY(len < 0)) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::bson, "byte array length cannot be negative, is " + std::to_string(len), "binary"), BasicJsonType())); + } + + // All BSON binary values have a subtype + std::uint8_t subtype{}; + get_number(input_format_t::bson, subtype); + result.set_subtype(subtype); + + return get_binary(input_format_t::bson, len, result); + } + + /*! + @brief Read a BSON document element of the given @a element_type. + @param[in] element_type The BSON element type, c.f. http://bsonspec.org/spec.html + @param[in] element_type_parse_position The position in the input stream, + where the `element_type` was read. + @warning Not all BSON element types are supported yet. An unsupported + @a element_type will give rise to a parse_error.114: + Unsupported BSON record type 0x... + @return whether a valid BSON-object/array was passed to the SAX parser + */ + bool parse_bson_element_internal(const char_int_type element_type, + const std::size_t element_type_parse_position) + { + switch (element_type) + { + case 0x01: // double + { + double number{}; + return get_number(input_format_t::bson, number) && sax->number_float(static_cast(number), ""); + } + + case 0x02: // string + { + std::int32_t len{}; + string_t value; + return get_number(input_format_t::bson, len) && get_bson_string(len, value) && sax->string(value); + } + + case 0x03: // object + { + return parse_bson_internal(); + } + + case 0x04: // array + { + return parse_bson_array(); + } + + case 0x05: // binary + { + std::int32_t len{}; + binary_t value; + return get_number(input_format_t::bson, len) && get_bson_binary(len, value) && sax->binary(value); + } + + case 0x08: // boolean + { + return sax->boolean(get() != 0); + } + + case 0x0A: // null + { + return sax->null(); + } + + case 0x10: // int32 + { + std::int32_t value{}; + return get_number(input_format_t::bson, value) && sax->number_integer(value); + } + + case 0x12: // int64 + { + std::int64_t value{}; + return get_number(input_format_t::bson, value) && sax->number_integer(value); + } + + default: // anything else not supported (yet) + { + std::array cr{{}}; + (std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(element_type)); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + return sax->parse_error(element_type_parse_position, std::string(cr.data()), parse_error::create(114, element_type_parse_position, "Unsupported BSON record type 0x" + std::string(cr.data()), BasicJsonType())); + } + } + } + + /*! + @brief Read a BSON element list (as specified in the BSON-spec) + + The same binary layout is used for objects and arrays, hence it must be + indicated with the argument @a is_array which one is expected + (true --> array, false --> object). + + @param[in] is_array Determines if the element list being read is to be + treated as an object (@a is_array == false), or as an + array (@a is_array == true). + @return whether a valid BSON-object/array was passed to the SAX parser + */ + bool parse_bson_element_list(const bool is_array) + { + string_t key; + + while (auto element_type = get()) + { + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bson, "element list"))) + { + return false; + } + + const std::size_t element_type_parse_position = chars_read; + if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key))) + { + return false; + } + + if (!is_array && !sax->key(key)) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_internal(element_type, element_type_parse_position))) + { + return false; + } + + // get_bson_cstr only appends + key.clear(); + } + + return true; + } + + /*! + @brief Reads an array from the BSON input and passes it to the SAX-parser. + @return whether a valid BSON-array was passed to the SAX parser + */ + bool parse_bson_array() + { + std::int32_t document_size{}; + get_number(input_format_t::bson, document_size); + + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(std::size_t(-1)))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_list(/*is_array*/true))) + { + return false; + } + + return sax->end_array(); + } + + ////////// + // CBOR // + ////////// + + /*! + @param[in] get_char whether a new character should be retrieved from the + input (true) or whether the last read character should + be considered instead (false) + @param[in] tag_handler how CBOR tags should be treated + + @return whether a valid CBOR value was passed to the SAX parser + */ + bool parse_cbor_internal(const bool get_char, + const cbor_tag_handler_t tag_handler) + { + switch (get_char ? get() : current) + { + // EOF + case std::char_traits::eof(): + return unexpect_eof(input_format_t::cbor, "value"); + + // Integer 0x00..0x17 (0..23) + 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 sax->number_unsigned(static_cast(current)); + + case 0x18: // Unsigned integer (one-byte uint8_t follows) + { + std::uint8_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); + } + + case 0x19: // Unsigned integer (two-byte uint16_t follows) + { + std::uint16_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); + } + + case 0x1A: // Unsigned integer (four-byte uint32_t follows) + { + std::uint32_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); + } + + case 0x1B: // Unsigned integer (eight-byte uint64_t follows) + { + std::uint64_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); + } + + // Negative integer -1-0x00..-1-0x17 (-1..-24) + 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 sax->number_integer(static_cast(0x20 - 1 - current)); + + case 0x38: // Negative integer (one-byte uint8_t follows) + { + std::uint8_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) - number); + } + + case 0x39: // Negative integer -1-n (two-byte uint16_t follows) + { + std::uint16_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) - number); + } + + case 0x3A: // Negative integer -1-n (four-byte uint32_t follows) + { + std::uint32_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) - number); + } + + case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows) + { + std::uint64_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) + - static_cast(number)); + } + + // Binary data (0x00..0x17 bytes follow) + 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: // Binary data (one-byte uint8_t for n follows) + case 0x59: // Binary data (two-byte uint16_t for n follow) + case 0x5A: // Binary data (four-byte uint32_t for n follow) + case 0x5B: // Binary data (eight-byte uint64_t for n follow) + case 0x5F: // Binary data (indefinite length) + { + binary_t b; + return get_cbor_binary(b) && sax->binary(b); + } + + // UTF-8 string (0x00..0x17 bytes follow) + 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: // UTF-8 string (one-byte uint8_t for n follows) + case 0x79: // UTF-8 string (two-byte uint16_t for n follow) + case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) + case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) + case 0x7F: // UTF-8 string (indefinite length) + { + string_t s; + return get_cbor_string(s) && sax->string(s); + } + + // array (0x00..0x17 data items follow) + 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(static_cast(static_cast(current) & 0x1Fu), tag_handler); + + case 0x98: // array (one-byte uint8_t for n follows) + { + std::uint8_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); + } + + case 0x99: // array (two-byte uint16_t for n follow) + { + std::uint16_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); + } + + case 0x9A: // array (four-byte uint32_t for n follow) + { + std::uint32_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); + } + + case 0x9B: // array (eight-byte uint64_t for n follow) + { + std::uint64_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); + } + + case 0x9F: // array (indefinite length) + return get_cbor_array(std::size_t(-1), tag_handler); + + // map (0x00..0x17 pairs of data items follow) + 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(static_cast(static_cast(current) & 0x1Fu), tag_handler); + + case 0xB8: // map (one-byte uint8_t for n follows) + { + std::uint8_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); + } + + case 0xB9: // map (two-byte uint16_t for n follow) + { + std::uint16_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); + } + + case 0xBA: // map (four-byte uint32_t for n follow) + { + std::uint32_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); + } + + case 0xBB: // map (eight-byte uint64_t for n follow) + { + std::uint64_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); + } + + case 0xBF: // map (indefinite length) + return get_cbor_object(std::size_t(-1), tag_handler); + + case 0xC6: // tagged item + 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 0xD8: // tagged item (1 bytes follow) + case 0xD9: // tagged item (2 bytes follow) + case 0xDA: // tagged item (4 bytes follow) + case 0xDB: // tagged item (8 bytes follow) + { + switch (tag_handler) + { + case cbor_tag_handler_t::error: + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::cbor, "invalid byte: 0x" + last_token, "value"), BasicJsonType())); + } + + case cbor_tag_handler_t::ignore: + { + switch (current) + { + case 0xD8: + { + std::uint8_t len{}; + get_number(input_format_t::cbor, len); + break; + } + case 0xD9: + { + std::uint16_t len{}; + get_number(input_format_t::cbor, len); + break; + } + case 0xDA: + { + std::uint32_t len{}; + get_number(input_format_t::cbor, len); + break; + } + case 0xDB: + { + std::uint64_t len{}; + get_number(input_format_t::cbor, len); + break; + } + default: + break; + } + return parse_cbor_internal(true, tag_handler); + } + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + return false; // LCOV_EXCL_LINE + } + } + + case 0xF4: // false + return sax->boolean(false); + + case 0xF5: // true + return sax->boolean(true); + + case 0xF6: // null + return sax->null(); + + case 0xF9: // Half-Precision Float (two-byte IEEE 754) + { + const auto byte1_raw = get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "number"))) + { + return false; + } + const auto byte2_raw = get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "number"))) + { + return false; + } + + const auto byte1 = static_cast(byte1_raw); + const auto byte2 = static_cast(byte2_raw); + + // code from RFC 7049, Appendix D, Figure 3: + // As half-precision floating-point numbers were only added + // to IEEE 754 in 2008, today's programming platforms often + // still only have limited support for them. It is very + // easy to include at least decoding support for them even + // without such support. An example of a small decoder for + // half-precision floating-point numbers in the C language + // is shown in Fig. 3. + const auto half = static_cast((byte1 << 8u) + byte2); + const double val = [&half] + { + const int exp = (half >> 10u) & 0x1Fu; + const unsigned int mant = half & 0x3FFu; + JSON_ASSERT(0 <= exp&& exp <= 32); + JSON_ASSERT(mant <= 1024); + switch (exp) + { + case 0: + return std::ldexp(mant, -24); + case 31: + return (mant == 0) + ? std::numeric_limits::infinity() + : std::numeric_limits::quiet_NaN(); + default: + return std::ldexp(mant + 1024, exp - 25); + } + }(); + return sax->number_float((half & 0x8000u) != 0 + ? static_cast(-val) + : static_cast(val), ""); + } + + case 0xFA: // Single-Precision Float (four-byte IEEE 754) + { + float number{}; + return get_number(input_format_t::cbor, number) && sax->number_float(static_cast(number), ""); + } + + case 0xFB: // Double-Precision Float (eight-byte IEEE 754) + { + double number{}; + return get_number(input_format_t::cbor, number) && sax->number_float(static_cast(number), ""); + } + + default: // anything else (0xFF is handled inside the other types) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::cbor, "invalid byte: 0x" + last_token, "value"), BasicJsonType())); + } + } + } + + /*! + @brief reads a CBOR string + + This function first reads starting bytes to determine the expected + string length and then copies this number of bytes into a string. + Additionally, CBOR's strings with indefinite lengths are supported. + + @param[out] result created string + + @return whether string creation completed + */ + bool get_cbor_string(string_t& result) + { + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "string"))) + { + return false; + } + + switch (current) + { + // UTF-8 string (0x00..0x17 bytes follow) + 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(input_format_t::cbor, static_cast(current) & 0x1Fu, result); + } + + case 0x78: // UTF-8 string (one-byte uint8_t for n follows) + { + std::uint8_t len{}; + return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); + } + + case 0x79: // UTF-8 string (two-byte uint16_t for n follow) + { + std::uint16_t len{}; + return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); + } + + case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) + { + std::uint32_t len{}; + return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); + } + + case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) + { + std::uint64_t len{}; + return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); + } + + case 0x7F: // UTF-8 string (indefinite length) + { + while (get() != 0xFF) + { + string_t chunk; + if (!get_cbor_string(chunk)) + { + return false; + } + result.append(chunk); + } + return true; + } + + default: + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::cbor, "expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x" + last_token, "string"), BasicJsonType())); + } + } + } + + /*! + @brief reads a CBOR byte array + + This function first reads starting bytes to determine the expected + byte array length and then copies this number of bytes into the byte array. + Additionally, CBOR's byte arrays with indefinite lengths are supported. + + @param[out] result created byte array + + @return whether byte array creation completed + */ + bool get_cbor_binary(binary_t& result) + { + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "binary"))) + { + return false; + } + + switch (current) + { + // Binary data (0x00..0x17 bytes follow) + 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: + { + return get_binary(input_format_t::cbor, static_cast(current) & 0x1Fu, result); + } + + case 0x58: // Binary data (one-byte uint8_t for n follows) + { + std::uint8_t len{}; + return get_number(input_format_t::cbor, len) && + get_binary(input_format_t::cbor, len, result); + } + + case 0x59: // Binary data (two-byte uint16_t for n follow) + { + std::uint16_t len{}; + return get_number(input_format_t::cbor, len) && + get_binary(input_format_t::cbor, len, result); + } + + case 0x5A: // Binary data (four-byte uint32_t for n follow) + { + std::uint32_t len{}; + return get_number(input_format_t::cbor, len) && + get_binary(input_format_t::cbor, len, result); + } + + case 0x5B: // Binary data (eight-byte uint64_t for n follow) + { + std::uint64_t len{}; + return get_number(input_format_t::cbor, len) && + get_binary(input_format_t::cbor, len, result); + } + + case 0x5F: // Binary data (indefinite length) + { + while (get() != 0xFF) + { + binary_t chunk; + if (!get_cbor_binary(chunk)) + { + return false; + } + result.insert(result.end(), chunk.begin(), chunk.end()); + } + return true; + } + + default: + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::cbor, "expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x" + last_token, "binary"), BasicJsonType())); + } + } + } + + /*! + @param[in] len the length of the array or std::size_t(-1) for an + array of indefinite size + @param[in] tag_handler how CBOR tags should be treated + @return whether array creation completed + */ + bool get_cbor_array(const std::size_t len, + const cbor_tag_handler_t tag_handler) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(len))) + { + return false; + } + + if (len != std::size_t(-1)) + { + for (std::size_t i = 0; i < len; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) + { + return false; + } + } + } + else + { + while (get() != 0xFF) + { + if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(false, tag_handler))) + { + return false; + } + } + } + + return sax->end_array(); + } + + /*! + @param[in] len the length of the object or std::size_t(-1) for an + object of indefinite size + @param[in] tag_handler how CBOR tags should be treated + @return whether object creation completed + */ + bool get_cbor_object(const std::size_t len, + const cbor_tag_handler_t tag_handler) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(len))) + { + return false; + } + + string_t key; + if (len != std::size_t(-1)) + { + for (std::size_t i = 0; i < len; ++i) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) + { + return false; + } + key.clear(); + } + } + else + { + while (get() != 0xFF) + { + if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) + { + return false; + } + key.clear(); + } + } + + return sax->end_object(); + } + + ///////////// + // MsgPack // + ///////////// + + /*! + @return whether a valid MessagePack value was passed to the SAX parser + */ + bool parse_msgpack_internal() + { + switch (get()) + { + // EOF + case std::char_traits::eof(): + return unexpect_eof(input_format_t::msgpack, "value"); + + // positive fixint + 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 sax->number_unsigned(static_cast(current)); + + // fixmap + 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(static_cast(static_cast(current) & 0x0Fu)); + + // fixarray + 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(static_cast(static_cast(current) & 0x0Fu)); + + // fixstr + 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: + case 0xD9: // str 8 + case 0xDA: // str 16 + case 0xDB: // str 32 + { + string_t s; + return get_msgpack_string(s) && sax->string(s); + } + + case 0xC0: // nil + return sax->null(); + + case 0xC2: // false + return sax->boolean(false); + + case 0xC3: // true + return sax->boolean(true); + + case 0xC4: // bin 8 + case 0xC5: // bin 16 + case 0xC6: // bin 32 + case 0xC7: // ext 8 + case 0xC8: // ext 16 + case 0xC9: // ext 32 + case 0xD4: // fixext 1 + case 0xD5: // fixext 2 + case 0xD6: // fixext 4 + case 0xD7: // fixext 8 + case 0xD8: // fixext 16 + { + binary_t b; + return get_msgpack_binary(b) && sax->binary(b); + } + + case 0xCA: // float 32 + { + float number{}; + return get_number(input_format_t::msgpack, number) && sax->number_float(static_cast(number), ""); + } + + case 0xCB: // float 64 + { + double number{}; + return get_number(input_format_t::msgpack, number) && sax->number_float(static_cast(number), ""); + } + + case 0xCC: // uint 8 + { + std::uint8_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); + } + + case 0xCD: // uint 16 + { + std::uint16_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); + } + + case 0xCE: // uint 32 + { + std::uint32_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); + } + + case 0xCF: // uint 64 + { + std::uint64_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); + } + + case 0xD0: // int 8 + { + std::int8_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + } + + case 0xD1: // int 16 + { + std::int16_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + } + + case 0xD2: // int 32 + { + std::int32_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + } + + case 0xD3: // int 64 + { + std::int64_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + } + + case 0xDC: // array 16 + { + std::uint16_t len{}; + return get_number(input_format_t::msgpack, len) && get_msgpack_array(static_cast(len)); + } + + case 0xDD: // array 32 + { + std::uint32_t len{}; + return get_number(input_format_t::msgpack, len) && get_msgpack_array(static_cast(len)); + } + + case 0xDE: // map 16 + { + std::uint16_t len{}; + return get_number(input_format_t::msgpack, len) && get_msgpack_object(static_cast(len)); + } + + case 0xDF: // map 32 + { + std::uint32_t len{}; + return get_number(input_format_t::msgpack, len) && get_msgpack_object(static_cast(len)); + } + + // negative fixint + 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 sax->number_integer(static_cast(current)); + + default: // anything else + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::msgpack, "invalid byte: 0x" + last_token, "value"), BasicJsonType())); + } + } + } + + /*! + @brief reads a MessagePack string + + This function first reads starting bytes to determine the expected + string length and then copies this number of bytes into a string. + + @param[out] result created string + + @return whether string creation completed + */ + bool get_msgpack_string(string_t& result) + { + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::msgpack, "string"))) + { + return false; + } + + switch (current) + { + // fixstr + 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(input_format_t::msgpack, static_cast(current) & 0x1Fu, result); + } + + case 0xD9: // str 8 + { + std::uint8_t len{}; + return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); + } + + case 0xDA: // str 16 + { + std::uint16_t len{}; + return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); + } + + case 0xDB: // str 32 + { + std::uint32_t len{}; + return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); + } + + default: + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::msgpack, "expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x" + last_token, "string"), BasicJsonType())); + } + } + } + + /*! + @brief reads a MessagePack byte array + + This function first reads starting bytes to determine the expected + byte array length and then copies this number of bytes into a byte array. + + @param[out] result created byte array + + @return whether byte array creation completed + */ + bool get_msgpack_binary(binary_t& result) + { + // helper function to set the subtype + auto assign_and_return_true = [&result](std::int8_t subtype) + { + result.set_subtype(static_cast(subtype)); + return true; + }; + + switch (current) + { + case 0xC4: // bin 8 + { + std::uint8_t len{}; + return get_number(input_format_t::msgpack, len) && + get_binary(input_format_t::msgpack, len, result); + } + + case 0xC5: // bin 16 + { + std::uint16_t len{}; + return get_number(input_format_t::msgpack, len) && + get_binary(input_format_t::msgpack, len, result); + } + + case 0xC6: // bin 32 + { + std::uint32_t len{}; + return get_number(input_format_t::msgpack, len) && + get_binary(input_format_t::msgpack, len, result); + } + + case 0xC7: // ext 8 + { + std::uint8_t len{}; + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, len) && + get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, len, result) && + assign_and_return_true(subtype); + } + + case 0xC8: // ext 16 + { + std::uint16_t len{}; + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, len) && + get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, len, result) && + assign_and_return_true(subtype); + } + + case 0xC9: // ext 32 + { + std::uint32_t len{}; + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, len) && + get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, len, result) && + assign_and_return_true(subtype); + } + + case 0xD4: // fixext 1 + { + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, 1, result) && + assign_and_return_true(subtype); + } + + case 0xD5: // fixext 2 + { + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, 2, result) && + assign_and_return_true(subtype); + } + + case 0xD6: // fixext 4 + { + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, 4, result) && + assign_and_return_true(subtype); + } + + case 0xD7: // fixext 8 + { + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, 8, result) && + assign_and_return_true(subtype); + } + + case 0xD8: // fixext 16 + { + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, 16, result) && + assign_and_return_true(subtype); + } + + default: // LCOV_EXCL_LINE + return false; // LCOV_EXCL_LINE + } + } + + /*! + @param[in] len the length of the array + @return whether array creation completed + */ + bool get_msgpack_array(const std::size_t len) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(len))) + { + return false; + } + + for (std::size_t i = 0; i < len; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!parse_msgpack_internal())) + { + return false; + } + } + + return sax->end_array(); + } + + /*! + @param[in] len the length of the object + @return whether object creation completed + */ + bool get_msgpack_object(const std::size_t len) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(len))) + { + return false; + } + + string_t key; + for (std::size_t i = 0; i < len; ++i) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!get_msgpack_string(key) || !sax->key(key))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_msgpack_internal())) + { + return false; + } + key.clear(); + } + + return sax->end_object(); + } + + //////////// + // UBJSON // + //////////// + + /*! + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + + @return whether a valid UBJSON value was passed to the SAX parser + */ + bool parse_ubjson_internal(const bool get_char = true) + { + return get_ubjson_value(get_char ? get_ignore_noop() : current); + } + + /*! + @brief reads a UBJSON string + + This function is either called after reading the 'S' byte explicitly + indicating a string, or in case of an object key where the 'S' byte can be + left out. + + @param[out] result created string + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + + @return whether string creation completed + */ + bool get_ubjson_string(string_t& result, const bool get_char = true) + { + if (get_char) + { + get(); // TODO(niels): may we ignore N here? + } + + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "value"))) + { + return false; + } + + switch (current) + { + case 'U': + { + std::uint8_t len{}; + return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); + } + + case 'i': + { + std::int8_t len{}; + return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); + } + + case 'I': + { + std::int16_t len{}; + return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); + } + + case 'l': + { + std::int32_t len{}; + return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); + } + + case 'L': + { + std::int64_t len{}; + return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); + } + + default: + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "expected length type specification (U, i, I, l, L); last byte: 0x" + last_token, "string"), BasicJsonType())); + } + } + + /*! + @param[out] result determined size + @return whether size determination completed + */ + bool get_ubjson_size_value(std::size_t& result) + { + switch (get_ignore_noop()) + { + case 'U': + { + std::uint8_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) + { + return false; + } + result = static_cast(number); + return true; + } + + case 'i': + { + std::int8_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) + { + return false; + } + result = static_cast(number); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char + return true; + } + + case 'I': + { + std::int16_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) + { + return false; + } + result = static_cast(number); + return true; + } + + case 'l': + { + std::int32_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) + { + return false; + } + result = static_cast(number); + return true; + } + + case 'L': + { + std::int64_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) + { + return false; + } + result = static_cast(number); + return true; + } + + default: + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "expected length type specification (U, i, I, l, L) after '#'; last byte: 0x" + last_token, "size"), BasicJsonType())); + } + } + } + + /*! + @brief determine the type and size for a container + + In the optimized UBJSON format, a type and a size can be provided to allow + for a more compact representation. + + @param[out] result pair of the size and the type + + @return whether pair creation completed + */ + bool get_ubjson_size_type(std::pair& result) + { + result.first = string_t::npos; // size + result.second = 0; // type + + get_ignore_noop(); + + if (current == '$') + { + result.second = get(); // must not ignore 'N', because 'N' maybe the type + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "type"))) + { + return false; + } + + get_ignore_noop(); + if (JSON_HEDLEY_UNLIKELY(current != '#')) + { + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "value"))) + { + return false; + } + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::ubjson, "expected '#' after type information; last byte: 0x" + last_token, "size"), BasicJsonType())); + } + + return get_ubjson_size_value(result.first); + } + + if (current == '#') + { + return get_ubjson_size_value(result.first); + } + + return true; + } + + /*! + @param prefix the previously read or set type prefix + @return whether value creation completed + */ + bool get_ubjson_value(const char_int_type prefix) + { + switch (prefix) + { + case std::char_traits::eof(): // EOF + return unexpect_eof(input_format_t::ubjson, "value"); + + case 'T': // true + return sax->boolean(true); + case 'F': // false + return sax->boolean(false); + + case 'Z': // null + return sax->null(); + + case 'U': + { + std::uint8_t number{}; + return get_number(input_format_t::ubjson, number) && sax->number_unsigned(number); + } + + case 'i': + { + std::int8_t number{}; + return get_number(input_format_t::ubjson, number) && sax->number_integer(number); + } + + case 'I': + { + std::int16_t number{}; + return get_number(input_format_t::ubjson, number) && sax->number_integer(number); + } + + case 'l': + { + std::int32_t number{}; + return get_number(input_format_t::ubjson, number) && sax->number_integer(number); + } + + case 'L': + { + std::int64_t number{}; + return get_number(input_format_t::ubjson, number) && sax->number_integer(number); + } + + case 'd': + { + float number{}; + return get_number(input_format_t::ubjson, number) && sax->number_float(static_cast(number), ""); + } + + case 'D': + { + double number{}; + return get_number(input_format_t::ubjson, number) && sax->number_float(static_cast(number), ""); + } + + case 'H': + { + return get_ubjson_high_precision_number(); + } + + case 'C': // char + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "char"))) + { + return false; + } + if (JSON_HEDLEY_UNLIKELY(current > 127)) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "byte after 'C' must be in range 0x00..0x7F; last byte: 0x" + last_token, "char"), BasicJsonType())); + } + string_t s(1, static_cast(current)); + return sax->string(s); + } + + case 'S': // string + { + string_t s; + return get_ubjson_string(s) && sax->string(s); + } + + case '[': // array + return get_ubjson_array(); + + case '{': // object + return get_ubjson_object(); + + default: // anything else + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::ubjson, "invalid byte: 0x" + last_token, "value"), BasicJsonType())); + } + } + } + + /*! + @return whether array creation completed + */ + bool get_ubjson_array() + { + std::pair size_and_type; + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) + { + return false; + } + + if (size_and_type.first != string_t::npos) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(size_and_type.first))) + { + return false; + } + + if (size_and_type.second != 0) + { + if (size_and_type.second != 'N') + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) + { + return false; + } + } + } + } + else + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) + { + return false; + } + } + } + } + else + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(std::size_t(-1)))) + { + return false; + } + + while (current != ']') + { + if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal(false))) + { + return false; + } + get_ignore_noop(); + } + } + + return sax->end_array(); + } + + /*! + @return whether object creation completed + */ + bool get_ubjson_object() + { + std::pair size_and_type; + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) + { + return false; + } + + string_t key; + if (size_and_type.first != string_t::npos) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(size_and_type.first))) + { + return false; + } + + if (size_and_type.second != 0) + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key))) + { + return false; + } + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) + { + return false; + } + key.clear(); + } + } + else + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key))) + { + return false; + } + if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) + { + return false; + } + key.clear(); + } + } + } + else + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(std::size_t(-1)))) + { + return false; + } + + while (current != '}') + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key, false) || !sax->key(key))) + { + return false; + } + if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) + { + return false; + } + get_ignore_noop(); + key.clear(); + } + } + + return sax->end_object(); + } + + // Note, no reader for UBJSON binary types is implemented because they do + // not exist + + bool get_ubjson_high_precision_number() + { + // get size of following number string + std::size_t size{}; + auto res = get_ubjson_size_value(size); + if (JSON_HEDLEY_UNLIKELY(!res)) + { + return res; + } + + // get number string + std::vector number_vector; + for (std::size_t i = 0; i < size; ++i) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "number"))) + { + return false; + } + number_vector.push_back(static_cast(current)); + } + + // parse number string + using ia_type = decltype(detail::input_adapter(number_vector)); + auto number_lexer = detail::lexer(detail::input_adapter(number_vector), false); + const auto result_number = number_lexer.scan(); + const auto number_string = number_lexer.get_token_string(); + const auto result_remainder = number_lexer.scan(); + + using token_type = typename detail::lexer_base::token_type; + + if (JSON_HEDLEY_UNLIKELY(result_remainder != token_type::end_of_input)) + { + return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, exception_message(input_format_t::ubjson, "invalid number text: " + number_lexer.get_token_string(), "high-precision number"), BasicJsonType())); + } + + switch (result_number) + { + case token_type::value_integer: + return sax->number_integer(number_lexer.get_number_integer()); + case token_type::value_unsigned: + return sax->number_unsigned(number_lexer.get_number_unsigned()); + case token_type::value_float: + return sax->number_float(number_lexer.get_number_float(), std::move(number_string)); + default: + return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, exception_message(input_format_t::ubjson, "invalid number text: " + number_lexer.get_token_string(), "high-precision number"), BasicJsonType())); + } + } + + /////////////////////// + // Utility functions // + /////////////////////// + + /*! + @brief get next character from the input + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns a -'ve valued + `std::char_traits::eof()` in that case. + + @return character read from the input + */ + char_int_type get() + { + ++chars_read; + return current = ia.get_character(); + } + + /*! + @return character read from the input after ignoring all 'N' entries + */ + char_int_type get_ignore_noop() + { + do + { + get(); + } + while (current == 'N'); + + return current; + } + + /* + @brief read a number from the input + + @tparam NumberType the type of the number + @param[in] format the current format (for diagnostics) + @param[out] result number of type @a NumberType + + @return whether conversion completed + + @note This function needs to respect the system's endianess, because + bytes in CBOR, MessagePack, and UBJSON are stored in network order + (big endian) and therefore need reordering on little endian systems. + */ + template + bool get_number(const input_format_t format, NumberType& result) + { + // step 1: read input into array with system's byte order + std::array vec{}; + for (std::size_t i = 0; i < sizeof(NumberType); ++i) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "number"))) + { + return false; + } + + // reverse byte order prior to conversion if necessary + if (is_little_endian != InputIsLittleEndian) + { + vec[sizeof(NumberType) - i - 1] = static_cast(current); + } + else + { + vec[i] = static_cast(current); // LCOV_EXCL_LINE + } + } + + // step 2: convert array into number of type T and return + std::memcpy(&result, vec.data(), sizeof(NumberType)); + return true; + } + + /*! + @brief create a string by reading characters from the input + + @tparam NumberType the type of the number + @param[in] format the current format (for diagnostics) + @param[in] len number of characters to read + @param[out] result string created by reading @a len bytes + + @return whether string creation completed + + @note We can not reserve @a len bytes for the result, because @a len + may be too large. Usually, @ref unexpect_eof() detects the end of + the input before we run out of string memory. + */ + template + bool get_string(const input_format_t format, + const NumberType len, + string_t& result) + { + bool success = true; + for (NumberType i = 0; i < len; i++) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "string"))) + { + success = false; + break; + } + result.push_back(static_cast(current)); + } + return success; + } + + /*! + @brief create a byte array by reading bytes from the input + + @tparam NumberType the type of the number + @param[in] format the current format (for diagnostics) + @param[in] len number of bytes to read + @param[out] result byte array created by reading @a len bytes + + @return whether byte array creation completed + + @note We can not reserve @a len bytes for the result, because @a len + may be too large. Usually, @ref unexpect_eof() detects the end of + the input before we run out of memory. + */ + template + bool get_binary(const input_format_t format, + const NumberType len, + binary_t& result) + { + bool success = true; + for (NumberType i = 0; i < len; i++) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "binary"))) + { + success = false; + break; + } + result.push_back(static_cast(current)); + } + return success; + } + + /*! + @param[in] format the current format (for diagnostics) + @param[in] context further context information (for diagnostics) + @return whether the last read character is not EOF + */ + JSON_HEDLEY_NON_NULL(3) + bool unexpect_eof(const input_format_t format, const char* context) const + { + if (JSON_HEDLEY_UNLIKELY(current == std::char_traits::eof())) + { + return sax->parse_error(chars_read, "", + parse_error::create(110, chars_read, exception_message(format, "unexpected end of input", context), BasicJsonType())); + } + return true; + } + + /*! + @return a string representation of the last read byte + */ + std::string get_token_string() const + { + std::array cr{{}}; + (std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(current)); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + return std::string{cr.data()}; + } + + /*! + @param[in] format the current format + @param[in] detail a detailed error message + @param[in] context further context information + @return a message string to use in the parse_error exceptions + */ + std::string exception_message(const input_format_t format, + const std::string& detail, + const std::string& context) const + { + std::string error_msg = "syntax error while parsing "; + + switch (format) + { + case input_format_t::cbor: + error_msg += "CBOR"; + break; + + case input_format_t::msgpack: + error_msg += "MessagePack"; + break; + + case input_format_t::ubjson: + error_msg += "UBJSON"; + break; + + case input_format_t::bson: + error_msg += "BSON"; + break; + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } + + return error_msg + " " + context + ": " + detail; + } + + private: + /// input adapter + InputAdapterType ia; + + /// the current character + char_int_type current = std::char_traits::eof(); + + /// the number of characters read + std::size_t chars_read = 0; + + /// whether we can assume little endianess + const bool is_little_endian = little_endianess(); + + /// the SAX parser + json_sax_t* sax = nullptr; +}; +} // namespace detail +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/input/input_adapters.hpp b/lib/json/include/nlohmann/detail/input/input_adapters.hpp new file mode 100644 index 0000000..dced9bf --- /dev/null +++ b/lib/json/include/nlohmann/detail/input/input_adapters.hpp @@ -0,0 +1,476 @@ +#pragma once + +#include // array +#include // size_t +#include //FILE * +#include // strlen +#include // istream +#include // begin, end, iterator_traits, random_access_iterator_tag, distance, next +#include // shared_ptr, make_shared, addressof +#include // accumulate +#include // string, char_traits +#include // enable_if, is_base_of, is_pointer, is_integral, remove_pointer +#include // pair, declval + +#include +#include + +namespace nlohmann +{ +namespace detail +{ +/// the supported input formats +enum class input_format_t { json, cbor, msgpack, ubjson, bson }; + +//////////////////// +// input adapters // +//////////////////// + +/*! +Input adapter for stdio file access. This adapter read only 1 byte and do not use any + buffer. This adapter is a very low level adapter. +*/ +class file_input_adapter +{ + public: + using char_type = char; + + JSON_HEDLEY_NON_NULL(2) + explicit file_input_adapter(std::FILE* f) noexcept + : m_file(f) + {} + + // make class move-only + file_input_adapter(const file_input_adapter&) = delete; + file_input_adapter(file_input_adapter&&) noexcept = default; + file_input_adapter& operator=(const file_input_adapter&) = delete; + file_input_adapter& operator=(file_input_adapter&&) = delete; + ~file_input_adapter() = default; + + std::char_traits::int_type get_character() noexcept + { + return std::fgetc(m_file); + } + + private: + /// the file pointer to read from + std::FILE* m_file; +}; + + +/*! +Input adapter for a (caching) istream. Ignores a UFT Byte Order Mark at +beginning of input. Does not support changing the underlying std::streambuf +in mid-input. Maintains underlying std::istream and std::streambuf to support +subsequent use of standard std::istream operations to process any input +characters following those used in parsing the JSON input. Clears the +std::istream flags; any input errors (e.g., EOF) will be detected by the first +subsequent call for input from the std::istream. +*/ +class input_stream_adapter +{ + public: + using char_type = char; + + ~input_stream_adapter() + { + // clear stream flags; we use underlying streambuf I/O, do not + // maintain ifstream flags, except eof + if (is != nullptr) + { + is->clear(is->rdstate() & std::ios::eofbit); + } + } + + explicit input_stream_adapter(std::istream& i) + : is(&i), sb(i.rdbuf()) + {} + + // delete because of pointer members + input_stream_adapter(const input_stream_adapter&) = delete; + input_stream_adapter& operator=(input_stream_adapter&) = delete; + input_stream_adapter& operator=(input_stream_adapter&&) = delete; + + input_stream_adapter(input_stream_adapter&& rhs) noexcept + : is(rhs.is), sb(rhs.sb) + { + rhs.is = nullptr; + rhs.sb = nullptr; + } + + // std::istream/std::streambuf use std::char_traits::to_int_type, to + // ensure that std::char_traits::eof() and the character 0xFF do not + // end up as the same value, eg. 0xFFFFFFFF. + std::char_traits::int_type get_character() + { + auto res = sb->sbumpc(); + // set eof manually, as we don't use the istream interface. + if (JSON_HEDLEY_UNLIKELY(res == std::char_traits::eof())) + { + is->clear(is->rdstate() | std::ios::eofbit); + } + return res; + } + + private: + /// the associated input stream + std::istream* is = nullptr; + std::streambuf* sb = nullptr; +}; + +// General-purpose iterator-based adapter. It might not be as fast as +// theoretically possible for some containers, but it is extremely versatile. +template +class iterator_input_adapter +{ + public: + using char_type = typename std::iterator_traits::value_type; + + iterator_input_adapter(IteratorType first, IteratorType last) + : current(std::move(first)), end(std::move(last)) + {} + + typename std::char_traits::int_type get_character() + { + if (JSON_HEDLEY_LIKELY(current != end)) + { + auto result = std::char_traits::to_int_type(*current); + std::advance(current, 1); + return result; + } + + return std::char_traits::eof(); + } + + private: + IteratorType current; + IteratorType end; + + template + friend struct wide_string_input_helper; + + bool empty() const + { + return current == end; + } +}; + + +template +struct wide_string_input_helper; + +template +struct wide_string_input_helper +{ + // UTF-32 + static void fill_buffer(BaseInputAdapter& input, + std::array::int_type, 4>& utf8_bytes, + size_t& utf8_bytes_index, + size_t& utf8_bytes_filled) + { + utf8_bytes_index = 0; + + if (JSON_HEDLEY_UNLIKELY(input.empty())) + { + utf8_bytes[0] = std::char_traits::eof(); + utf8_bytes_filled = 1; + } + else + { + // get the current character + const auto wc = input.get_character(); + + // UTF-32 to UTF-8 encoding + if (wc < 0x80) + { + utf8_bytes[0] = static_cast::int_type>(wc); + utf8_bytes_filled = 1; + } + else if (wc <= 0x7FF) + { + utf8_bytes[0] = static_cast::int_type>(0xC0u | ((static_cast(wc) >> 6u) & 0x1Fu)); + utf8_bytes[1] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); + utf8_bytes_filled = 2; + } + else if (wc <= 0xFFFF) + { + utf8_bytes[0] = static_cast::int_type>(0xE0u | ((static_cast(wc) >> 12u) & 0x0Fu)); + utf8_bytes[1] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 6u) & 0x3Fu)); + utf8_bytes[2] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); + utf8_bytes_filled = 3; + } + else if (wc <= 0x10FFFF) + { + utf8_bytes[0] = static_cast::int_type>(0xF0u | ((static_cast(wc) >> 18u) & 0x07u)); + utf8_bytes[1] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 12u) & 0x3Fu)); + utf8_bytes[2] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 6u) & 0x3Fu)); + utf8_bytes[3] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); + utf8_bytes_filled = 4; + } + else + { + // unknown character + utf8_bytes[0] = static_cast::int_type>(wc); + utf8_bytes_filled = 1; + } + } + } +}; + +template +struct wide_string_input_helper +{ + // UTF-16 + static void fill_buffer(BaseInputAdapter& input, + std::array::int_type, 4>& utf8_bytes, + size_t& utf8_bytes_index, + size_t& utf8_bytes_filled) + { + utf8_bytes_index = 0; + + if (JSON_HEDLEY_UNLIKELY(input.empty())) + { + utf8_bytes[0] = std::char_traits::eof(); + utf8_bytes_filled = 1; + } + else + { + // get the current character + const auto wc = input.get_character(); + + // UTF-16 to UTF-8 encoding + if (wc < 0x80) + { + utf8_bytes[0] = static_cast::int_type>(wc); + utf8_bytes_filled = 1; + } + else if (wc <= 0x7FF) + { + utf8_bytes[0] = static_cast::int_type>(0xC0u | ((static_cast(wc) >> 6u))); + utf8_bytes[1] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); + utf8_bytes_filled = 2; + } + else if (0xD800 > wc || wc >= 0xE000) + { + utf8_bytes[0] = static_cast::int_type>(0xE0u | ((static_cast(wc) >> 12u))); + utf8_bytes[1] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 6u) & 0x3Fu)); + utf8_bytes[2] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); + utf8_bytes_filled = 3; + } + else + { + if (JSON_HEDLEY_UNLIKELY(!input.empty())) + { + const auto wc2 = static_cast(input.get_character()); + const auto charcode = 0x10000u + (((static_cast(wc) & 0x3FFu) << 10u) | (wc2 & 0x3FFu)); + utf8_bytes[0] = static_cast::int_type>(0xF0u | (charcode >> 18u)); + utf8_bytes[1] = static_cast::int_type>(0x80u | ((charcode >> 12u) & 0x3Fu)); + utf8_bytes[2] = static_cast::int_type>(0x80u | ((charcode >> 6u) & 0x3Fu)); + utf8_bytes[3] = static_cast::int_type>(0x80u | (charcode & 0x3Fu)); + utf8_bytes_filled = 4; + } + else + { + utf8_bytes[0] = static_cast::int_type>(wc); + utf8_bytes_filled = 1; + } + } + } + } +}; + +// Wraps another input apdater to convert wide character types into individual bytes. +template +class wide_string_input_adapter +{ + public: + using char_type = char; + + wide_string_input_adapter(BaseInputAdapter base) + : base_adapter(base) {} + + typename std::char_traits::int_type get_character() noexcept + { + // check if buffer needs to be filled + if (utf8_bytes_index == utf8_bytes_filled) + { + fill_buffer(); + + JSON_ASSERT(utf8_bytes_filled > 0); + JSON_ASSERT(utf8_bytes_index == 0); + } + + // use buffer + JSON_ASSERT(utf8_bytes_filled > 0); + JSON_ASSERT(utf8_bytes_index < utf8_bytes_filled); + return utf8_bytes[utf8_bytes_index++]; + } + + private: + BaseInputAdapter base_adapter; + + template + void fill_buffer() + { + wide_string_input_helper::fill_buffer(base_adapter, utf8_bytes, utf8_bytes_index, utf8_bytes_filled); + } + + /// a buffer for UTF-8 bytes + std::array::int_type, 4> utf8_bytes = {{0, 0, 0, 0}}; + + /// index to the utf8_codes array for the next valid byte + std::size_t utf8_bytes_index = 0; + /// number of valid bytes in the utf8_codes array + std::size_t utf8_bytes_filled = 0; +}; + + +template +struct iterator_input_adapter_factory +{ + using iterator_type = IteratorType; + using char_type = typename std::iterator_traits::value_type; + using adapter_type = iterator_input_adapter; + + static adapter_type create(IteratorType first, IteratorType last) + { + return adapter_type(std::move(first), std::move(last)); + } +}; + +template +struct is_iterator_of_multibyte +{ + using value_type = typename std::iterator_traits::value_type; + enum + { + value = sizeof(value_type) > 1 + }; +}; + +template +struct iterator_input_adapter_factory::value>> +{ + using iterator_type = IteratorType; + using char_type = typename std::iterator_traits::value_type; + using base_adapter_type = iterator_input_adapter; + using adapter_type = wide_string_input_adapter; + + static adapter_type create(IteratorType first, IteratorType last) + { + return adapter_type(base_adapter_type(std::move(first), std::move(last))); + } +}; + +// General purpose iterator-based input +template +typename iterator_input_adapter_factory::adapter_type input_adapter(IteratorType first, IteratorType last) +{ + using factory_type = iterator_input_adapter_factory; + return factory_type::create(first, last); +} + +// Convenience shorthand from container to iterator +// Enables ADL on begin(container) and end(container) +// Encloses the using declarations in namespace for not to leak them to outside scope + +namespace container_input_adapter_factory_impl +{ + +using std::begin; +using std::end; + +template +struct container_input_adapter_factory {}; + +template +struct container_input_adapter_factory< ContainerType, + void_t()), end(std::declval()))>> + { + using adapter_type = decltype(input_adapter(begin(std::declval()), end(std::declval()))); + + static adapter_type create(const ContainerType& container) +{ + return input_adapter(begin(container), end(container)); +} + }; + +} // namespace container_input_adapter_factory_impl + +template +typename container_input_adapter_factory_impl::container_input_adapter_factory::adapter_type input_adapter(const ContainerType& container) +{ + return container_input_adapter_factory_impl::container_input_adapter_factory::create(container); +} + +// Special cases with fast paths +inline file_input_adapter input_adapter(std::FILE* file) +{ + return file_input_adapter(file); +} + +inline input_stream_adapter input_adapter(std::istream& stream) +{ + return input_stream_adapter(stream); +} + +inline input_stream_adapter input_adapter(std::istream&& stream) +{ + return input_stream_adapter(stream); +} + +using contiguous_bytes_input_adapter = decltype(input_adapter(std::declval(), std::declval())); + +// Null-delimited strings, and the like. +template < typename CharT, + typename std::enable_if < + std::is_pointer::value&& + !std::is_array::value&& + std::is_integral::type>::value&& + sizeof(typename std::remove_pointer::type) == 1, + int >::type = 0 > +contiguous_bytes_input_adapter input_adapter(CharT b) +{ + auto length = std::strlen(reinterpret_cast(b)); + const auto* ptr = reinterpret_cast(b); + return input_adapter(ptr, ptr + length); +} + +template +auto input_adapter(T (&array)[N]) -> decltype(input_adapter(array, array + N)) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +{ + return input_adapter(array, array + N); +} + +// This class only handles inputs of input_buffer_adapter type. +// It's required so that expressions like {ptr, len} can be implicitely casted +// to the correct adapter. +class span_input_adapter +{ + public: + template < typename CharT, + typename std::enable_if < + std::is_pointer::value&& + std::is_integral::type>::value&& + sizeof(typename std::remove_pointer::type) == 1, + int >::type = 0 > + span_input_adapter(CharT b, std::size_t l) + : ia(reinterpret_cast(b), reinterpret_cast(b) + l) {} + + template::iterator_category, std::random_access_iterator_tag>::value, + int>::type = 0> + span_input_adapter(IteratorType first, IteratorType last) + : ia(input_adapter(first, last)) {} + + contiguous_bytes_input_adapter&& get() + { + return std::move(ia); // NOLINT(hicpp-move-const-arg,performance-move-const-arg) + } + + private: + contiguous_bytes_input_adapter ia; +}; +} // namespace detail +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/input/json_sax.hpp b/lib/json/include/nlohmann/detail/input/json_sax.hpp new file mode 100644 index 0000000..67278f8 --- /dev/null +++ b/lib/json/include/nlohmann/detail/input/json_sax.hpp @@ -0,0 +1,711 @@ +#pragma once + +#include +#include // string +#include // move +#include // vector + +#include +#include + +namespace nlohmann +{ + +/*! +@brief SAX interface + +This class describes the SAX interface used by @ref nlohmann::json::sax_parse. +Each function is called in different situations while the input is parsed. The +boolean return value informs the parser whether to continue processing the +input. +*/ +template +struct json_sax +{ + 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 binary_t = typename BasicJsonType::binary_t; + + /*! + @brief a null value was read + @return whether parsing should proceed + */ + virtual bool null() = 0; + + /*! + @brief a boolean value was read + @param[in] val boolean value + @return whether parsing should proceed + */ + virtual bool boolean(bool val) = 0; + + /*! + @brief an integer number was read + @param[in] val integer value + @return whether parsing should proceed + */ + virtual bool number_integer(number_integer_t val) = 0; + + /*! + @brief an unsigned integer number was read + @param[in] val unsigned integer value + @return whether parsing should proceed + */ + virtual bool number_unsigned(number_unsigned_t val) = 0; + + /*! + @brief an floating-point number was read + @param[in] val floating-point value + @param[in] s raw token value + @return whether parsing should proceed + */ + virtual bool number_float(number_float_t val, const string_t& s) = 0; + + /*! + @brief a string was read + @param[in] val string value + @return whether parsing should proceed + @note It is safe to move the passed string. + */ + virtual bool string(string_t& val) = 0; + + /*! + @brief a binary string was read + @param[in] val binary value + @return whether parsing should proceed + @note It is safe to move the passed binary. + */ + virtual bool binary(binary_t& val) = 0; + + /*! + @brief the beginning of an object was read + @param[in] elements number of object elements or -1 if unknown + @return whether parsing should proceed + @note binary formats may report the number of elements + */ + virtual bool start_object(std::size_t elements) = 0; + + /*! + @brief an object key was read + @param[in] val object key + @return whether parsing should proceed + @note It is safe to move the passed string. + */ + virtual bool key(string_t& val) = 0; + + /*! + @brief the end of an object was read + @return whether parsing should proceed + */ + virtual bool end_object() = 0; + + /*! + @brief the beginning of an array was read + @param[in] elements number of array elements or -1 if unknown + @return whether parsing should proceed + @note binary formats may report the number of elements + */ + virtual bool start_array(std::size_t elements) = 0; + + /*! + @brief the end of an array was read + @return whether parsing should proceed + */ + virtual bool end_array() = 0; + + /*! + @brief a parse error occurred + @param[in] position the position in the input where the error occurs + @param[in] last_token the last read token + @param[in] ex an exception object describing the error + @return whether parsing should proceed (must return false) + */ + virtual bool parse_error(std::size_t position, + const std::string& last_token, + const detail::exception& ex) = 0; + + json_sax() = default; + json_sax(const json_sax&) = default; + json_sax(json_sax&&) noexcept = default; + json_sax& operator=(const json_sax&) = default; + json_sax& operator=(json_sax&&) noexcept = default; + virtual ~json_sax() = default; +}; + + +namespace detail +{ +/*! +@brief SAX implementation to create a JSON value from SAX events + +This class implements the @ref json_sax interface and processes the SAX events +to create a JSON value which makes it basically a DOM parser. The structure or +hierarchy of the JSON value is managed by the stack `ref_stack` which contains +a pointer to the respective array or object for each recursion depth. + +After successful parsing, the value that is passed by reference to the +constructor contains the parsed value. + +@tparam BasicJsonType the JSON type +*/ +template +class json_sax_dom_parser +{ + public: + 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 binary_t = typename BasicJsonType::binary_t; + + /*! + @param[in,out] r reference to a JSON value that is manipulated while + parsing + @param[in] allow_exceptions_ whether parse errors yield exceptions + */ + explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true) + : root(r), allow_exceptions(allow_exceptions_) + {} + + // make class move-only + json_sax_dom_parser(const json_sax_dom_parser&) = delete; + json_sax_dom_parser(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + json_sax_dom_parser& operator=(const json_sax_dom_parser&) = delete; + json_sax_dom_parser& operator=(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + ~json_sax_dom_parser() = default; + + bool null() + { + handle_value(nullptr); + return true; + } + + bool boolean(bool val) + { + handle_value(val); + return true; + } + + bool number_integer(number_integer_t val) + { + handle_value(val); + return true; + } + + bool number_unsigned(number_unsigned_t val) + { + handle_value(val); + return true; + } + + bool number_float(number_float_t val, const string_t& /*unused*/) + { + handle_value(val); + return true; + } + + bool string(string_t& val) + { + handle_value(val); + return true; + } + + bool binary(binary_t& val) + { + handle_value(std::move(val)); + return true; + } + + bool start_object(std::size_t len) + { + ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); + + if (JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, "excessive object size: " + std::to_string(len), *ref_stack.back())); + } + + return true; + } + + bool key(string_t& val) + { + // add null at given key and store the reference for later + object_element = &(ref_stack.back()->m_value.object->operator[](val)); + return true; + } + + bool end_object() + { + ref_stack.back()->set_parents(); + ref_stack.pop_back(); + return true; + } + + bool start_array(std::size_t len) + { + ref_stack.push_back(handle_value(BasicJsonType::value_t::array)); + + if (JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, "excessive array size: " + std::to_string(len), *ref_stack.back())); + } + + return true; + } + + bool end_array() + { + ref_stack.back()->set_parents(); + ref_stack.pop_back(); + return true; + } + + template + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, + const Exception& ex) + { + errored = true; + static_cast(ex); + if (allow_exceptions) + { + JSON_THROW(ex); + } + return false; + } + + constexpr bool is_errored() const + { + return errored; + } + + private: + /*! + @invariant If the ref stack is empty, then the passed value will be the new + root. + @invariant If the ref stack contains a value, then it is an array or an + object to which we can add elements + */ + template + JSON_HEDLEY_RETURNS_NON_NULL + BasicJsonType* handle_value(Value&& v) + { + if (ref_stack.empty()) + { + root = BasicJsonType(std::forward(v)); + return &root; + } + + JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); + + if (ref_stack.back()->is_array()) + { + ref_stack.back()->m_value.array->emplace_back(std::forward(v)); + return &(ref_stack.back()->m_value.array->back()); + } + + JSON_ASSERT(ref_stack.back()->is_object()); + JSON_ASSERT(object_element); + *object_element = BasicJsonType(std::forward(v)); + return object_element; + } + + /// the parsed JSON value + BasicJsonType& root; + /// stack to model hierarchy of values + std::vector ref_stack {}; + /// helper to hold the reference for the next object element + BasicJsonType* object_element = nullptr; + /// whether a syntax error occurred + bool errored = false; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; +}; + +template +class json_sax_dom_callback_parser +{ + public: + 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 binary_t = typename BasicJsonType::binary_t; + using parser_callback_t = typename BasicJsonType::parser_callback_t; + using parse_event_t = typename BasicJsonType::parse_event_t; + + json_sax_dom_callback_parser(BasicJsonType& r, + const parser_callback_t cb, + const bool allow_exceptions_ = true) + : root(r), callback(cb), allow_exceptions(allow_exceptions_) + { + keep_stack.push_back(true); + } + + // make class move-only + json_sax_dom_callback_parser(const json_sax_dom_callback_parser&) = delete; + json_sax_dom_callback_parser(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + json_sax_dom_callback_parser& operator=(const json_sax_dom_callback_parser&) = delete; + json_sax_dom_callback_parser& operator=(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + ~json_sax_dom_callback_parser() = default; + + bool null() + { + handle_value(nullptr); + return true; + } + + bool boolean(bool val) + { + handle_value(val); + return true; + } + + bool number_integer(number_integer_t val) + { + handle_value(val); + return true; + } + + bool number_unsigned(number_unsigned_t val) + { + handle_value(val); + return true; + } + + bool number_float(number_float_t val, const string_t& /*unused*/) + { + handle_value(val); + return true; + } + + bool string(string_t& val) + { + handle_value(val); + return true; + } + + bool binary(binary_t& val) + { + handle_value(std::move(val)); + return true; + } + + bool start_object(std::size_t len) + { + // check callback for object start + const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::object_start, discarded); + keep_stack.push_back(keep); + + auto val = handle_value(BasicJsonType::value_t::object, true); + ref_stack.push_back(val.second); + + // check object limit + if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, "excessive object size: " + std::to_string(len), *ref_stack.back())); + } + + return true; + } + + bool key(string_t& val) + { + BasicJsonType k = BasicJsonType(val); + + // check callback for key + const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::key, k); + key_keep_stack.push_back(keep); + + // add discarded value at given key and store the reference for later + if (keep && ref_stack.back()) + { + object_element = &(ref_stack.back()->m_value.object->operator[](val) = discarded); + } + + return true; + } + + bool end_object() + { + if (ref_stack.back()) + { + if (!callback(static_cast(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back())) + { + // discard object + *ref_stack.back() = discarded; + } + else + { + ref_stack.back()->set_parents(); + } + } + + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(!keep_stack.empty()); + ref_stack.pop_back(); + keep_stack.pop_back(); + + if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured()) + { + // remove discarded value + for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it) + { + if (it->is_discarded()) + { + ref_stack.back()->erase(it); + break; + } + } + } + + return true; + } + + bool start_array(std::size_t len) + { + const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::array_start, discarded); + keep_stack.push_back(keep); + + auto val = handle_value(BasicJsonType::value_t::array, true); + ref_stack.push_back(val.second); + + // check array limit + if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) + { + JSON_THROW(out_of_range::create(408, "excessive array size: " + std::to_string(len), *ref_stack.back())); + } + + return true; + } + + bool end_array() + { + bool keep = true; + + if (ref_stack.back()) + { + keep = callback(static_cast(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back()); + if (keep) + { + ref_stack.back()->set_parents(); + } + else + { + // discard array + *ref_stack.back() = discarded; + } + } + + JSON_ASSERT(!ref_stack.empty()); + JSON_ASSERT(!keep_stack.empty()); + ref_stack.pop_back(); + keep_stack.pop_back(); + + // remove discarded value + if (!keep && !ref_stack.empty() && ref_stack.back()->is_array()) + { + ref_stack.back()->m_value.array->pop_back(); + } + + return true; + } + + template + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, + const Exception& ex) + { + errored = true; + static_cast(ex); + if (allow_exceptions) + { + JSON_THROW(ex); + } + return false; + } + + constexpr bool is_errored() const + { + return errored; + } + + private: + /*! + @param[in] v value to add to the JSON value we build during parsing + @param[in] skip_callback whether we should skip calling the callback + function; this is required after start_array() and + start_object() SAX events, because otherwise we would call the + callback function with an empty array or object, respectively. + + @invariant If the ref stack is empty, then the passed value will be the new + root. + @invariant If the ref stack contains a value, then it is an array or an + object to which we can add elements + + @return pair of boolean (whether value should be kept) and pointer (to the + passed value in the ref_stack hierarchy; nullptr if not kept) + */ + template + std::pair handle_value(Value&& v, const bool skip_callback = false) + { + JSON_ASSERT(!keep_stack.empty()); + + // do not handle this value if we know it would be added to a discarded + // container + if (!keep_stack.back()) + { + return {false, nullptr}; + } + + // create value + auto value = BasicJsonType(std::forward(v)); + + // check callback + const bool keep = skip_callback || callback(static_cast(ref_stack.size()), parse_event_t::value, value); + + // do not handle this value if we just learnt it shall be discarded + if (!keep) + { + return {false, nullptr}; + } + + if (ref_stack.empty()) + { + root = std::move(value); + return {true, &root}; + } + + // skip this value if we already decided to skip the parent + // (https://github.com/nlohmann/json/issues/971#issuecomment-413678360) + if (!ref_stack.back()) + { + return {false, nullptr}; + } + + // we now only expect arrays and objects + JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); + + // array + if (ref_stack.back()->is_array()) + { + ref_stack.back()->m_value.array->emplace_back(std::move(value)); + return {true, &(ref_stack.back()->m_value.array->back())}; + } + + // object + JSON_ASSERT(ref_stack.back()->is_object()); + // check if we should store an element for the current key + JSON_ASSERT(!key_keep_stack.empty()); + const bool store_element = key_keep_stack.back(); + key_keep_stack.pop_back(); + + if (!store_element) + { + return {false, nullptr}; + } + + JSON_ASSERT(object_element); + *object_element = std::move(value); + return {true, object_element}; + } + + /// the parsed JSON value + BasicJsonType& root; + /// stack to model hierarchy of values + std::vector ref_stack {}; + /// stack to manage which values to keep + std::vector keep_stack {}; + /// stack to manage which object keys to keep + std::vector key_keep_stack {}; + /// helper to hold the reference for the next object element + BasicJsonType* object_element = nullptr; + /// whether a syntax error occurred + bool errored = false; + /// callback function + const parser_callback_t callback = nullptr; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; + /// a discarded value for the callback + BasicJsonType discarded = BasicJsonType::value_t::discarded; +}; + +template +class json_sax_acceptor +{ + public: + 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 binary_t = typename BasicJsonType::binary_t; + + bool null() + { + return true; + } + + bool boolean(bool /*unused*/) + { + return true; + } + + bool number_integer(number_integer_t /*unused*/) + { + return true; + } + + bool number_unsigned(number_unsigned_t /*unused*/) + { + return true; + } + + bool number_float(number_float_t /*unused*/, const string_t& /*unused*/) + { + return true; + } + + bool string(string_t& /*unused*/) + { + return true; + } + + bool binary(binary_t& /*unused*/) + { + return true; + } + + bool start_object(std::size_t /*unused*/ = std::size_t(-1)) + { + return true; + } + + bool key(string_t& /*unused*/) + { + return true; + } + + bool end_object() + { + return true; + } + + bool start_array(std::size_t /*unused*/ = std::size_t(-1)) + { + return true; + } + + bool end_array() + { + return true; + } + + bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const detail::exception& /*unused*/) + { + return false; + } +}; +} // namespace detail + +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/input/lexer.hpp b/lib/json/include/nlohmann/detail/input/lexer.hpp new file mode 100644 index 0000000..3a47167 --- /dev/null +++ b/lib/json/include/nlohmann/detail/input/lexer.hpp @@ -0,0 +1,1623 @@ +#pragma once + +#include // array +#include // localeconv +#include // size_t +#include // snprintf +#include // strtof, strtod, strtold, strtoll, strtoull +#include // initializer_list +#include // char_traits, string +#include // move +#include // vector + +#include +#include +#include + +namespace nlohmann +{ +namespace detail +{ +/////////// +// lexer // +/////////// + +template +class lexer_base +{ + public: + /// token types for the parser + enum class token_type + { + uninitialized, ///< indicating the scanner is uninitialized + literal_true, ///< the `true` literal + literal_false, ///< the `false` literal + literal_null, ///< the `null` literal + value_string, ///< a string -- use get_string() for actual value + value_unsigned, ///< an unsigned integer -- use get_number_unsigned() for actual value + value_integer, ///< a signed integer -- use get_number_integer() for actual value + value_float, ///< an floating point number -- use get_number_float() for actual value + begin_array, ///< the character for array begin `[` + begin_object, ///< the character for object begin `{` + end_array, ///< the character for array end `]` + end_object, ///< the character for object end `}` + name_separator, ///< the name separator `:` + value_separator, ///< the value separator `,` + parse_error, ///< indicating a parse error + end_of_input, ///< indicating the end of the input buffer + literal_or_value ///< a literal or the begin of a value (only for diagnostics) + }; + + /// return name of values of type token_type (only used for errors) + JSON_HEDLEY_RETURNS_NON_NULL + JSON_HEDLEY_CONST + static const char* token_type_name(const token_type t) noexcept + { + switch (t) + { + case token_type::uninitialized: + return ""; + 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 token_type::value_unsigned: + case token_type::value_integer: + case 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 ""; + case token_type::end_of_input: + return "end of input"; + case token_type::literal_or_value: + return "'[', '{', or a literal"; + // LCOV_EXCL_START + default: // catch non-enum values + return "unknown token"; + // LCOV_EXCL_STOP + } + } +}; +/*! +@brief lexical analysis + +This class organizes the lexical analysis during JSON deserialization. +*/ +template +class lexer : public lexer_base +{ + 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 char_type = typename InputAdapterType::char_type; + using char_int_type = typename std::char_traits::int_type; + + public: + using token_type = typename lexer_base::token_type; + + explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) noexcept + : ia(std::move(adapter)) + , ignore_comments(ignore_comments_) + , decimal_point_char(static_cast(get_decimal_point())) + {} + + // delete because of pointer members + lexer(const lexer&) = delete; + lexer(lexer&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + lexer& operator=(lexer&) = delete; + lexer& operator=(lexer&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor) + ~lexer() = default; + + private: + ///////////////////// + // locales + ///////////////////// + + /// return the locale-dependent decimal point + JSON_HEDLEY_PURE + static char get_decimal_point() noexcept + { + const auto* loc = localeconv(); + JSON_ASSERT(loc != nullptr); + return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); + } + + ///////////////////// + // scan functions + ///////////////////// + + /*! + @brief get codepoint from 4 hex characters following `\u` + + For input "\u c1 c2 c3 c4" the codepoint is: + (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4 + = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0) + + Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f' + must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The + conversion is done by subtracting the offset (0x30, 0x37, and 0x57) + between the ASCII value of the character and the desired integer value. + + @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or + non-hex character) + */ + int get_codepoint() + { + // this function only makes sense after reading `\u` + JSON_ASSERT(current == 'u'); + int codepoint = 0; + + const auto factors = { 12u, 8u, 4u, 0u }; + for (const auto factor : factors) + { + get(); + + if (current >= '0' && current <= '9') + { + codepoint += static_cast((static_cast(current) - 0x30u) << factor); + } + else if (current >= 'A' && current <= 'F') + { + codepoint += static_cast((static_cast(current) - 0x37u) << factor); + } + else if (current >= 'a' && current <= 'f') + { + codepoint += static_cast((static_cast(current) - 0x57u) << factor); + } + else + { + return -1; + } + } + + JSON_ASSERT(0x0000 <= codepoint && codepoint <= 0xFFFF); + return codepoint; + } + + /*! + @brief check if the next byte(s) are inside a given range + + Adds the current byte and, for each passed range, reads a new byte and + checks if it is inside the range. If a violation was detected, set up an + error message and return false. Otherwise, return true. + + @param[in] ranges list of integers; interpreted as list of pairs of + inclusive lower and upper bound, respectively + + @pre The passed list @a ranges must have 2, 4, or 6 elements; that is, + 1, 2, or 3 pairs. This precondition is enforced by an assertion. + + @return true if and only if no range violation was detected + */ + bool next_byte_in_range(std::initializer_list ranges) + { + JSON_ASSERT(ranges.size() == 2 || ranges.size() == 4 || ranges.size() == 6); + add(current); + + for (auto range = ranges.begin(); range != ranges.end(); ++range) + { + get(); + if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range))) + { + add(current); + } + else + { + error_message = "invalid string: ill-formed UTF-8 byte"; + return false; + } + } + + return true; + } + + /*! + @brief scan a string literal + + This function scans a string according to Sect. 7 of RFC 8259. While + scanning, bytes are escaped and copied into buffer token_buffer. Then the + function returns successfully, token_buffer is *not* null-terminated (as it + may contain \0 bytes), and token_buffer.size() is the number of bytes in the + string. + + @return token_type::value_string if string could be successfully scanned, + token_type::parse_error otherwise + + @note In case of errors, variable error_message contains a textual + description. + */ + token_type scan_string() + { + // reset token_buffer (ignore opening quote) + reset(); + + // we entered the function by reading an open quote + JSON_ASSERT(current == '\"'); + + while (true) + { + // get next character + switch (get()) + { + // end of file while parsing string + case std::char_traits::eof(): + { + error_message = "invalid string: missing closing quote"; + return token_type::parse_error; + } + + // closing quote + case '\"': + { + return token_type::value_string; + } + + // escapes + case '\\': + { + switch (get()) + { + // quotation mark + case '\"': + add('\"'); + break; + // reverse solidus + case '\\': + add('\\'); + break; + // solidus + case '/': + add('/'); + break; + // backspace + case 'b': + add('\b'); + break; + // form feed + case 'f': + add('\f'); + break; + // line feed + case 'n': + add('\n'); + break; + // carriage return + case 'r': + add('\r'); + break; + // tab + case 't': + add('\t'); + break; + + // unicode escapes + case 'u': + { + const int codepoint1 = get_codepoint(); + int codepoint = codepoint1; // start with codepoint1 + + if (JSON_HEDLEY_UNLIKELY(codepoint1 == -1)) + { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } + + // check if code point is a high surrogate + if (0xD800 <= codepoint1 && codepoint1 <= 0xDBFF) + { + // expect next \uxxxx entry + if (JSON_HEDLEY_LIKELY(get() == '\\' && get() == 'u')) + { + const int codepoint2 = get_codepoint(); + + if (JSON_HEDLEY_UNLIKELY(codepoint2 == -1)) + { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } + + // check if codepoint2 is a low surrogate + if (JSON_HEDLEY_LIKELY(0xDC00 <= codepoint2 && codepoint2 <= 0xDFFF)) + { + // overwrite codepoint + codepoint = static_cast( + // high surrogate occupies the most significant 22 bits + (static_cast(codepoint1) << 10u) + // low surrogate occupies the least significant 15 bits + + static_cast(codepoint2) + // there is still the 0xD800, 0xDC00 and 0x10000 noise + // in the result so we have to subtract with: + // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 + - 0x35FDC00u); + } + else + { + error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } + else + { + error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } + else + { + if (JSON_HEDLEY_UNLIKELY(0xDC00 <= codepoint1 && codepoint1 <= 0xDFFF)) + { + error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF"; + return token_type::parse_error; + } + } + + // result of the above calculation yields a proper codepoint + JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF); + + // translate codepoint into bytes + if (codepoint < 0x80) + { + // 1-byte characters: 0xxxxxxx (ASCII) + add(static_cast(codepoint)); + } + else if (codepoint <= 0x7FF) + { + // 2-byte characters: 110xxxxx 10xxxxxx + add(static_cast(0xC0u | (static_cast(codepoint) >> 6u))); + add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); + } + else if (codepoint <= 0xFFFF) + { + // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx + add(static_cast(0xE0u | (static_cast(codepoint) >> 12u))); + add(static_cast(0x80u | ((static_cast(codepoint) >> 6u) & 0x3Fu))); + add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); + } + else + { + // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + add(static_cast(0xF0u | (static_cast(codepoint) >> 18u))); + add(static_cast(0x80u | ((static_cast(codepoint) >> 12u) & 0x3Fu))); + add(static_cast(0x80u | ((static_cast(codepoint) >> 6u) & 0x3Fu))); + add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); + } + + break; + } + + // other characters after escape + default: + error_message = "invalid string: forbidden character after backslash"; + return token_type::parse_error; + } + + break; + } + + // invalid control characters + case 0x00: + { + error_message = "invalid string: control character U+0000 (NUL) must be escaped to \\u0000"; + return token_type::parse_error; + } + + case 0x01: + { + error_message = "invalid string: control character U+0001 (SOH) must be escaped to \\u0001"; + return token_type::parse_error; + } + + case 0x02: + { + error_message = "invalid string: control character U+0002 (STX) must be escaped to \\u0002"; + return token_type::parse_error; + } + + case 0x03: + { + error_message = "invalid string: control character U+0003 (ETX) must be escaped to \\u0003"; + return token_type::parse_error; + } + + case 0x04: + { + error_message = "invalid string: control character U+0004 (EOT) must be escaped to \\u0004"; + return token_type::parse_error; + } + + case 0x05: + { + error_message = "invalid string: control character U+0005 (ENQ) must be escaped to \\u0005"; + return token_type::parse_error; + } + + case 0x06: + { + error_message = "invalid string: control character U+0006 (ACK) must be escaped to \\u0006"; + return token_type::parse_error; + } + + case 0x07: + { + error_message = "invalid string: control character U+0007 (BEL) must be escaped to \\u0007"; + return token_type::parse_error; + } + + case 0x08: + { + error_message = "invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b"; + return token_type::parse_error; + } + + case 0x09: + { + error_message = "invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t"; + return token_type::parse_error; + } + + case 0x0A: + { + error_message = "invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n"; + return token_type::parse_error; + } + + case 0x0B: + { + error_message = "invalid string: control character U+000B (VT) must be escaped to \\u000B"; + return token_type::parse_error; + } + + case 0x0C: + { + error_message = "invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f"; + return token_type::parse_error; + } + + case 0x0D: + { + error_message = "invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r"; + return token_type::parse_error; + } + + case 0x0E: + { + error_message = "invalid string: control character U+000E (SO) must be escaped to \\u000E"; + return token_type::parse_error; + } + + case 0x0F: + { + error_message = "invalid string: control character U+000F (SI) must be escaped to \\u000F"; + return token_type::parse_error; + } + + case 0x10: + { + error_message = "invalid string: control character U+0010 (DLE) must be escaped to \\u0010"; + return token_type::parse_error; + } + + case 0x11: + { + error_message = "invalid string: control character U+0011 (DC1) must be escaped to \\u0011"; + return token_type::parse_error; + } + + case 0x12: + { + error_message = "invalid string: control character U+0012 (DC2) must be escaped to \\u0012"; + return token_type::parse_error; + } + + case 0x13: + { + error_message = "invalid string: control character U+0013 (DC3) must be escaped to \\u0013"; + return token_type::parse_error; + } + + case 0x14: + { + error_message = "invalid string: control character U+0014 (DC4) must be escaped to \\u0014"; + return token_type::parse_error; + } + + case 0x15: + { + error_message = "invalid string: control character U+0015 (NAK) must be escaped to \\u0015"; + return token_type::parse_error; + } + + case 0x16: + { + error_message = "invalid string: control character U+0016 (SYN) must be escaped to \\u0016"; + return token_type::parse_error; + } + + case 0x17: + { + error_message = "invalid string: control character U+0017 (ETB) must be escaped to \\u0017"; + return token_type::parse_error; + } + + case 0x18: + { + error_message = "invalid string: control character U+0018 (CAN) must be escaped to \\u0018"; + return token_type::parse_error; + } + + case 0x19: + { + error_message = "invalid string: control character U+0019 (EM) must be escaped to \\u0019"; + return token_type::parse_error; + } + + case 0x1A: + { + error_message = "invalid string: control character U+001A (SUB) must be escaped to \\u001A"; + return token_type::parse_error; + } + + case 0x1B: + { + error_message = "invalid string: control character U+001B (ESC) must be escaped to \\u001B"; + return token_type::parse_error; + } + + case 0x1C: + { + error_message = "invalid string: control character U+001C (FS) must be escaped to \\u001C"; + return token_type::parse_error; + } + + case 0x1D: + { + error_message = "invalid string: control character U+001D (GS) must be escaped to \\u001D"; + return token_type::parse_error; + } + + case 0x1E: + { + error_message = "invalid string: control character U+001E (RS) must be escaped to \\u001E"; + return token_type::parse_error; + } + + case 0x1F: + { + error_message = "invalid string: control character U+001F (US) must be escaped to \\u001F"; + return token_type::parse_error; + } + + // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace)) + 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; + } + + // U+0080..U+07FF: bytes C2..DF 80..BF + 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_HEDLEY_UNLIKELY(!next_byte_in_range({0x80, 0xBF}))) + { + return token_type::parse_error; + } + break; + } + + // U+0800..U+0FFF: bytes E0 A0..BF 80..BF + case 0xE0: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF + // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF + 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_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+D000..U+D7FF: bytes ED 80..9F 80..BF + case 0xED: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF + case 0xF0: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF + case 0xF1: + case 0xF2: + case 0xF3: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF + case 0xF4: + { + if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // remaining bytes (80..C1 and F5..FF) are ill-formed + default: + { + error_message = "invalid string: ill-formed UTF-8 byte"; + return token_type::parse_error; + } + } + } + } + + /*! + * @brief scan a comment + * @return whether comment could be scanned successfully + */ + bool scan_comment() + { + switch (get()) + { + // single-line comments skip input until a newline or EOF is read + case '/': + { + while (true) + { + switch (get()) + { + case '\n': + case '\r': + case std::char_traits::eof(): + case '\0': + return true; + + default: + break; + } + } + } + + // multi-line comments skip input until */ is read + case '*': + { + while (true) + { + switch (get()) + { + case std::char_traits::eof(): + case '\0': + { + error_message = "invalid comment; missing closing '*/'"; + return false; + } + + case '*': + { + switch (get()) + { + case '/': + return true; + + default: + { + unget(); + continue; + } + } + } + + default: + continue; + } + } + } + + // unexpected character after reading '/' + default: + { + error_message = "invalid comment; expecting '/' or '*' after '/'"; + return false; + } + } + } + + JSON_HEDLEY_NON_NULL(2) + static void strtof(float& f, const char* str, char** endptr) noexcept + { + f = std::strtof(str, endptr); + } + + JSON_HEDLEY_NON_NULL(2) + static void strtof(double& f, const char* str, char** endptr) noexcept + { + f = std::strtod(str, endptr); + } + + JSON_HEDLEY_NON_NULL(2) + static void strtof(long double& f, const char* str, char** endptr) noexcept + { + f = std::strtold(str, endptr); + } + + /*! + @brief scan a number literal + + This function scans a string according to Sect. 6 of RFC 8259. + + The function is realized with a deterministic finite state machine derived + from the grammar described in RFC 8259. Starting in state "init", the + input is read and used to determined the next state. Only state "done" + accepts the number. State "error" is a trap state to model errors. In the + table below, "anything" means any character but the ones listed before. + + state | 0 | 1-9 | e E | + | - | . | anything + ---------|----------|----------|----------|---------|---------|----------|----------- + init | zero | any1 | [error] | [error] | minus | [error] | [error] + minus | zero | any1 | [error] | [error] | [error] | [error] | [error] + zero | done | done | exponent | done | done | decimal1 | done + any1 | any1 | any1 | exponent | done | done | decimal1 | done + decimal1 | decimal2 | decimal2 | [error] | [error] | [error] | [error] | [error] + decimal2 | decimal2 | decimal2 | exponent | done | done | done | done + exponent | any2 | any2 | [error] | sign | sign | [error] | [error] + sign | any2 | any2 | [error] | [error] | [error] | [error] | [error] + any2 | any2 | any2 | done | done | done | done | done + + The state machine is realized with one label per state (prefixed with + "scan_number_") and `goto` statements between them. The state machine + contains cycles, but any cycle can be left when EOF is read. Therefore, + the function is guaranteed to terminate. + + During scanning, the read bytes are stored in token_buffer. This string is + then converted to a signed integer, an unsigned integer, or a + floating-point number. + + @return token_type::value_unsigned, token_type::value_integer, or + token_type::value_float if number could be successfully scanned, + token_type::parse_error otherwise + + @note The scanner is independent of the current locale. Internally, the + locale's decimal point is used instead of `.` to work with the + locale-dependent converters. + */ + token_type scan_number() // lgtm [cpp/use-of-goto] + { + // reset token_buffer to store the number's bytes + reset(); + + // the type of the parsed number; initially set to unsigned; will be + // changed if minus sign, decimal point or exponent is read + token_type number_type = token_type::value_unsigned; + + // state (init): we just found out we need to scan a number + 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; + } + + // all other characters are rejected outside scan_number() + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } + +scan_number_minus: + // state: we just parsed a leading minus sign + 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: + // state: we just parse a zero (maybe with a leading minus sign) + 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: + // state: we just parsed a number 0-9 (maybe with a leading minus 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_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: + // state: we just parsed a decimal point + 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: + // we just parsed at least one number after a decimal point + 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: + // we just parsed an 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: + // we just parsed an exponent 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: + // we just parsed a number after the exponent or exponent 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: + goto scan_number_done; + } + +scan_number_done: + // unget the character after the number (we only read it to know that + // we are done scanning a number) + unget(); + + char* endptr = nullptr; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + errno = 0; + + // try to parse integers first and fall back to floats + if (number_type == token_type::value_unsigned) + { + const auto x = std::strtoull(token_buffer.data(), &endptr, 10); + + // we checked the number format before + JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); + + if (errno == 0) + { + value_unsigned = static_cast(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); + + // we checked the number format before + JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); + + if (errno == 0) + { + value_integer = static_cast(x); + if (value_integer == x) + { + return token_type::value_integer; + } + } + } + + // this code is reached if we parse a floating-point number or if an + // integer conversion above failed + strtof(value_float, token_buffer.data(), &endptr); + + // we checked the number format before + JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); + + return token_type::value_float; + } + + /*! + @param[in] literal_text the literal text to expect + @param[in] length the length of the passed literal text + @param[in] return_type the token type to return on success + */ + JSON_HEDLEY_NON_NULL(2) + token_type scan_literal(const char_type* literal_text, const std::size_t length, + token_type return_type) + { + JSON_ASSERT(std::char_traits::to_char_type(current) == literal_text[0]); + for (std::size_t i = 1; i < length; ++i) + { + if (JSON_HEDLEY_UNLIKELY(std::char_traits::to_char_type(get()) != literal_text[i])) + { + error_message = "invalid literal"; + return token_type::parse_error; + } + } + return return_type; + } + + ///////////////////// + // input management + ///////////////////// + + /// reset token_buffer; current character is beginning of token + void reset() noexcept + { + token_buffer.clear(); + token_string.clear(); + token_string.push_back(std::char_traits::to_char_type(current)); + } + + /* + @brief get next character from the input + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns a + `std::char_traits::eof()` in that case. Stores the scanned characters + for use in error messages. + + @return character read from the input + */ + char_int_type get() + { + ++position.chars_read_total; + ++position.chars_read_current_line; + + if (next_unget) + { + // just reset the next_unget variable and work with current + next_unget = false; + } + else + { + current = ia.get_character(); + } + + if (JSON_HEDLEY_LIKELY(current != std::char_traits::eof())) + { + token_string.push_back(std::char_traits::to_char_type(current)); + } + + if (current == '\n') + { + ++position.lines_read; + position.chars_read_current_line = 0; + } + + return current; + } + + /*! + @brief unget current character (read it again on next get) + + We implement unget by setting variable next_unget to true. The input is not + changed - we just simulate ungetting by modifying chars_read_total, + chars_read_current_line, and token_string. The next call to get() will + behave as if the unget character is read again. + */ + void unget() + { + next_unget = true; + + --position.chars_read_total; + + // in case we "unget" a newline, we have to also decrement the lines_read + if (position.chars_read_current_line == 0) + { + if (position.lines_read > 0) + { + --position.lines_read; + } + } + else + { + --position.chars_read_current_line; + } + + if (JSON_HEDLEY_LIKELY(current != std::char_traits::eof())) + { + JSON_ASSERT(!token_string.empty()); + token_string.pop_back(); + } + } + + /// add a character to token_buffer + void add(char_int_type c) + { + token_buffer.push_back(static_cast(c)); + } + + public: + ///////////////////// + // value getters + ///////////////////// + + /// return integer value + constexpr number_integer_t get_number_integer() const noexcept + { + return value_integer; + } + + /// return unsigned integer value + constexpr number_unsigned_t get_number_unsigned() const noexcept + { + return value_unsigned; + } + + /// return floating-point value + constexpr number_float_t get_number_float() const noexcept + { + return value_float; + } + + /// return current string value (implicitly resets the token; useful only once) + string_t& get_string() + { + return token_buffer; + } + + ///////////////////// + // diagnostics + ///////////////////// + + /// return position of last read token + constexpr position_t get_position() const noexcept + { + return position; + } + + /// return the last read token (for errors only). Will never contain EOF + /// (an arbitrary value that is not a valid char value, often -1), because + /// 255 may legitimately occur. May contain NUL, which should be escaped. + std::string get_token_string() const + { + // escape control characters + std::string result; + for (const auto c : token_string) + { + if (static_cast(c) <= '\x1F') + { + // escape control characters + std::array cs{{}}; + (std::snprintf)(cs.data(), cs.size(), "", static_cast(c)); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + result += cs.data(); + } + else + { + // add character as is + result.push_back(static_cast(c)); + } + } + + return result; + } + + /// return syntax error message + JSON_HEDLEY_RETURNS_NON_NULL + constexpr const char* get_error_message() const noexcept + { + return error_message; + } + + ///////////////////// + // actual scanner + ///////////////////// + + /*! + @brief skip the UTF-8 byte order mark + @return true iff there is no BOM or the correct BOM has been skipped + */ + bool skip_bom() + { + if (get() == 0xEF) + { + // check if we completely parse the BOM + return get() == 0xBB && get() == 0xBF; + } + + // the first character is not the beginning of the BOM; unget it to + // process is later + unget(); + return true; + } + + void skip_whitespace() + { + do + { + get(); + } + while (current == ' ' || current == '\t' || current == '\n' || current == '\r'); + } + + token_type scan() + { + // initially, skip the BOM + if (position.chars_read_total == 0 && !skip_bom()) + { + error_message = "invalid BOM; must be 0xEF 0xBB 0xBF if given"; + return token_type::parse_error; + } + + // read next character and ignore whitespace + skip_whitespace(); + + // ignore comments + while (ignore_comments && current == '/') + { + if (!scan_comment()) + { + return token_type::parse_error; + } + + // skip following whitespace + skip_whitespace(); + } + + switch (current) + { + // structural characters + 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; + + // literals + case 't': + { + std::array true_literal = {{char_type('t'), char_type('r'), char_type('u'), char_type('e')}}; + return scan_literal(true_literal.data(), true_literal.size(), token_type::literal_true); + } + case 'f': + { + std::array false_literal = {{char_type('f'), char_type('a'), char_type('l'), char_type('s'), char_type('e')}}; + return scan_literal(false_literal.data(), false_literal.size(), token_type::literal_false); + } + case 'n': + { + std::array null_literal = {{char_type('n'), char_type('u'), char_type('l'), char_type('l')}}; + return scan_literal(null_literal.data(), null_literal.size(), token_type::literal_null); + } + + // string + case '\"': + return scan_string(); + + // number + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return scan_number(); + + // end of input (the null byte is needed when parsing from + // string literals) + case '\0': + case std::char_traits::eof(): + return token_type::end_of_input; + + // error + default: + error_message = "invalid literal"; + return token_type::parse_error; + } + } + + private: + /// input adapter + InputAdapterType ia; + + /// whether comments should be ignored (true) or signaled as errors (false) + const bool ignore_comments = false; + + /// the current character + char_int_type current = std::char_traits::eof(); + + /// whether the next get() call should just return current + bool next_unget = false; + + /// the start position of the current token + position_t position {}; + + /// raw input token string (for error messages) + std::vector token_string {}; + + /// buffer for variable-length tokens (numbers, strings) + string_t token_buffer {}; + + /// a description of occurred lexer errors + const char* error_message = ""; + + // number values + number_integer_t value_integer = 0; + number_unsigned_t value_unsigned = 0; + number_float_t value_float = 0; + + /// the decimal point + const char_int_type decimal_point_char = '.'; +}; +} // namespace detail +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/input/parser.hpp b/lib/json/include/nlohmann/detail/input/parser.hpp new file mode 100644 index 0000000..90f2327 --- /dev/null +++ b/lib/json/include/nlohmann/detail/input/parser.hpp @@ -0,0 +1,492 @@ +#pragma once + +#include // isfinite +#include // uint8_t +#include // function +#include // string +#include // move +#include // vector + +#include +#include +#include +#include +#include +#include +#include + +namespace nlohmann +{ +namespace detail +{ +//////////// +// parser // +//////////// + +enum class parse_event_t : uint8_t +{ + /// the parser read `{` and started to process a JSON object + object_start, + /// the parser read `}` and finished processing a JSON object + object_end, + /// the parser read `[` and started to process a JSON array + array_start, + /// the parser read `]` and finished processing a JSON array + array_end, + /// the parser read a key of a value in an object + key, + /// the parser finished reading a JSON value + value +}; + +template +using parser_callback_t = + std::function; + +/*! +@brief syntax analysis + +This class implements a recursive descent parser. +*/ +template +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; + using token_type = typename lexer_t::token_type; + + public: + /// a parser reading from an input adapter + explicit parser(InputAdapterType&& adapter, + const parser_callback_t cb = nullptr, + const bool allow_exceptions_ = true, + const bool skip_comments = false) + : callback(cb) + , m_lexer(std::move(adapter), skip_comments) + , allow_exceptions(allow_exceptions_) + { + // read first token + get_token(); + } + + /*! + @brief public parser interface + + @param[in] strict whether to expect the last token to be EOF + @param[in,out] result parsed JSON value + + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + */ + void parse(const bool strict, BasicJsonType& result) + { + if (callback) + { + json_sax_dom_callback_parser sdp(result, callback, allow_exceptions); + sax_parse_internal(&sdp); + + // in strict mode, input must be completely read + if (strict && (get_token() != token_type::end_of_input)) + { + sdp.parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::end_of_input, "value"), BasicJsonType())); + } + + // in case of an error, return discarded value + if (sdp.is_errored()) + { + result = value_t::discarded; + return; + } + + // set top-level value to null if it was discarded by the callback + // function + if (result.is_discarded()) + { + result = nullptr; + } + } + else + { + json_sax_dom_parser sdp(result, allow_exceptions); + sax_parse_internal(&sdp); + + // in strict mode, input must be completely read + if (strict && (get_token() != token_type::end_of_input)) + { + sdp.parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), BasicJsonType())); + } + + // in case of an error, return discarded value + if (sdp.is_errored()) + { + result = value_t::discarded; + return; + } + } + + result.assert_invariant(); + } + + /*! + @brief public accept interface + + @param[in] strict whether to expect the last token to be EOF + @return whether the input is a proper JSON text + */ + bool accept(const bool strict = true) + { + json_sax_acceptor sax_acceptor; + return sax_parse(&sax_acceptor, strict); + } + + template + JSON_HEDLEY_NON_NULL(2) + bool sax_parse(SAX* sax, const bool strict = true) + { + (void)detail::is_sax_static_asserts {}; + const bool result = sax_parse_internal(sax); + + // strict mode: next byte must be EOF + if (result && strict && (get_token() != token_type::end_of_input)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), BasicJsonType())); + } + + return result; + } + + private: + template + JSON_HEDLEY_NON_NULL(2) + bool sax_parse_internal(SAX* sax) + { + // stack to remember the hierarchy of structured values we are parsing + // true = array; false = object + std::vector states; + // value to avoid a goto (see comment where set to true) + bool skip_to_state_evaluation = false; + + while (true) + { + if (!skip_to_state_evaluation) + { + // invariant: get_token() was called before each iteration + switch (last_token) + { + case token_type::begin_object: + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(std::size_t(-1)))) + { + return false; + } + + // closing } -> we are done + if (get_token() == token_type::end_object) + { + if (JSON_HEDLEY_UNLIKELY(!sax->end_object())) + { + return false; + } + break; + } + + // parse key + if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), BasicJsonType())); + } + if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) + { + return false; + } + + // parse separator (:) + if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), BasicJsonType())); + } + + // remember we are now inside an object + states.push_back(false); + + // parse values + get_token(); + continue; + } + + case token_type::begin_array: + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(std::size_t(-1)))) + { + return false; + } + + // closing ] -> we are done + if (get_token() == token_type::end_array) + { + if (JSON_HEDLEY_UNLIKELY(!sax->end_array())) + { + return false; + } + break; + } + + // remember we are now inside an array + states.push_back(true); + + // parse values (no need to call get_token) + continue; + } + + case token_type::value_float: + { + const auto res = m_lexer.get_number_float(); + + if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res))) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + out_of_range::create(406, "number overflow parsing '" + m_lexer.get_token_string() + "'", BasicJsonType())); + } + + if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string()))) + { + return false; + } + + break; + } + + case token_type::literal_false: + { + if (JSON_HEDLEY_UNLIKELY(!sax->boolean(false))) + { + return false; + } + break; + } + + case token_type::literal_null: + { + if (JSON_HEDLEY_UNLIKELY(!sax->null())) + { + return false; + } + break; + } + + case token_type::literal_true: + { + if (JSON_HEDLEY_UNLIKELY(!sax->boolean(true))) + { + return false; + } + break; + } + + case token_type::value_integer: + { + if (JSON_HEDLEY_UNLIKELY(!sax->number_integer(m_lexer.get_number_integer()))) + { + return false; + } + break; + } + + case token_type::value_string: + { + if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string()))) + { + return false; + } + break; + } + + case token_type::value_unsigned: + { + if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(m_lexer.get_number_unsigned()))) + { + return false; + } + break; + } + + case token_type::parse_error: + { + // using "uninitialized" to avoid "expected" message + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::uninitialized, "value"), BasicJsonType())); + } + + default: // the last token was unexpected + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), BasicJsonType())); + } + } + } + else + { + skip_to_state_evaluation = false; + } + + // we reached this line after we successfully parsed a value + if (states.empty()) + { + // empty stack: we reached the end of the hierarchy: done + return true; + } + + if (states.back()) // array + { + // comma -> next value + if (get_token() == token_type::value_separator) + { + // parse a new value + get_token(); + continue; + } + + // closing ] + if (JSON_HEDLEY_LIKELY(last_token == token_type::end_array)) + { + if (JSON_HEDLEY_UNLIKELY(!sax->end_array())) + { + return false; + } + + // We are done with this array. Before we can parse a + // new value, we need to evaluate the new state first. + // By setting skip_to_state_evaluation to false, we + // are effectively jumping to the beginning of this if. + JSON_ASSERT(!states.empty()); + states.pop_back(); + skip_to_state_evaluation = true; + continue; + } + + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_array, "array"), BasicJsonType())); + } + + // states.back() is false -> object + + // comma -> next value + if (get_token() == token_type::value_separator) + { + // parse key + if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::value_string)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), BasicJsonType())); + } + + if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) + { + return false; + } + + // parse separator (:) + if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), BasicJsonType())); + } + + // parse values + get_token(); + continue; + } + + // closing } + if (JSON_HEDLEY_LIKELY(last_token == token_type::end_object)) + { + if (JSON_HEDLEY_UNLIKELY(!sax->end_object())) + { + return false; + } + + // We are done with this object. Before we can parse a + // new value, we need to evaluate the new state first. + // By setting skip_to_state_evaluation to false, we + // are effectively jumping to the beginning of this if. + JSON_ASSERT(!states.empty()); + states.pop_back(); + skip_to_state_evaluation = true; + continue; + } + + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_object, "object"), BasicJsonType())); + } + } + + /// get next token from lexer + token_type get_token() + { + return last_token = m_lexer.scan(); + } + + std::string exception_message(const token_type expected, const std::string& context) + { + std::string error_msg = "syntax error "; + + if (!context.empty()) + { + error_msg += "while parsing " + context + " "; + } + + error_msg += "- "; + + 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)); + } + + return error_msg; + } + + private: + /// callback function + const parser_callback_t callback = nullptr; + /// the type of the last read token + token_type last_token = token_type::uninitialized; + /// the lexer + lexer_t m_lexer; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; +}; + +} // namespace detail +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/input/position_t.hpp b/lib/json/include/nlohmann/detail/input/position_t.hpp new file mode 100644 index 0000000..14e9649 --- /dev/null +++ b/lib/json/include/nlohmann/detail/input/position_t.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include // size_t + +namespace nlohmann +{ +namespace detail +{ +/// struct to capture the start position of the current token +struct position_t +{ + /// the total number of characters read + std::size_t chars_read_total = 0; + /// the number of characters read in the current line + std::size_t chars_read_current_line = 0; + /// the number of lines read + std::size_t lines_read = 0; + + /// conversion to size_t to preserve SAX interface + constexpr operator size_t() const + { + return chars_read_total; + } +}; + +} // namespace detail +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/iterators/internal_iterator.hpp b/lib/json/include/nlohmann/detail/iterators/internal_iterator.hpp new file mode 100644 index 0000000..2c81f72 --- /dev/null +++ b/lib/json/include/nlohmann/detail/iterators/internal_iterator.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +namespace nlohmann +{ +namespace detail +{ +/*! +@brief an iterator value + +@note This structure could easily be a union, but MSVC currently does not allow +unions members with complex constructors, see https://github.com/nlohmann/json/pull/105. +*/ +template struct internal_iterator +{ + /// iterator for JSON objects + typename BasicJsonType::object_t::iterator object_iterator {}; + /// iterator for JSON arrays + typename BasicJsonType::array_t::iterator array_iterator {}; + /// generic iterator for all other types + primitive_iterator_t primitive_iterator {}; +}; +} // namespace detail +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/iterators/iter_impl.hpp b/lib/json/include/nlohmann/detail/iterators/iter_impl.hpp new file mode 100644 index 0000000..1747a88 --- /dev/null +++ b/lib/json/include/nlohmann/detail/iterators/iter_impl.hpp @@ -0,0 +1,646 @@ +#pragma once + +#include // iterator, random_access_iterator_tag, bidirectional_iterator_tag, advance, next +#include // conditional, is_const, remove_const + +#include +#include +#include +#include +#include +#include +#include + +namespace nlohmann +{ +namespace detail +{ +// forward declare, to be able to friend it later on +template class iteration_proxy; +template class iteration_proxy_value; + +/*! +@brief a template for a bidirectional iterator for the @ref basic_json class +This class implements a both iterators (iterator and const_iterator) for the +@ref basic_json class. +@note An iterator is called *initialized* when a pointer to a JSON value has + been set (e.g., by a constructor or a copy assignment). If the iterator is + default-constructed, it is *uninitialized* and most methods are undefined. + **The library uses assertions to detect calls on uninitialized iterators.** +@requirement The class satisfies the following concept requirements: +- +[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator): + The iterator that can be moved can be moved in both directions (i.e. + incremented and decremented). +@since version 1.0.0, simplified in version 2.0.9, change to bidirectional + iterators in version 3.0.0 (see https://github.com/nlohmann/json/issues/593) +*/ +template +class iter_impl +{ + /// the iterator with BasicJsonType of different const-ness + using other_iter_impl = iter_impl::value, typename std::remove_const::type, const BasicJsonType>::type>; + /// allow basic_json to access private members + friend other_iter_impl; + friend BasicJsonType; + friend iteration_proxy; + friend iteration_proxy_value; + + using object_t = typename BasicJsonType::object_t; + using array_t = typename BasicJsonType::array_t; + // make sure BasicJsonType is basic_json or const basic_json + static_assert(is_basic_json::type>::value, + "iter_impl only accepts (const) basic_json"); + + public: + + /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17. + /// The C++ Standard has never required user-defined iterators to derive from std::iterator. + /// A user-defined iterator should provide publicly accessible typedefs named + /// iterator_category, value_type, difference_type, pointer, and reference. + /// Note that value_type is required to be non-const, even for constant iterators. + using iterator_category = std::bidirectional_iterator_tag; + + /// the type of the values when the iterator is dereferenced + using value_type = typename BasicJsonType::value_type; + /// a type to represent differences between iterators + using difference_type = typename BasicJsonType::difference_type; + /// defines a pointer to the type iterated over (value_type) + using pointer = typename std::conditional::value, + typename BasicJsonType::const_pointer, + typename BasicJsonType::pointer>::type; + /// defines a reference to the type iterated over (value_type) + using reference = + typename std::conditional::value, + typename BasicJsonType::const_reference, + typename BasicJsonType::reference>::type; + + iter_impl() = default; + ~iter_impl() = default; + iter_impl(iter_impl&&) noexcept = default; + iter_impl& operator=(iter_impl&&) noexcept = default; + + /*! + @brief constructor for a given JSON instance + @param[in] object pointer to a JSON object for this iterator + @pre object != nullptr + @post The iterator is initialized; i.e. `m_object != nullptr`. + */ + explicit iter_impl(pointer object) noexcept : m_object(object) + { + JSON_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; + } + } + } + + /*! + @note The conventional copy constructor and copy assignment are implicitly + defined. Combined with the following converting constructor and + assignment, they support: (1) copy from iterator to iterator, (2) + copy from const iterator to const iterator, and (3) conversion from + iterator to const iterator. However conversion from const iterator + to iterator is not defined. + */ + + /*! + @brief const copy constructor + @param[in] other const iterator to copy from + @note This copy constructor had to be defined explicitly to circumvent a bug + occurring on msvc v19.0 compiler (VS 2015) debug build. For more + information refer to: https://github.com/nlohmann/json/issues/1608 + */ + iter_impl(const iter_impl& other) noexcept + : m_object(other.m_object), m_it(other.m_it) + {} + + /*! + @brief converting assignment + @param[in] other const iterator to copy from + @return const/non-const iterator + @note It is not checked whether @a other is initialized. + */ + iter_impl& operator=(const iter_impl& other) noexcept + { + if (&other != this) + { + m_object = other.m_object; + m_it = other.m_it; + } + return *this; + } + + /*! + @brief converting constructor + @param[in] other non-const iterator to copy from + @note It is not checked whether @a other is initialized. + */ + iter_impl(const iter_impl::type>& other) noexcept + : m_object(other.m_object), m_it(other.m_it) + {} + + /*! + @brief converting assignment + @param[in] other non-const iterator to copy from + @return const/non-const iterator + @note It is not checked whether @a other is initialized. + */ + iter_impl& operator=(const iter_impl::type>& other) noexcept // NOLINT(cert-oop54-cpp) + { + m_object = other.m_object; + m_it = other.m_it; + return *this; + } + + JSON_PRIVATE_UNLESS_TESTED: + /*! + @brief set the iterator to the first value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_begin() noexcept + { + JSON_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: + { + // set to end so begin()==end() is true: null is empty + m_it.primitive_iterator.set_end(); + break; + } + + default: + { + m_it.primitive_iterator.set_begin(); + break; + } + } + } + + /*! + @brief set the iterator past the last value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_end() noexcept + { + JSON_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: + /*! + @brief return a reference to the value pointed to by the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator*() const + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + JSON_ASSERT(m_it.object_iterator != m_object->m_value.object->end()); + return m_it.object_iterator->second; + } + + case value_t::array: + { + JSON_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", *m_object)); + + default: + { + if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) + { + return *m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value", *m_object)); + } + } + } + + /*! + @brief dereference the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + pointer operator->() const + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + JSON_ASSERT(m_it.object_iterator != m_object->m_value.object->end()); + return &(m_it.object_iterator->second); + } + + case value_t::array: + { + JSON_ASSERT(m_it.array_iterator != m_object->m_value.array->end()); + return &*m_it.array_iterator; + } + + default: + { + if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) + { + return m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value", *m_object)); + } + } + } + + /*! + @brief post-increment (it++) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl const operator++(int) // NOLINT(readability-const-return-type) + { + auto result = *this; + ++(*this); + return result; + } + + /*! + @brief pre-increment (++it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator++() + { + JSON_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; + } + + /*! + @brief post-decrement (it--) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl const operator--(int) // NOLINT(readability-const-return-type) + { + auto result = *this; + --(*this); + return result; + } + + /*! + @brief pre-decrement (--it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator--() + { + JSON_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; + } + + /*! + @brief comparison: equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + template < typename IterImpl, detail::enable_if_t < (std::is_same::value || std::is_same::value), std::nullptr_t > = nullptr > + bool operator==(const IterImpl& other) const + { + // if objects are not the same, the comparison is undefined + if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) + { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", *m_object)); + } + + JSON_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); + } + } + + /*! + @brief comparison: not equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + template < typename IterImpl, detail::enable_if_t < (std::is_same::value || std::is_same::value), std::nullptr_t > = nullptr > + bool operator!=(const IterImpl& other) const + { + return !operator==(other); + } + + /*! + @brief comparison: smaller + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<(const iter_impl& other) const + { + // if objects are not the same, the comparison is undefined + if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) + { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", *m_object)); + } + + JSON_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", *m_object)); + + 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); + } + } + + /*! + @brief comparison: less than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<=(const iter_impl& other) const + { + return !other.operator < (*this); + } + + /*! + @brief comparison: greater than + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>(const iter_impl& other) const + { + return !operator<=(other); + } + + /*! + @brief comparison: greater than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>=(const iter_impl& other) const + { + return !operator<(other); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator+=(difference_type i) + { + JSON_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", *m_object)); + + case value_t::array: + { + std::advance(m_it.array_iterator, i); + break; + } + + default: + { + m_it.primitive_iterator += i; + break; + } + } + + return *this; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator-=(difference_type i) + { + return operator+=(-i); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator+(difference_type i) const + { + auto result = *this; + result += i; + return result; + } + + /*! + @brief addition of distance and iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + friend iter_impl operator+(difference_type i, const iter_impl& it) + { + auto result = it; + result += i; + return result; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator-(difference_type i) const + { + auto result = *this; + result -= i; + return result; + } + + /*! + @brief return difference + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + difference_type operator-(const iter_impl& other) const + { + JSON_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", *m_object)); + + 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; + } + } + + /*! + @brief access to successor + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator[](difference_type n) const + { + JSON_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", *m_object)); + + 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", *m_object)); + + default: + { + if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.get_value() == -n)) + { + return *m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value", *m_object)); + } + } + } + + /*! + @brief return the key of an object iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + const typename object_t::key_type& key() const + { + JSON_ASSERT(m_object != nullptr); + + if (JSON_HEDLEY_LIKELY(m_object->is_object())) + { + return m_it.object_iterator->first; + } + + JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators", *m_object)); + } + + /*! + @brief return the value of an iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference value() const + { + return operator*(); + } + + JSON_PRIVATE_UNLESS_TESTED: + /// associated JSON instance + pointer m_object = nullptr; + /// the actual iterator of the associated instance + internal_iterator::type> m_it {}; +}; +} // namespace detail +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/iterators/iteration_proxy.hpp b/lib/json/include/nlohmann/detail/iterators/iteration_proxy.hpp new file mode 100644 index 0000000..d59098d --- /dev/null +++ b/lib/json/include/nlohmann/detail/iterators/iteration_proxy.hpp @@ -0,0 +1,181 @@ +#pragma once + +#include // size_t +#include // input_iterator_tag +#include // string, to_string +#include // tuple_size, get, tuple_element +#include // move + +#include +#include + +namespace nlohmann +{ +namespace detail +{ +template +void int_to_string( string_type& target, std::size_t value ) +{ + // For ADL + using std::to_string; + target = to_string(value); +} +template class iteration_proxy_value +{ + public: + using difference_type = std::ptrdiff_t; + using value_type = iteration_proxy_value; + using pointer = value_type * ; + using reference = value_type & ; + using iterator_category = std::input_iterator_tag; + using string_type = typename std::remove_cv< typename std::remove_reference().key() ) >::type >::type; + + private: + /// the iterator + IteratorType anchor; + /// an index for arrays (used to create key names) + std::size_t array_index = 0; + /// last stringified array index + mutable std::size_t array_index_last = 0; + /// a string representation of the array index + mutable string_type array_index_str = "0"; + /// an empty string (to return a reference for primitive values) + const string_type empty_str{}; + + public: + explicit iteration_proxy_value(IteratorType it) noexcept + : anchor(std::move(it)) + {} + + /// dereference operator (needed for range-based for) + iteration_proxy_value& operator*() + { + return *this; + } + + /// increment operator (needed for range-based for) + iteration_proxy_value& operator++() + { + ++anchor; + ++array_index; + + return *this; + } + + /// equality operator (needed for InputIterator) + bool operator==(const iteration_proxy_value& o) const + { + return anchor == o.anchor; + } + + /// inequality operator (needed for range-based for) + bool operator!=(const iteration_proxy_value& o) const + { + return anchor != o.anchor; + } + + /// return key of the iterator + const string_type& key() const + { + JSON_ASSERT(anchor.m_object != nullptr); + + switch (anchor.m_object->type()) + { + // use integer array index as key + case value_t::array: + { + if (array_index != array_index_last) + { + int_to_string( array_index_str, array_index ); + array_index_last = array_index; + } + return array_index_str; + } + + // use key from the object + case value_t::object: + return anchor.key(); + + // use an empty key for all primitive types + default: + return empty_str; + } + } + + /// return value of the iterator + typename IteratorType::reference value() const + { + return anchor.value(); + } +}; + +/// proxy class for the items() function +template class iteration_proxy +{ + private: + /// the container to iterate + typename IteratorType::reference container; + + public: + /// construct iteration proxy from a container + explicit iteration_proxy(typename IteratorType::reference cont) noexcept + : container(cont) {} + + /// return iterator begin (needed for range-based for) + iteration_proxy_value begin() noexcept + { + return iteration_proxy_value(container.begin()); + } + + /// return iterator end (needed for range-based for) + iteration_proxy_value end() noexcept + { + return iteration_proxy_value(container.end()); + } +}; +// Structured Bindings Support +// For further reference see https://blog.tartanllama.xyz/structured-bindings/ +// And see https://github.com/nlohmann/json/pull/1391 +template = 0> +auto get(const nlohmann::detail::iteration_proxy_value& i) -> decltype(i.key()) +{ + return i.key(); +} +// Structured Bindings Support +// For further reference see https://blog.tartanllama.xyz/structured-bindings/ +// And see https://github.com/nlohmann/json/pull/1391 +template = 0> +auto get(const nlohmann::detail::iteration_proxy_value& i) -> decltype(i.value()) +{ + return i.value(); +} +} // namespace detail +} // namespace nlohmann + +// The Addition to the STD Namespace is required to add +// Structured Bindings Support to the iteration_proxy_value class +// For further reference see https://blog.tartanllama.xyz/structured-bindings/ +// And see https://github.com/nlohmann/json/pull/1391 +namespace std +{ +#if defined(__clang__) + // Fix: https://github.com/nlohmann/json/issues/1401 + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wmismatched-tags" +#endif +template +class tuple_size<::nlohmann::detail::iteration_proxy_value> + : public std::integral_constant {}; + +template +class tuple_element> +{ + public: + using type = decltype( + get(std::declval < + ::nlohmann::detail::iteration_proxy_value> ())); +}; +#if defined(__clang__) + #pragma clang diagnostic pop +#endif +} // namespace std diff --git a/lib/json/include/nlohmann/detail/iterators/iterator_traits.hpp b/lib/json/include/nlohmann/detail/iterators/iterator_traits.hpp new file mode 100644 index 0000000..da56361 --- /dev/null +++ b/lib/json/include/nlohmann/detail/iterators/iterator_traits.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include // random_access_iterator_tag + +#include +#include + +namespace nlohmann +{ +namespace detail +{ +template +struct iterator_types {}; + +template +struct iterator_types < + It, + void_t> +{ + using difference_type = typename It::difference_type; + using value_type = typename It::value_type; + using pointer = typename It::pointer; + using reference = typename It::reference; + using iterator_category = typename It::iterator_category; +}; + +// This is required as some compilers implement std::iterator_traits in a way that +// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. +template +struct iterator_traits +{ +}; + +template +struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> + : iterator_types +{ +}; + +template +struct iterator_traits::value>> +{ + using iterator_category = std::random_access_iterator_tag; + using value_type = T; + using difference_type = ptrdiff_t; + using pointer = T*; + using reference = T&; +}; +} // namespace detail +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/iterators/json_reverse_iterator.hpp b/lib/json/include/nlohmann/detail/iterators/json_reverse_iterator.hpp new file mode 100644 index 0000000..e787fdb --- /dev/null +++ b/lib/json/include/nlohmann/detail/iterators/json_reverse_iterator.hpp @@ -0,0 +1,119 @@ +#pragma once + +#include // ptrdiff_t +#include // reverse_iterator +#include // declval + +namespace nlohmann +{ +namespace detail +{ +////////////////////// +// reverse_iterator // +////////////////////// + +/*! +@brief a template for a reverse iterator class + +@tparam Base the base iterator type to reverse. Valid types are @ref +iterator (to create @ref reverse_iterator) and @ref const_iterator (to +create @ref const_reverse_iterator). + +@requirement The class satisfies the following concept requirements: +- +[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator): + The iterator that can be moved can be moved in both directions (i.e. + incremented and decremented). +- [OutputIterator](https://en.cppreference.com/w/cpp/named_req/OutputIterator): + It is possible to write to the pointed-to element (only if @a Base is + @ref iterator). + +@since version 1.0.0 +*/ +template +class json_reverse_iterator : public std::reverse_iterator +{ + public: + using difference_type = std::ptrdiff_t; + /// shortcut to the reverse iterator adapter + using base_iterator = std::reverse_iterator; + /// the reference type for the pointed-to element + using reference = typename Base::reference; + + /// create reverse iterator from iterator + explicit json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept + : base_iterator(it) {} + + /// create reverse iterator from base class + explicit json_reverse_iterator(const base_iterator& it) noexcept : base_iterator(it) {} + + /// post-increment (it++) + json_reverse_iterator const operator++(int) // NOLINT(readability-const-return-type) + { + return static_cast(base_iterator::operator++(1)); + } + + /// pre-increment (++it) + json_reverse_iterator& operator++() + { + return static_cast(base_iterator::operator++()); + } + + /// post-decrement (it--) + json_reverse_iterator const operator--(int) // NOLINT(readability-const-return-type) + { + return static_cast(base_iterator::operator--(1)); + } + + /// pre-decrement (--it) + json_reverse_iterator& operator--() + { + return static_cast(base_iterator::operator--()); + } + + /// add to iterator + json_reverse_iterator& operator+=(difference_type i) + { + return static_cast(base_iterator::operator+=(i)); + } + + /// add to iterator + json_reverse_iterator operator+(difference_type i) const + { + return static_cast(base_iterator::operator+(i)); + } + + /// subtract from iterator + json_reverse_iterator operator-(difference_type i) const + { + return static_cast(base_iterator::operator-(i)); + } + + /// return difference + difference_type operator-(const json_reverse_iterator& other) const + { + return base_iterator(*this) - base_iterator(other); + } + + /// access to successor + reference operator[](difference_type n) const + { + return *(this->operator+(n)); + } + + /// return the key of an object iterator + auto key() const -> decltype(std::declval().key()) + { + auto it = --this->base(); + return it.key(); + } + + /// return the value of an iterator + reference value() const + { + auto it = --this->base(); + return it.operator * (); + } +}; +} // namespace detail +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/iterators/primitive_iterator.hpp b/lib/json/include/nlohmann/detail/iterators/primitive_iterator.hpp new file mode 100644 index 0000000..15aa2f0 --- /dev/null +++ b/lib/json/include/nlohmann/detail/iterators/primitive_iterator.hpp @@ -0,0 +1,123 @@ +#pragma once + +#include // ptrdiff_t +#include // numeric_limits + +#include + +namespace nlohmann +{ +namespace detail +{ +/* +@brief an iterator for primitive JSON types + +This class models an iterator for primitive JSON types (boolean, number, +string). It's only purpose is to allow the iterator/const_iterator classes +to "iterate" over primitive values. Internally, the iterator is modeled by +a `difference_type` variable. Value begin_value (`0`) models the begin, +end_value (`1`) models past the end. +*/ +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; + + JSON_PRIVATE_UNLESS_TESTED: + /// iterator as signed integer type + difference_type m_it = (std::numeric_limits::min)(); + + public: + constexpr difference_type get_value() const noexcept + { + return m_it; + } + + /// set iterator to a defined beginning + void set_begin() noexcept + { + m_it = begin_value; + } + + /// set iterator to a defined past the end + void set_end() noexcept + { + m_it = end_value; + } + + /// return whether the iterator can be dereferenced + constexpr bool is_begin() const noexcept + { + return m_it == begin_value; + } + + /// return whether the iterator is at end + 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 // NOLINT(readability-const-return-type) + { + auto result = *this; + ++m_it; + return result; + } + + primitive_iterator_t& operator--() noexcept + { + --m_it; + return *this; + } + + primitive_iterator_t const operator--(int) noexcept // NOLINT(readability-const-return-type) + { + 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 detail +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/json_pointer.hpp b/lib/json/include/nlohmann/detail/json_pointer.hpp new file mode 100644 index 0000000..72e14f0 --- /dev/null +++ b/lib/json/include/nlohmann/detail/json_pointer.hpp @@ -0,0 +1,934 @@ +#pragma once + +#include // all_of +#include // isdigit +#include // max +#include // accumulate +#include // string +#include // move +#include // vector + +#include +#include +#include +#include + +namespace nlohmann +{ +template +class json_pointer +{ + // allow basic_json to access private members + NLOHMANN_BASIC_JSON_TPL_DECLARATION + friend class basic_json; + + public: + /*! + @brief create JSON pointer + + Create a JSON pointer according to the syntax described in + [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3). + + @param[in] s string representing the JSON pointer; if omitted, the empty + string is assumed which references the whole JSON value + + @throw parse_error.107 if the given JSON pointer @a s is nonempty and does + not begin with a slash (`/`); see example below + + @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s is + not followed by `0` (representing `~`) or `1` (representing `/`); see + example below + + @liveexample{The example shows the construction several valid JSON pointers + as well as the exceptional behavior.,json_pointer} + + @since version 2.0.0 + */ + explicit json_pointer(const std::string& s = "") + : reference_tokens(split(s)) + {} + + /*! + @brief return a string representation of the JSON pointer + + @invariant For each JSON pointer `ptr`, it holds: + @code {.cpp} + ptr == json_pointer(ptr.to_string()); + @endcode + + @return a string representation of the JSON pointer + + @liveexample{The example shows the result of `to_string`.,json_pointer__to_string} + + @since version 2.0.0 + */ + std::string to_string() const + { + return std::accumulate(reference_tokens.begin(), reference_tokens.end(), + std::string{}, + [](const std::string & a, const std::string & b) + { + return a + "/" + detail::escape(b); + }); + } + + /// @copydoc to_string() + operator std::string() const + { + return to_string(); + } + + /*! + @brief append another JSON pointer at the end of this JSON pointer + + @param[in] ptr JSON pointer to append + @return JSON pointer with @a ptr appended + + @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} + + @complexity Linear in the length of @a ptr. + + @sa see @ref operator/=(std::string) to append a reference token + @sa see @ref operator/=(std::size_t) to append an array index + @sa see @ref operator/(const json_pointer&, const json_pointer&) for a binary operator + + @since version 3.6.0 + */ + json_pointer& operator/=(const json_pointer& ptr) + { + reference_tokens.insert(reference_tokens.end(), + ptr.reference_tokens.begin(), + ptr.reference_tokens.end()); + return *this; + } + + /*! + @brief append an unescaped reference token at the end of this JSON pointer + + @param[in] token reference token to append + @return JSON pointer with @a token appended without escaping @a token + + @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} + + @complexity Amortized constant. + + @sa see @ref operator/=(const json_pointer&) to append a JSON pointer + @sa see @ref operator/=(std::size_t) to append an array index + @sa see @ref operator/(const json_pointer&, std::size_t) for a binary operator + + @since version 3.6.0 + */ + json_pointer& operator/=(std::string token) + { + push_back(std::move(token)); + return *this; + } + + /*! + @brief append an array index at the end of this JSON pointer + + @param[in] array_idx array index to append + @return JSON pointer with @a array_idx appended + + @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} + + @complexity Amortized constant. + + @sa see @ref operator/=(const json_pointer&) to append a JSON pointer + @sa see @ref operator/=(std::string) to append a reference token + @sa see @ref operator/(const json_pointer&, std::string) for a binary operator + + @since version 3.6.0 + */ + json_pointer& operator/=(std::size_t array_idx) + { + return *this /= std::to_string(array_idx); + } + + /*! + @brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer + + @param[in] lhs JSON pointer + @param[in] rhs JSON pointer + @return a new JSON pointer with @a rhs appended to @a lhs + + @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} + + @complexity Linear in the length of @a lhs and @a rhs. + + @sa see @ref operator/=(const json_pointer&) to append a JSON pointer + + @since version 3.6.0 + */ + friend json_pointer operator/(const json_pointer& lhs, + const json_pointer& rhs) + { + return json_pointer(lhs) /= rhs; + } + + /*! + @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer + + @param[in] ptr JSON pointer + @param[in] token reference token + @return a new JSON pointer with unescaped @a token appended to @a ptr + + @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} + + @complexity Linear in the length of @a ptr. + + @sa see @ref operator/=(std::string) to append a reference token + + @since version 3.6.0 + */ + friend json_pointer operator/(const json_pointer& ptr, std::string token) // NOLINT(performance-unnecessary-value-param) + { + return json_pointer(ptr) /= std::move(token); + } + + /*! + @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer + + @param[in] ptr JSON pointer + @param[in] array_idx array index + @return a new JSON pointer with @a array_idx appended to @a ptr + + @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} + + @complexity Linear in the length of @a ptr. + + @sa see @ref operator/=(std::size_t) to append an array index + + @since version 3.6.0 + */ + friend json_pointer operator/(const json_pointer& ptr, std::size_t array_idx) + { + return json_pointer(ptr) /= array_idx; + } + + /*! + @brief returns the parent of this JSON pointer + + @return parent of this JSON pointer; in case this JSON pointer is the root, + the root itself is returned + + @complexity Linear in the length of the JSON pointer. + + @liveexample{The example shows the result of `parent_pointer` for different + JSON Pointers.,json_pointer__parent_pointer} + + @since version 3.6.0 + */ + json_pointer parent_pointer() const + { + if (empty()) + { + return *this; + } + + json_pointer res = *this; + res.pop_back(); + return res; + } + + /*! + @brief remove last reference token + + @pre not `empty()` + + @liveexample{The example shows the usage of `pop_back`.,json_pointer__pop_back} + + @complexity Constant. + + @throw out_of_range.405 if JSON pointer has no parent + + @since version 3.6.0 + */ + void pop_back() + { + if (JSON_HEDLEY_UNLIKELY(empty())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", BasicJsonType())); + } + + reference_tokens.pop_back(); + } + + /*! + @brief return last reference token + + @pre not `empty()` + @return last reference token + + @liveexample{The example shows the usage of `back`.,json_pointer__back} + + @complexity Constant. + + @throw out_of_range.405 if JSON pointer has no parent + + @since version 3.6.0 + */ + const std::string& back() const + { + if (JSON_HEDLEY_UNLIKELY(empty())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", BasicJsonType())); + } + + return reference_tokens.back(); + } + + /*! + @brief append an unescaped token at the end of the reference pointer + + @param[in] token token to add + + @complexity Amortized constant. + + @liveexample{The example shows the result of `push_back` for different + JSON Pointers.,json_pointer__push_back} + + @since version 3.6.0 + */ + void push_back(const std::string& token) + { + reference_tokens.push_back(token); + } + + /// @copydoc push_back(const std::string&) + void push_back(std::string&& token) + { + reference_tokens.push_back(std::move(token)); + } + + /*! + @brief return whether pointer points to the root document + + @return true iff the JSON pointer points to the root document + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example shows the result of `empty` for different JSON + Pointers.,json_pointer__empty} + + @since version 3.6.0 + */ + bool empty() const noexcept + { + return reference_tokens.empty(); + } + + private: + /*! + @param[in] s reference token to be converted into an array index + + @return integer representation of @a s + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index begins not with a digit + @throw out_of_range.404 if string @a s could not be converted to an integer + @throw out_of_range.410 if an array index exceeds size_type + */ + static typename BasicJsonType::size_type array_index(const std::string& s) + { + using size_type = typename BasicJsonType::size_type; + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, "array index '" + s + "' must not begin with '0'", BasicJsonType())); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9'))) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + s + "' is not a number", BasicJsonType())); + } + + std::size_t processed_chars = 0; + unsigned long long res = 0; // NOLINT(runtime/int) + JSON_TRY + { + res = std::stoull(s, &processed_chars); + } + JSON_CATCH(std::out_of_range&) + { + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'", BasicJsonType())); + } + + // check if the string was completely read + if (JSON_HEDLEY_UNLIKELY(processed_chars != s.size())) + { + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'", BasicJsonType())); + } + + // only triggered on special platforms (like 32bit), see also + // https://github.com/nlohmann/json/pull/2203 + if (res >= static_cast((std::numeric_limits::max)())) // NOLINT(runtime/int) + { + JSON_THROW(detail::out_of_range::create(410, "array index " + s + " exceeds size_type", BasicJsonType())); // LCOV_EXCL_LINE + } + + return static_cast(res); + } + + JSON_PRIVATE_UNLESS_TESTED: + json_pointer top() const + { + if (JSON_HEDLEY_UNLIKELY(empty())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", BasicJsonType())); + } + + json_pointer result = *this; + result.reference_tokens = {reference_tokens[0]}; + return result; + } + + private: + /*! + @brief create and return a reference to the pointed to value + + @complexity Linear in the number of reference tokens. + + @throw parse_error.109 if array index is not a number + @throw type_error.313 if value cannot be unflattened + */ + BasicJsonType& get_and_create(BasicJsonType& j) const + { + auto* result = &j; + + // in case no reference tokens exist, return a reference to the JSON value + // j which will be overwritten by a primitive value + for (const auto& reference_token : reference_tokens) + { + switch (result->type()) + { + case detail::value_t::null: + { + if (reference_token == "0") + { + // start a new array if reference token is 0 + result = &result->operator[](0); + } + else + { + // start a new object otherwise + result = &result->operator[](reference_token); + } + break; + } + + case detail::value_t::object: + { + // create an entry in the object + result = &result->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + // create an entry in the array + result = &result->operator[](array_index(reference_token)); + break; + } + + /* + The following code is only reached if there exists a reference + token _and_ the current value is primitive. In this case, we have + an error situation, because primitive values may only occur as + single value; that is, with an empty list of reference tokens. + */ + default: + JSON_THROW(detail::type_error::create(313, "invalid value to unflatten", j)); + } + } + + return *result; + } + + /*! + @brief return a reference to the pointed to value + + @note This version does not throw if a value is not present, but tries to + create nested values instead. For instance, calling this function + with pointer `"/this/that"` on a null value is equivalent to calling + `operator[]("this").operator[]("that")` on that value, effectively + changing the null value to an object. + + @param[in] ptr a JSON value + + @return reference to the JSON value pointed to by the JSON pointer + + @complexity Linear in the length of the JSON pointer. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + BasicJsonType& get_unchecked(BasicJsonType* ptr) const + { + for (const auto& reference_token : reference_tokens) + { + // convert null values to arrays or objects before continuing + if (ptr->is_null()) + { + // check if reference token is a number + const bool nums = + std::all_of(reference_token.begin(), reference_token.end(), + [](const unsigned char x) + { + return std::isdigit(x); + }); + + // change value to array for numbers or "-" or to object otherwise + *ptr = (nums || reference_token == "-") + ? detail::value_t::array + : detail::value_t::object; + } + + switch (ptr->type()) + { + case detail::value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + if (reference_token == "-") + { + // explicitly treat "-" as index beyond the end + ptr = &ptr->operator[](ptr->m_value.array->size()); + } + else + { + // convert array index to number; unchecked access + ptr = &ptr->operator[](array_index(reference_token)); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'", *ptr)); + } + } + + return *ptr; + } + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + BasicJsonType& get_checked(BasicJsonType* ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->type()) + { + case detail::value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range", *ptr)); + } + + // note: at performs range check + ptr = &ptr->at(array_index(reference_token)); + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'", *ptr)); + } + } + + return *ptr; + } + + /*! + @brief return a const reference to the pointed to value + + @param[in] ptr a JSON value + + @return const reference to the JSON value pointed to by the JSON + pointer + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->type()) + { + case detail::value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) + { + // "-" cannot be used for const access + JSON_THROW(detail::out_of_range::create(402, "array index '-' (" + std::to_string(ptr->m_value.array->size()) + ") is out of range", *ptr)); + } + + // use unchecked array access + ptr = &ptr->operator[](array_index(reference_token)); + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'", *ptr)); + } + } + + return *ptr; + } + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + const BasicJsonType& get_checked(const BasicJsonType* ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->type()) + { + case detail::value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range", *ptr)); + } + + // note: at performs range check + ptr = &ptr->at(array_index(reference_token)); + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'", *ptr)); + } + } + + return *ptr; + } + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + */ + bool contains(const BasicJsonType* ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->type()) + { + case detail::value_t::object: + { + if (!ptr->contains(reference_token)) + { + // we did not find the key in the object + return false; + } + + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + return false; + } + if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !("0" <= reference_token && reference_token <= "9"))) + { + // invalid char + return false; + } + if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1)) + { + if (JSON_HEDLEY_UNLIKELY(!('1' <= reference_token[0] && reference_token[0] <= '9'))) + { + // first char should be between '1' and '9' + return false; + } + for (std::size_t i = 1; i < reference_token.size(); i++) + { + if (JSON_HEDLEY_UNLIKELY(!('0' <= reference_token[i] && reference_token[i] <= '9'))) + { + // other char should be between '0' and '9' + return false; + } + } + } + + const auto idx = array_index(reference_token); + if (idx >= ptr->size()) + { + // index out of range + return false; + } + + ptr = &ptr->operator[](idx); + break; + } + + default: + { + // we do not expect primitive values if there is still a + // reference token to process + return false; + } + } + } + + // no reference token left means we found a primitive value + return true; + } + + /*! + @brief split the string input to reference tokens + + @note This function is only called by the json_pointer constructor. + All exceptions below are documented there. + + @throw parse_error.107 if the pointer is not empty or begins with '/' + @throw parse_error.108 if character '~' is not followed by '0' or '1' + */ + static std::vector split(const std::string& reference_string) + { + std::vector result; + + // special case: empty reference string -> no reference tokens + if (reference_string.empty()) + { + return result; + } + + // check if nonempty reference string begins with slash + if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/')) + { + JSON_THROW(detail::parse_error::create(107, 1, "JSON pointer must be empty or begin with '/' - was: '" + reference_string + "'", BasicJsonType())); + } + + // extract the reference tokens: + // - slash: position of the last read slash (or end of string) + // - start: position after the previous slash + for ( + // search for the first slash after the first character + std::size_t slash = reference_string.find_first_of('/', 1), + // set the beginning of the first reference token + start = 1; + // we can stop if start == 0 (if slash == std::string::npos) + start != 0; + // set the beginning of the next reference token + // (will eventually be 0 if slash == std::string::npos) + start = (slash == std::string::npos) ? 0 : slash + 1, + // find next slash + slash = reference_string.find_first_of('/', start)) + { + // use the text between the beginning of the reference token + // (start) and the last slash (slash). + auto reference_token = reference_string.substr(start, slash - start); + + // check reference tokens are properly escaped + for (std::size_t pos = reference_token.find_first_of('~'); + pos != std::string::npos; + pos = reference_token.find_first_of('~', pos + 1)) + { + JSON_ASSERT(reference_token[pos] == '~'); + + // ~ must be followed by 0 or 1 + if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 || + (reference_token[pos + 1] != '0' && + reference_token[pos + 1] != '1'))) + { + JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'", BasicJsonType())); + } + } + + // finally, store the reference token + detail::unescape(reference_token); + result.push_back(reference_token); + } + + return result; + } + + private: + /*! + @param[in] reference_string the reference string to the current value + @param[in] value the value to consider + @param[in,out] result the result object to insert values to + + @note Empty objects or arrays are flattened to `null`. + */ + static void flatten(const std::string& reference_string, + const BasicJsonType& value, + BasicJsonType& result) + { + switch (value.type()) + { + case detail::value_t::array: + { + if (value.m_value.array->empty()) + { + // flatten empty array as null + result[reference_string] = nullptr; + } + else + { + // iterate array and use index as reference string + 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()) + { + // flatten empty object as null + result[reference_string] = nullptr; + } + else + { + // iterate object and use keys as reference string + for (const auto& element : *value.m_value.object) + { + flatten(reference_string + "/" + detail::escape(element.first), element.second, result); + } + } + break; + } + + default: + { + // add primitive value with its reference string + result[reference_string] = value; + break; + } + } + } + + /*! + @param[in] value flattened JSON + + @return unflattened JSON + + @throw parse_error.109 if array index is not a number + @throw type_error.314 if value is not an object + @throw type_error.315 if object values are not primitive + @throw type_error.313 if value cannot be unflattened + */ + static BasicJsonType + unflatten(const BasicJsonType& value) + { + if (JSON_HEDLEY_UNLIKELY(!value.is_object())) + { + JSON_THROW(detail::type_error::create(314, "only objects can be unflattened", value)); + } + + BasicJsonType result; + + // iterate the JSON object values + for (const auto& element : *value.m_value.object) + { + if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive())) + { + JSON_THROW(detail::type_error::create(315, "values in object must be primitive", element.second)); + } + + // assign value to reference pointed to by JSON pointer; Note that if + // the JSON pointer is "" (i.e., points to the whole value), function + // get_and_create returns a reference to result itself. An assignment + // will then create a primitive value. + json_pointer(element.first).get_and_create(result) = element.second; + } + + return result; + } + + /*! + @brief compares two JSON pointers for equality + + @param[in] lhs JSON pointer to compare + @param[in] rhs JSON pointer to compare + @return whether @a lhs is equal to @a rhs + + @complexity Linear in the length of the JSON pointer + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + */ + friend bool operator==(json_pointer const& lhs, + json_pointer const& rhs) noexcept + { + return lhs.reference_tokens == rhs.reference_tokens; + } + + /*! + @brief compares two JSON pointers for inequality + + @param[in] lhs JSON pointer to compare + @param[in] rhs JSON pointer to compare + @return whether @a lhs is not equal @a rhs + + @complexity Linear in the length of the JSON pointer + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + */ + friend bool operator!=(json_pointer const& lhs, + json_pointer const& rhs) noexcept + { + return !(lhs == rhs); + } + + /// the reference tokens + std::vector reference_tokens; +}; +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/json_ref.hpp b/lib/json/include/nlohmann/detail/json_ref.hpp new file mode 100644 index 0000000..b4e5dab --- /dev/null +++ b/lib/json/include/nlohmann/detail/json_ref.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include +#include + +#include + +namespace nlohmann +{ +namespace detail +{ +template +class json_ref +{ + public: + using value_type = BasicJsonType; + + json_ref(value_type&& value) + : owned_value(std::move(value)) + {} + + json_ref(const value_type& value) + : value_ref(&value) + {} + + json_ref(std::initializer_list init) + : owned_value(init) + {} + + template < + class... Args, + enable_if_t::value, int> = 0 > + json_ref(Args && ... args) + : owned_value(std::forward(args)...) + {} + + // class should be movable only + json_ref(json_ref&&) noexcept = default; + json_ref(const json_ref&) = delete; + json_ref& operator=(const json_ref&) = delete; + json_ref& operator=(json_ref&&) = delete; + ~json_ref() = default; + + value_type moved_or_copied() const + { + if (value_ref == nullptr) + { + return std::move(owned_value); + } + return *value_ref; + } + + value_type const& operator*() const + { + return value_ref ? *value_ref : owned_value; + } + + value_type const* operator->() const + { + return &** this; + } + + private: + mutable value_type owned_value = nullptr; + value_type const* value_ref = nullptr; +}; +} // namespace detail +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/macro_scope.hpp b/lib/json/include/nlohmann/detail/macro_scope.hpp new file mode 100644 index 0000000..663c2fb --- /dev/null +++ b/lib/json/include/nlohmann/detail/macro_scope.hpp @@ -0,0 +1,302 @@ +#pragma once + +#include // pair +#include + +// This file contains all internal macro definitions +// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them + +// exclude unsupported compilers +#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) + #if defined(__clang__) + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #endif +#endif + +// C++ language standard detection +// if the user manually specified the used c++ version this is skipped +#if !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11) + #if (defined(__cplusplus) && __cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) + #define JSON_HAS_CPP_20 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #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 + // the cpp 11 flag is always specified because it is the minimal required version + #define JSON_HAS_CPP_11 +#endif + +// disable documentation warnings on clang +#if defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdocumentation" +#endif + +// allow to disable exceptions +#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) + #define JSON_INTERNAL_CATCH(exception) catch(exception) +#else + #include + #define JSON_THROW(exception) std::abort() + #define JSON_TRY if(true) + #define JSON_CATCH(exception) if(false) + #define JSON_INTERNAL_CATCH(exception) if(false) +#endif + +// override exception macros +#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 + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_CATCH_USER +#endif +#if defined(JSON_INTERNAL_CATCH_USER) + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER +#endif + +// allow to override assert +#if !defined(JSON_ASSERT) + #include // assert + #define JSON_ASSERT(x) assert(x) +#endif + +// allow to access some private functions (needed by the test suite) +#if defined(JSON_TESTS_PRIVATE) + #define JSON_PRIVATE_UNLESS_TESTED public +#else + #define JSON_PRIVATE_UNLESS_TESTED private +#endif + +/*! +@brief macro to briefly define a mapping between an enum and JSON +@def NLOHMANN_JSON_SERIALIZE_ENUM +@since version 3.4.0 +*/ +#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ + template \ + inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [e](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.first == e; \ + }); \ + j = ((it != std::end(m)) ? it : std::begin(m))->second; \ + } \ + template \ + inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [&j](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.second == j; \ + }); \ + e = ((it != std::end(m)) ? it : std::begin(m))->first; \ + } + +// Ugly macros to avoid uglier copy-paste when specializing basic_json. They +// may be removed in the future once the class is split. + +#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ + template class ObjectType, \ + template class ArrayType, \ + class StringType, class BooleanType, class NumberIntegerType, \ + class NumberUnsignedType, class NumberFloatType, \ + template class AllocatorType, \ + template class JSONSerializer, \ + class BinaryType> + +#define NLOHMANN_BASIC_JSON_TPL \ + basic_json + +// Macros to simplify conversion from/to types + +#define NLOHMANN_JSON_EXPAND( x ) x +#define NLOHMANN_JSON_GET_MACRO(_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, NAME,...) NAME +#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \ + NLOHMANN_JSON_PASTE64, \ + NLOHMANN_JSON_PASTE63, \ + NLOHMANN_JSON_PASTE62, \ + NLOHMANN_JSON_PASTE61, \ + NLOHMANN_JSON_PASTE60, \ + NLOHMANN_JSON_PASTE59, \ + NLOHMANN_JSON_PASTE58, \ + NLOHMANN_JSON_PASTE57, \ + NLOHMANN_JSON_PASTE56, \ + NLOHMANN_JSON_PASTE55, \ + NLOHMANN_JSON_PASTE54, \ + NLOHMANN_JSON_PASTE53, \ + NLOHMANN_JSON_PASTE52, \ + NLOHMANN_JSON_PASTE51, \ + NLOHMANN_JSON_PASTE50, \ + NLOHMANN_JSON_PASTE49, \ + NLOHMANN_JSON_PASTE48, \ + NLOHMANN_JSON_PASTE47, \ + NLOHMANN_JSON_PASTE46, \ + NLOHMANN_JSON_PASTE45, \ + NLOHMANN_JSON_PASTE44, \ + NLOHMANN_JSON_PASTE43, \ + NLOHMANN_JSON_PASTE42, \ + NLOHMANN_JSON_PASTE41, \ + NLOHMANN_JSON_PASTE40, \ + NLOHMANN_JSON_PASTE39, \ + NLOHMANN_JSON_PASTE38, \ + NLOHMANN_JSON_PASTE37, \ + NLOHMANN_JSON_PASTE36, \ + NLOHMANN_JSON_PASTE35, \ + NLOHMANN_JSON_PASTE34, \ + NLOHMANN_JSON_PASTE33, \ + NLOHMANN_JSON_PASTE32, \ + NLOHMANN_JSON_PASTE31, \ + NLOHMANN_JSON_PASTE30, \ + NLOHMANN_JSON_PASTE29, \ + NLOHMANN_JSON_PASTE28, \ + NLOHMANN_JSON_PASTE27, \ + NLOHMANN_JSON_PASTE26, \ + NLOHMANN_JSON_PASTE25, \ + NLOHMANN_JSON_PASTE24, \ + NLOHMANN_JSON_PASTE23, \ + NLOHMANN_JSON_PASTE22, \ + NLOHMANN_JSON_PASTE21, \ + NLOHMANN_JSON_PASTE20, \ + NLOHMANN_JSON_PASTE19, \ + NLOHMANN_JSON_PASTE18, \ + NLOHMANN_JSON_PASTE17, \ + NLOHMANN_JSON_PASTE16, \ + NLOHMANN_JSON_PASTE15, \ + NLOHMANN_JSON_PASTE14, \ + NLOHMANN_JSON_PASTE13, \ + NLOHMANN_JSON_PASTE12, \ + NLOHMANN_JSON_PASTE11, \ + NLOHMANN_JSON_PASTE10, \ + NLOHMANN_JSON_PASTE9, \ + NLOHMANN_JSON_PASTE8, \ + NLOHMANN_JSON_PASTE7, \ + NLOHMANN_JSON_PASTE6, \ + NLOHMANN_JSON_PASTE5, \ + NLOHMANN_JSON_PASTE4, \ + NLOHMANN_JSON_PASTE3, \ + NLOHMANN_JSON_PASTE2, \ + NLOHMANN_JSON_PASTE1)(__VA_ARGS__)) +#define NLOHMANN_JSON_PASTE2(func, v1) func(v1) +#define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2) +#define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3) +#define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4) +#define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5) +#define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6) +#define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7) +#define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8) +#define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9) +#define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10) +#define NLOHMANN_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) +#define NLOHMANN_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) +#define NLOHMANN_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) +#define NLOHMANN_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) +#define NLOHMANN_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) +#define NLOHMANN_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) +#define NLOHMANN_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) +#define NLOHMANN_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) +#define NLOHMANN_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) +#define NLOHMANN_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) +#define NLOHMANN_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) +#define NLOHMANN_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) +#define NLOHMANN_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) +#define NLOHMANN_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) +#define NLOHMANN_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) +#define NLOHMANN_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) +#define NLOHMANN_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) +#define NLOHMANN_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) +#define NLOHMANN_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) +#define NLOHMANN_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) +#define NLOHMANN_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) +#define NLOHMANN_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) +#define NLOHMANN_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) +#define NLOHMANN_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) +#define NLOHMANN_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) +#define NLOHMANN_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) +#define NLOHMANN_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) +#define NLOHMANN_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) +#define NLOHMANN_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) +#define NLOHMANN_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) +#define NLOHMANN_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) +#define NLOHMANN_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) +#define NLOHMANN_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) +#define NLOHMANN_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) +#define NLOHMANN_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) +#define NLOHMANN_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) +#define NLOHMANN_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) +#define NLOHMANN_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) +#define NLOHMANN_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) +#define NLOHMANN_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) +#define NLOHMANN_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) +#define NLOHMANN_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) +#define NLOHMANN_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) +#define NLOHMANN_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) +#define NLOHMANN_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) +#define NLOHMANN_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) +#define NLOHMANN_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) +#define NLOHMANN_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) +#define NLOHMANN_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) +#define NLOHMANN_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) +#define NLOHMANN_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) +#define NLOHMANN_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) +#define NLOHMANN_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) + +#define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; +#define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE +@since version 3.9.0 +*/ +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE +@since version 3.9.0 +*/ +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +#ifndef JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_USE_IMPLICIT_CONVERSIONS 1 +#endif + +#if JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_EXPLICIT +#else + #define JSON_EXPLICIT explicit +#endif diff --git a/lib/json/include/nlohmann/detail/macro_unscope.hpp b/lib/json/include/nlohmann/detail/macro_unscope.hpp new file mode 100644 index 0000000..28be047 --- /dev/null +++ b/lib/json/include/nlohmann/detail/macro_unscope.hpp @@ -0,0 +1,23 @@ +#pragma once + +// restore GCC/clang diagnostic settings +#if defined(__clang__) + #pragma GCC diagnostic pop +#endif + +// clean up +#undef JSON_ASSERT +#undef JSON_INTERNAL_CATCH +#undef JSON_CATCH +#undef JSON_THROW +#undef JSON_TRY +#undef JSON_PRIVATE_UNLESS_TESTED +#undef JSON_HAS_CPP_11 +#undef JSON_HAS_CPP_14 +#undef JSON_HAS_CPP_17 +#undef JSON_HAS_CPP_20 +#undef NLOHMANN_BASIC_JSON_TPL_DECLARATION +#undef NLOHMANN_BASIC_JSON_TPL +#undef JSON_EXPLICIT + +#include diff --git a/lib/json/include/nlohmann/detail/meta/cpp_future.hpp b/lib/json/include/nlohmann/detail/meta/cpp_future.hpp new file mode 100644 index 0000000..e24518f --- /dev/null +++ b/lib/json/include/nlohmann/detail/meta/cpp_future.hpp @@ -0,0 +1,154 @@ +#pragma once + +#include // size_t +#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type +#include // index_sequence, make_index_sequence, index_sequence_for + +#include + +namespace nlohmann +{ +namespace detail +{ + +template +using uncvref_t = typename std::remove_cv::type>::type; + +#ifdef JSON_HAS_CPP_14 + +// the following utilities are natively available in C++14 +using std::enable_if_t; +using std::index_sequence; +using std::make_index_sequence; +using std::index_sequence_for; + +#else + +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +// The following code is taken from https://github.com/abseil/abseil-cpp/blob/10cb35e459f5ecca5b2ff107635da0bfa41011b4/absl/utility/utility.h +// which is part of Google Abseil (https://github.com/abseil/abseil-cpp), licensed under the Apache License 2.0. + +//// START OF CODE FROM GOOGLE ABSEIL + +// integer_sequence +// +// Class template representing a compile-time integer sequence. An instantiation +// of `integer_sequence` has a sequence of integers encoded in its +// type through its template arguments (which is a common need when +// working with C++11 variadic templates). `absl::integer_sequence` is designed +// to be a drop-in replacement for C++14's `std::integer_sequence`. +// +// Example: +// +// template< class T, T... Ints > +// void user_function(integer_sequence); +// +// int main() +// { +// // user_function's `T` will be deduced to `int` and `Ints...` +// // will be deduced to `0, 1, 2, 3, 4`. +// user_function(make_integer_sequence()); +// } +template +struct integer_sequence +{ + using value_type = T; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +// index_sequence +// +// A helper template for an `integer_sequence` of `size_t`, +// `absl::index_sequence` is designed to be a drop-in replacement for C++14's +// `std::index_sequence`. +template +using index_sequence = integer_sequence; + +namespace utility_internal +{ + +template +struct Extend; + +// Note that SeqSize == sizeof...(Ints). It's passed explicitly for efficiency. +template +struct Extend, SeqSize, 0> +{ + using type = integer_sequence < T, Ints..., (Ints + SeqSize)... >; +}; + +template +struct Extend, SeqSize, 1> +{ + using type = integer_sequence < T, Ints..., (Ints + SeqSize)..., 2 * SeqSize >; +}; + +// Recursion helper for 'make_integer_sequence'. +// 'Gen::type' is an alias for 'integer_sequence'. +template +struct Gen +{ + using type = + typename Extend < typename Gen < T, N / 2 >::type, N / 2, N % 2 >::type; +}; + +template +struct Gen +{ + using type = integer_sequence; +}; + +} // namespace utility_internal + +// Compile-time sequences of integers + +// make_integer_sequence +// +// This template alias is equivalent to +// `integer_sequence`, and is designed to be a drop-in +// replacement for C++14's `std::make_integer_sequence`. +template +using make_integer_sequence = typename utility_internal::Gen::type; + +// make_index_sequence +// +// This template alias is equivalent to `index_sequence<0, 1, ..., N-1>`, +// and is designed to be a drop-in replacement for C++14's +// `std::make_index_sequence`. +template +using make_index_sequence = make_integer_sequence; + +// index_sequence_for +// +// Converts a typename pack into an index sequence of the same length, and +// is designed to be a drop-in replacement for C++14's +// `std::index_sequence_for()` +template +using index_sequence_for = make_index_sequence; + +//// END OF CODE FROM GOOGLE ABSEIL + +#endif + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + +// taken from ranges-v3 +template +struct static_const +{ + static constexpr T value{}; +}; + +template +constexpr T static_const::value; + +} // namespace detail +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/meta/detected.hpp b/lib/json/include/nlohmann/detail/meta/detected.hpp new file mode 100644 index 0000000..7b5a003 --- /dev/null +++ b/lib/json/include/nlohmann/detail/meta/detected.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include + +#include + +// https://en.cppreference.com/w/cpp/experimental/is_detected +namespace nlohmann +{ +namespace detail +{ +struct nonesuch +{ + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const&) = delete; + nonesuch(nonesuch const&&) = delete; + void operator=(nonesuch const&) = delete; + void operator=(nonesuch&&) = delete; +}; + +template class Op, + class... Args> +struct detector +{ + using value_t = std::false_type; + using type = Default; +}; + +template class Op, class... Args> +struct detector>, Op, Args...> +{ + using value_t = std::true_type; + using type = Op; +}; + +template class Op, class... Args> +using is_detected = typename detector::value_t; + +template class Op, class... Args> +using detected_t = typename detector::type; + +template class Op, class... Args> +using detected_or = detector; + +template class Op, class... Args> +using detected_or_t = typename detected_or::type; + +template class Op, class... Args> +using is_detected_exact = std::is_same>; + +template class Op, class... Args> +using is_detected_convertible = + std::is_convertible, To>; +} // namespace detail +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/meta/identity_tag.hpp b/lib/json/include/nlohmann/detail/meta/identity_tag.hpp new file mode 100644 index 0000000..73a3e91 --- /dev/null +++ b/lib/json/include/nlohmann/detail/meta/identity_tag.hpp @@ -0,0 +1,10 @@ +#pragma once + +namespace nlohmann +{ +namespace detail +{ +// dispatching helper struct +template struct identity_tag {}; +} // namespace detail +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/meta/is_sax.hpp b/lib/json/include/nlohmann/detail/meta/is_sax.hpp new file mode 100644 index 0000000..e1e48a0 --- /dev/null +++ b/lib/json/include/nlohmann/detail/meta/is_sax.hpp @@ -0,0 +1,149 @@ +#pragma once + +#include // size_t +#include // declval +#include // string + +#include +#include + +namespace nlohmann +{ +namespace detail +{ +template +using null_function_t = decltype(std::declval().null()); + +template +using boolean_function_t = + decltype(std::declval().boolean(std::declval())); + +template +using number_integer_function_t = + decltype(std::declval().number_integer(std::declval())); + +template +using number_unsigned_function_t = + decltype(std::declval().number_unsigned(std::declval())); + +template +using number_float_function_t = decltype(std::declval().number_float( + std::declval(), std::declval())); + +template +using string_function_t = + decltype(std::declval().string(std::declval())); + +template +using binary_function_t = + decltype(std::declval().binary(std::declval())); + +template +using start_object_function_t = + decltype(std::declval().start_object(std::declval())); + +template +using key_function_t = + decltype(std::declval().key(std::declval())); + +template +using end_object_function_t = decltype(std::declval().end_object()); + +template +using start_array_function_t = + decltype(std::declval().start_array(std::declval())); + +template +using end_array_function_t = decltype(std::declval().end_array()); + +template +using parse_error_function_t = decltype(std::declval().parse_error( + std::declval(), std::declval(), + std::declval())); + +template +struct is_sax +{ + private: + static_assert(is_basic_json::value, + "BasicJsonType must be of type basic_json<...>"); + + 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 binary_t = typename BasicJsonType::binary_t; + using exception_t = typename BasicJsonType::exception; + + public: + static constexpr bool value = + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value && + is_detected_exact::value; +}; + +template +struct is_sax_static_asserts +{ + private: + static_assert(is_basic_json::value, + "BasicJsonType must be of type basic_json<...>"); + + 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 binary_t = typename BasicJsonType::binary_t; + using exception_t = typename BasicJsonType::exception; + + public: + static_assert(is_detected_exact::value, + "Missing/invalid function: bool null()"); + static_assert(is_detected_exact::value, + "Missing/invalid function: bool boolean(bool)"); + static_assert(is_detected_exact::value, + "Missing/invalid function: bool boolean(bool)"); + static_assert( + is_detected_exact::value, + "Missing/invalid function: bool number_integer(number_integer_t)"); + static_assert( + is_detected_exact::value, + "Missing/invalid function: bool number_unsigned(number_unsigned_t)"); + static_assert(is_detected_exact::value, + "Missing/invalid function: bool number_float(number_float_t, const string_t&)"); + static_assert( + is_detected_exact::value, + "Missing/invalid function: bool string(string_t&)"); + static_assert( + is_detected_exact::value, + "Missing/invalid function: bool binary(binary_t&)"); + static_assert(is_detected_exact::value, + "Missing/invalid function: bool start_object(std::size_t)"); + static_assert(is_detected_exact::value, + "Missing/invalid function: bool key(string_t&)"); + static_assert(is_detected_exact::value, + "Missing/invalid function: bool end_object()"); + static_assert(is_detected_exact::value, + "Missing/invalid function: bool start_array(std::size_t)"); + static_assert(is_detected_exact::value, + "Missing/invalid function: bool end_array()"); + static_assert( + is_detected_exact::value, + "Missing/invalid function: bool parse_error(std::size_t, const " + "std::string&, const exception&)"); +}; +} // namespace detail +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/meta/type_traits.hpp b/lib/json/include/nlohmann/detail/meta/type_traits.hpp new file mode 100644 index 0000000..22d0bfe --- /dev/null +++ b/lib/json/include/nlohmann/detail/meta/type_traits.hpp @@ -0,0 +1,436 @@ +#pragma once + +#include // numeric_limits +#include // false_type, is_constructible, is_integral, is_same, true_type +#include // declval +#include // tuple + +#include +#include +#include +#include +#include + +namespace nlohmann +{ +/*! +@brief detail namespace with internal helper functions + +This namespace collects functions that should not be exposed, +implementations of some @ref basic_json methods, and meta-programming helpers. + +@since version 2.1.0 +*/ +namespace detail +{ +///////////// +// helpers // +///////////// + +// Note to maintainers: +// +// Every trait in this file expects a non CV-qualified type. +// The only exceptions are in the 'aliases for detected' section +// (i.e. those of the form: decltype(T::member_function(std::declval()))) +// +// In this case, T has to be properly CV-qualified to constraint the function arguments +// (e.g. to_json(BasicJsonType&, const T&)) + +template struct is_basic_json : std::false_type {}; + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +struct is_basic_json : std::true_type {}; + +////////////////////// +// json_ref helpers // +////////////////////// + +template +class json_ref; + +template +struct is_json_ref : std::false_type {}; + +template +struct is_json_ref> : std::true_type {}; + +////////////////////////// +// aliases for detected // +////////////////////////// + +template +using mapped_type_t = typename T::mapped_type; + +template +using key_type_t = typename T::key_type; + +template +using value_type_t = typename T::value_type; + +template +using difference_type_t = typename T::difference_type; + +template +using pointer_t = typename T::pointer; + +template +using reference_t = typename T::reference; + +template +using iterator_category_t = typename T::iterator_category; + +template +using iterator_t = typename T::iterator; + +template +using to_json_function = decltype(T::to_json(std::declval()...)); + +template +using from_json_function = decltype(T::from_json(std::declval()...)); + +template +using get_template_function = decltype(std::declval().template get()); + +// trait checking if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json : std::false_type {}; + +// trait checking if j.get is valid +// use this trait instead of std::is_constructible or std::is_convertible, +// both rely on, or make use of implicit conversions, and thus fail when T +// has several constructors/operator= (see https://github.com/nlohmann/json/issues/958) +template +struct is_getable +{ + static constexpr bool value = is_detected::value; +}; + +template +struct has_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template +struct has_non_default_from_json : std::false_type {}; + +template +struct has_non_default_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if BasicJsonType::json_serializer::to_json exists +// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. +template +struct has_to_json : std::false_type {}; + +template +struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + + +/////////////////// +// is_ functions // +/////////////////// + +// https://en.cppreference.com/w/cpp/types/conjunction +template struct conjunction : std::true_type { }; +template struct conjunction : B1 { }; +template +struct conjunction +: std::conditional, B1>::type {}; + +// Reimplementation of is_constructible and is_default_constructible, due to them being broken for +// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367). +// This causes compile errors in e.g. clang 3.5 or gcc 4.9. +template +struct is_default_constructible : std::is_default_constructible {}; + +template +struct is_default_constructible> + : conjunction, is_default_constructible> {}; + +template +struct is_default_constructible> + : conjunction, is_default_constructible> {}; + +template +struct is_default_constructible> + : conjunction...> {}; + +template +struct is_default_constructible> + : conjunction...> {}; + + +template +struct is_constructible : std::is_constructible {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + + +template +struct is_iterator_traits : std::false_type {}; + +template +struct is_iterator_traits> +{ + private: + using traits = iterator_traits; + + public: + static constexpr auto value = + is_detected::value && + is_detected::value && + is_detected::value && + is_detected::value && + is_detected::value; +}; + +// The following implementation of is_complete_type is taken from +// https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/ +// and is written by Xiang Fan who agreed to using it in this library. + +template +struct is_complete_type : std::false_type {}; + +template +struct is_complete_type : std::true_type {}; + +template +struct is_compatible_object_type_impl : std::false_type {}; + +template +struct is_compatible_object_type_impl < + BasicJsonType, CompatibleObjectType, + enable_if_t < is_detected::value&& + is_detected::value >> +{ + using object_t = typename BasicJsonType::object_t; + + // macOS's is_constructible does not play well with nonesuch... + static constexpr bool value = + is_constructible::value && + is_constructible::value; +}; + +template +struct is_compatible_object_type + : is_compatible_object_type_impl {}; + +template +struct is_constructible_object_type_impl : std::false_type {}; + +template +struct is_constructible_object_type_impl < + BasicJsonType, ConstructibleObjectType, + enable_if_t < is_detected::value&& + is_detected::value >> +{ + using object_t = typename BasicJsonType::object_t; + + static constexpr bool value = + (is_default_constructible::value && + (std::is_move_assignable::value || + std::is_copy_assignable::value) && + (is_constructible::value && + std::is_same < + typename object_t::mapped_type, + typename ConstructibleObjectType::mapped_type >::value)) || + (has_from_json::value || + has_non_default_from_json < + BasicJsonType, + typename ConstructibleObjectType::mapped_type >::value); +}; + +template +struct is_constructible_object_type + : is_constructible_object_type_impl {}; + +template +struct is_compatible_string_type_impl : std::false_type {}; + +template +struct is_compatible_string_type_impl < + BasicJsonType, CompatibleStringType, + enable_if_t::value >> +{ + static constexpr auto value = + is_constructible::value; +}; + +template +struct is_compatible_string_type + : is_compatible_string_type_impl {}; + +template +struct is_constructible_string_type_impl : std::false_type {}; + +template +struct is_constructible_string_type_impl < + BasicJsonType, ConstructibleStringType, + enable_if_t::value >> +{ + static constexpr auto value = + is_constructible::value; +}; + +template +struct is_constructible_string_type + : is_constructible_string_type_impl {}; + +template +struct is_compatible_array_type_impl : std::false_type {}; + +template +struct is_compatible_array_type_impl < + BasicJsonType, CompatibleArrayType, + enable_if_t < is_detected::value&& + is_detected::value&& +// This is needed because json_reverse_iterator has a ::iterator type... +// Therefore it is detected as a CompatibleArrayType. +// The real fix would be to have an Iterable concept. + !is_iterator_traits < + iterator_traits>::value >> +{ + static constexpr bool value = + is_constructible::value; +}; + +template +struct is_compatible_array_type + : is_compatible_array_type_impl {}; + +template +struct is_constructible_array_type_impl : std::false_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t::value >> + : std::true_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t < !std::is_same::value&& + is_default_constructible::value&& +(std::is_move_assignable::value || + std::is_copy_assignable::value)&& +is_detected::value&& +is_detected::value&& +is_complete_type < +detected_t>::value >> +{ + static constexpr bool value = + // This is needed because json_reverse_iterator has a ::iterator type, + // furthermore, std::back_insert_iterator (and other iterators) have a + // base class `iterator`... Therefore it is detected as a + // ConstructibleArrayType. The real fix would be to have an Iterable + // concept. + !is_iterator_traits>::value && + + (std::is_same::value || + has_from_json::value || + has_non_default_from_json < + BasicJsonType, typename ConstructibleArrayType::value_type >::value); +}; + +template +struct is_constructible_array_type + : is_constructible_array_type_impl {}; + +template +struct is_compatible_integer_type_impl : std::false_type {}; + +template +struct is_compatible_integer_type_impl < + RealIntegerType, CompatibleNumberIntegerType, + enable_if_t < std::is_integral::value&& + std::is_integral::value&& + !std::is_same::value >> +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits; + using CompatibleLimits = std::numeric_limits; + + static constexpr auto value = + is_constructible::value && + CompatibleLimits::is_integer && + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template +struct is_compatible_integer_type + : is_compatible_integer_type_impl {}; + +template +struct is_compatible_type_impl: std::false_type {}; + +template +struct is_compatible_type_impl < + BasicJsonType, CompatibleType, + enable_if_t::value >> +{ + static constexpr bool value = + has_to_json::value; +}; + +template +struct is_compatible_type + : is_compatible_type_impl {}; + +template +struct is_constructible_tuple : std::false_type {}; + +template +struct is_constructible_tuple> : conjunction...> {}; +} // namespace detail +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/meta/void_t.hpp b/lib/json/include/nlohmann/detail/meta/void_t.hpp new file mode 100644 index 0000000..4ee2c86 --- /dev/null +++ b/lib/json/include/nlohmann/detail/meta/void_t.hpp @@ -0,0 +1,13 @@ +#pragma once + +namespace nlohmann +{ +namespace detail +{ +template struct make_void +{ + using type = void; +}; +template using void_t = typename make_void::type; +} // namespace detail +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/output/binary_writer.hpp b/lib/json/include/nlohmann/detail/output/binary_writer.hpp new file mode 100644 index 0000000..24e7c10 --- /dev/null +++ b/lib/json/include/nlohmann/detail/output/binary_writer.hpp @@ -0,0 +1,1594 @@ +#pragma once + +#include // reverse +#include // array +#include // isnan, isinf +#include // uint8_t, uint16_t, uint32_t, uint64_t +#include // memcpy +#include // numeric_limits +#include // string +#include // move + +#include +#include +#include + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// binary writer // +/////////////////// + +/*! +@brief serialization to CBOR and MessagePack values +*/ +template +class binary_writer +{ + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + using number_float_t = typename BasicJsonType::number_float_t; + + public: + /*! + @brief create a binary writer + + @param[in] adapter output adapter to write to + */ + explicit binary_writer(output_adapter_t adapter) : oa(std::move(adapter)) + { + JSON_ASSERT(oa); + } + + /*! + @param[in] j JSON value to serialize + @pre j.type() == value_t::object + */ + void write_bson(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::object: + { + write_bson_object(*j.m_value.object); + break; + } + + default: + { + JSON_THROW(type_error::create(317, "to serialize to BSON, top-level type must be object, but is " + std::string(j.type_name()), j));; + } + } + } + + /*! + @param[in] j JSON value to serialize + */ + void write_cbor(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::null: + { + oa->write_character(to_char_type(0xF6)); + break; + } + + case value_t::boolean: + { + oa->write_character(j.m_value.boolean + ? to_char_type(0xF5) + : to_char_type(0xF4)); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) + { + // CBOR does not differentiate between positive signed + // integers and unsigned integers. Therefore, we used the + // code from the value_t::number_unsigned case here. + if (j.m_value.number_integer <= 0x17) + { + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x18)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x19)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x1A)); + write_number(static_cast(j.m_value.number_integer)); + } + else + { + oa->write_character(to_char_type(0x1B)); + write_number(static_cast(j.m_value.number_integer)); + } + } + else + { + // The conversions below encode the sign in the first + // byte, and the value is converted to a positive number. + const auto positive_number = -1 - j.m_value.number_integer; + if (j.m_value.number_integer >= -24) + { + write_number(static_cast(0x20 + positive_number)); + } + else if (positive_number <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x38)); + write_number(static_cast(positive_number)); + } + else if (positive_number <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x39)); + write_number(static_cast(positive_number)); + } + else if (positive_number <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x3A)); + write_number(static_cast(positive_number)); + } + else + { + oa->write_character(to_char_type(0x3B)); + write_number(static_cast(positive_number)); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned <= 0x17) + { + write_number(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x18)); + write_number(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x19)); + write_number(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x1A)); + write_number(static_cast(j.m_value.number_unsigned)); + } + else + { + oa->write_character(to_char_type(0x1B)); + write_number(static_cast(j.m_value.number_unsigned)); + } + break; + } + + case value_t::number_float: + { + if (std::isnan(j.m_value.number_float)) + { + // NaN is 0xf97e00 in CBOR + oa->write_character(to_char_type(0xF9)); + oa->write_character(to_char_type(0x7E)); + oa->write_character(to_char_type(0x00)); + } + else if (std::isinf(j.m_value.number_float)) + { + // Infinity is 0xf97c00, -Infinity is 0xf9fc00 + oa->write_character(to_char_type(0xf9)); + oa->write_character(j.m_value.number_float > 0 ? to_char_type(0x7C) : to_char_type(0xFC)); + oa->write_character(to_char_type(0x00)); + } + else + { + write_compact_float(j.m_value.number_float, detail::input_format_t::cbor); + } + break; + } + + case value_t::string: + { + // step 1: write control byte and the string length + const auto N = j.m_value.string->size(); + if (N <= 0x17) + { + write_number(static_cast(0x60 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x78)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x79)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x7A)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x7B)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write the string + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + // step 1: write control byte and the array size + const auto N = j.m_value.array->size(); + if (N <= 0x17) + { + write_number(static_cast(0x80 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x98)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x99)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x9A)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x9B)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + for (const auto& el : *j.m_value.array) + { + write_cbor(el); + } + break; + } + + case value_t::binary: + { + if (j.m_value.binary->has_subtype()) + { + write_number(static_cast(0xd8)); + write_number(j.m_value.binary->subtype()); + } + + // step 1: write control byte and the binary array size + const auto N = j.m_value.binary->size(); + if (N <= 0x17) + { + write_number(static_cast(0x40 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x58)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x59)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x5A)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x5B)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + oa->write_characters( + reinterpret_cast(j.m_value.binary->data()), + N); + + break; + } + + case value_t::object: + { + // step 1: write control byte and the object size + const auto N = j.m_value.object->size(); + if (N <= 0x17) + { + write_number(static_cast(0xA0 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0xB8)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0xB9)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0xBA)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0xBB)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + for (const auto& el : *j.m_value.object) + { + write_cbor(el.first); + write_cbor(el.second); + } + break; + } + + default: + break; + } + } + + /*! + @param[in] j JSON value to serialize + */ + void write_msgpack(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::null: // nil + { + oa->write_character(to_char_type(0xC0)); + break; + } + + case value_t::boolean: // true and false + { + oa->write_character(j.m_value.boolean + ? to_char_type(0xC3) + : to_char_type(0xC2)); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) + { + // MessagePack does not differentiate between positive + // signed integers and unsigned integers. Therefore, we used + // the code from the value_t::number_unsigned case here. + if (j.m_value.number_unsigned < 128) + { + // positive fixnum + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 8 + oa->write_character(to_char_type(0xCC)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 16 + oa->write_character(to_char_type(0xCD)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 32 + oa->write_character(to_char_type(0xCE)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 64 + oa->write_character(to_char_type(0xCF)); + write_number(static_cast(j.m_value.number_integer)); + } + } + else + { + if (j.m_value.number_integer >= -32) + { + // negative fixnum + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() && + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 8 + oa->write_character(to_char_type(0xD0)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() && + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 16 + oa->write_character(to_char_type(0xD1)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() && + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 32 + oa->write_character(to_char_type(0xD2)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() && + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 64 + oa->write_character(to_char_type(0xD3)); + write_number(static_cast(j.m_value.number_integer)); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned < 128) + { + // positive fixnum + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 8 + oa->write_character(to_char_type(0xCC)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 16 + oa->write_character(to_char_type(0xCD)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 32 + oa->write_character(to_char_type(0xCE)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 64 + oa->write_character(to_char_type(0xCF)); + write_number(static_cast(j.m_value.number_integer)); + } + break; + } + + case value_t::number_float: + { + write_compact_float(j.m_value.number_float, detail::input_format_t::msgpack); + break; + } + + case value_t::string: + { + // step 1: write control byte and the string length + const auto N = j.m_value.string->size(); + if (N <= 31) + { + // fixstr + write_number(static_cast(0xA0 | N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // str 8 + oa->write_character(to_char_type(0xD9)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // str 16 + oa->write_character(to_char_type(0xDA)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // str 32 + oa->write_character(to_char_type(0xDB)); + write_number(static_cast(N)); + } + + // step 2: write the string + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + // step 1: write control byte and the array size + const auto N = j.m_value.array->size(); + if (N <= 15) + { + // fixarray + write_number(static_cast(0x90 | N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // array 16 + oa->write_character(to_char_type(0xDC)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // array 32 + oa->write_character(to_char_type(0xDD)); + write_number(static_cast(N)); + } + + // step 2: write each element + for (const auto& el : *j.m_value.array) + { + write_msgpack(el); + } + break; + } + + case value_t::binary: + { + // step 0: determine if the binary type has a set subtype to + // determine whether or not to use the ext or fixext types + const bool use_ext = j.m_value.binary->has_subtype(); + + // step 1: write control byte and the byte string length + const auto N = j.m_value.binary->size(); + if (N <= (std::numeric_limits::max)()) + { + std::uint8_t output_type{}; + bool fixed = true; + if (use_ext) + { + switch (N) + { + case 1: + output_type = 0xD4; // fixext 1 + break; + case 2: + output_type = 0xD5; // fixext 2 + break; + case 4: + output_type = 0xD6; // fixext 4 + break; + case 8: + output_type = 0xD7; // fixext 8 + break; + case 16: + output_type = 0xD8; // fixext 16 + break; + default: + output_type = 0xC7; // ext 8 + fixed = false; + break; + } + + } + else + { + output_type = 0xC4; // bin 8 + fixed = false; + } + + oa->write_character(to_char_type(output_type)); + if (!fixed) + { + write_number(static_cast(N)); + } + } + else if (N <= (std::numeric_limits::max)()) + { + std::uint8_t output_type = use_ext + ? 0xC8 // ext 16 + : 0xC5; // bin 16 + + oa->write_character(to_char_type(output_type)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + std::uint8_t output_type = use_ext + ? 0xC9 // ext 32 + : 0xC6; // bin 32 + + oa->write_character(to_char_type(output_type)); + write_number(static_cast(N)); + } + + // step 1.5: if this is an ext type, write the subtype + if (use_ext) + { + write_number(static_cast(j.m_value.binary->subtype())); + } + + // step 2: write the byte string + oa->write_characters( + reinterpret_cast(j.m_value.binary->data()), + N); + + break; + } + + case value_t::object: + { + // step 1: write control byte and the object size + const auto N = j.m_value.object->size(); + if (N <= 15) + { + // fixmap + write_number(static_cast(0x80 | (N & 0xF))); + } + else if (N <= (std::numeric_limits::max)()) + { + // map 16 + oa->write_character(to_char_type(0xDE)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // map 32 + oa->write_character(to_char_type(0xDF)); + write_number(static_cast(N)); + } + + // step 2: write each element + for (const auto& el : *j.m_value.object) + { + write_msgpack(el.first); + write_msgpack(el.second); + } + break; + } + + default: + break; + } + } + + /*! + @param[in] j JSON value to serialize + @param[in] use_count whether to use '#' prefixes (optimized format) + @param[in] use_type whether to use '$' prefixes (optimized format) + @param[in] add_prefix whether prefixes need to be used for this value + */ + 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(to_char_type('Z')); + } + break; + } + + case value_t::boolean: + { + if (add_prefix) + { + oa->write_character(j.m_value.boolean + ? to_char_type('T') + : to_char_type('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(to_char_type('S')); + } + write_number_with_ubjson_prefix(j.m_value.string->size(), true); + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + if (add_prefix) + { + oa->write_character(to_char_type('[')); + } + + bool prefix_required = true; + if (use_type && !j.m_value.array->empty()) + { + JSON_ASSERT(use_count); + const CharType 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(to_char_type('$')); + oa->write_character(first_prefix); + } + } + + if (use_count) + { + oa->write_character(to_char_type('#')); + 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 (!use_count) + { + oa->write_character(to_char_type(']')); + } + + break; + } + + case value_t::binary: + { + if (add_prefix) + { + oa->write_character(to_char_type('[')); + } + + if (use_type && !j.m_value.binary->empty()) + { + JSON_ASSERT(use_count); + oa->write_character(to_char_type('$')); + oa->write_character('U'); + } + + if (use_count) + { + oa->write_character(to_char_type('#')); + write_number_with_ubjson_prefix(j.m_value.binary->size(), true); + } + + if (use_type) + { + oa->write_characters( + reinterpret_cast(j.m_value.binary->data()), + j.m_value.binary->size()); + } + else + { + for (size_t i = 0; i < j.m_value.binary->size(); ++i) + { + oa->write_character(to_char_type('U')); + oa->write_character(j.m_value.binary->data()[i]); + } + } + + if (!use_count) + { + oa->write_character(to_char_type(']')); + } + + break; + } + + case value_t::object: + { + if (add_prefix) + { + oa->write_character(to_char_type('{')); + } + + bool prefix_required = true; + if (use_type && !j.m_value.object->empty()) + { + JSON_ASSERT(use_count); + const CharType 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(to_char_type('$')); + oa->write_character(first_prefix); + } + } + + if (use_count) + { + oa->write_character(to_char_type('#')); + 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(el.first.c_str()), + el.first.size()); + write_ubjson(el.second, use_count, use_type, prefix_required); + } + + if (!use_count) + { + oa->write_character(to_char_type('}')); + } + + break; + } + + default: + break; + } + } + + private: + ////////// + // BSON // + ////////// + + /*! + @return The size of a BSON document entry header, including the id marker + and the entry name size (and its null-terminator). + */ + static std::size_t calc_bson_entry_header_size(const string_t& name, const BasicJsonType& j) + { + const auto it = name.find(static_cast(0)); + if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos)) + { + JSON_THROW(out_of_range::create(409, "BSON key cannot contain code point U+0000 (at byte " + std::to_string(it) + ")", j)); + } + + return /*id*/ 1ul + name.size() + /*zero-terminator*/1u; + } + + /*! + @brief Writes the given @a element_type and @a name to the output adapter + */ + void write_bson_entry_header(const string_t& name, + const std::uint8_t element_type) + { + oa->write_character(to_char_type(element_type)); // boolean + oa->write_characters( + reinterpret_cast(name.c_str()), + name.size() + 1u); + } + + /*! + @brief Writes a BSON element with key @a name and boolean value @a value + */ + void write_bson_boolean(const string_t& name, + const bool value) + { + write_bson_entry_header(name, 0x08); + oa->write_character(value ? to_char_type(0x01) : to_char_type(0x00)); + } + + /*! + @brief Writes a BSON element with key @a name and double value @a value + */ + void write_bson_double(const string_t& name, + const double value) + { + write_bson_entry_header(name, 0x01); + write_number(value); + } + + /*! + @return The size of the BSON-encoded string in @a value + */ + static std::size_t calc_bson_string_size(const string_t& value) + { + return sizeof(std::int32_t) + value.size() + 1ul; + } + + /*! + @brief Writes a BSON element with key @a name and string value @a value + */ + void write_bson_string(const string_t& name, + const string_t& value) + { + write_bson_entry_header(name, 0x02); + + write_number(static_cast(value.size() + 1ul)); + oa->write_characters( + reinterpret_cast(value.c_str()), + value.size() + 1); + } + + /*! + @brief Writes a BSON element with key @a name and null value + */ + void write_bson_null(const string_t& name) + { + write_bson_entry_header(name, 0x0A); + } + + /*! + @return The size of the BSON-encoded integer @a value + */ + static std::size_t calc_bson_integer_size(const std::int64_t value) + { + return (std::numeric_limits::min)() <= value && value <= (std::numeric_limits::max)() + ? sizeof(std::int32_t) + : sizeof(std::int64_t); + } + + /*! + @brief Writes a BSON element with key @a name and integer @a value + */ + void write_bson_integer(const string_t& name, + const std::int64_t value) + { + if ((std::numeric_limits::min)() <= value && value <= (std::numeric_limits::max)()) + { + write_bson_entry_header(name, 0x10); // int32 + write_number(static_cast(value)); + } + else + { + write_bson_entry_header(name, 0x12); // int64 + write_number(static_cast(value)); + } + } + + /*! + @return The size of the BSON-encoded unsigned integer in @a j + */ + static constexpr std::size_t calc_bson_unsigned_size(const std::uint64_t value) noexcept + { + return (value <= static_cast((std::numeric_limits::max)())) + ? sizeof(std::int32_t) + : sizeof(std::int64_t); + } + + /*! + @brief Writes a BSON element with key @a name and unsigned @a value + */ + void write_bson_unsigned(const string_t& name, + const BasicJsonType& j) + { + if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + write_bson_entry_header(name, 0x10 /* int32 */); + write_number(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + write_bson_entry_header(name, 0x12 /* int64 */); + write_number(static_cast(j.m_value.number_unsigned)); + } + else + { + JSON_THROW(out_of_range::create(407, "integer number " + std::to_string(j.m_value.number_unsigned) + " cannot be represented by BSON as it does not fit int64", j)); + } + } + + /*! + @brief Writes a BSON element with key @a name and object @a value + */ + void write_bson_object_entry(const string_t& name, + const typename BasicJsonType::object_t& value) + { + write_bson_entry_header(name, 0x03); // object + write_bson_object(value); + } + + /*! + @return The size of the BSON-encoded array @a value + */ + static std::size_t calc_bson_array_size(const typename BasicJsonType::array_t& value) + { + std::size_t array_index = 0ul; + + const std::size_t embedded_document_size = std::accumulate(std::begin(value), std::end(value), std::size_t(0), [&array_index](std::size_t result, const typename BasicJsonType::array_t::value_type & el) + { + return result + calc_bson_element_size(std::to_string(array_index++), el); + }); + + return sizeof(std::int32_t) + embedded_document_size + 1ul; + } + + /*! + @return The size of the BSON-encoded binary array @a value + */ + static std::size_t calc_bson_binary_size(const typename BasicJsonType::binary_t& value) + { + return sizeof(std::int32_t) + value.size() + 1ul; + } + + /*! + @brief Writes a BSON element with key @a name and array @a value + */ + void write_bson_array(const string_t& name, + const typename BasicJsonType::array_t& value) + { + write_bson_entry_header(name, 0x04); // array + write_number(static_cast(calc_bson_array_size(value))); + + std::size_t array_index = 0ul; + + for (const auto& el : value) + { + write_bson_element(std::to_string(array_index++), el); + } + + oa->write_character(to_char_type(0x00)); + } + + /*! + @brief Writes a BSON element with key @a name and binary value @a value + */ + void write_bson_binary(const string_t& name, + const binary_t& value) + { + write_bson_entry_header(name, 0x05); + + write_number(static_cast(value.size())); + write_number(value.has_subtype() ? value.subtype() : std::uint8_t(0x00)); + + oa->write_characters(reinterpret_cast(value.data()), value.size()); + } + + /*! + @brief Calculates the size necessary to serialize the JSON value @a j with its @a name + @return The calculated size for the BSON document entry for @a j with the given @a name. + */ + static std::size_t calc_bson_element_size(const string_t& name, + const BasicJsonType& j) + { + const auto header_size = calc_bson_entry_header_size(name, j); + switch (j.type()) + { + case value_t::object: + return header_size + calc_bson_object_size(*j.m_value.object); + + case value_t::array: + return header_size + calc_bson_array_size(*j.m_value.array); + + case value_t::binary: + return header_size + calc_bson_binary_size(*j.m_value.binary); + + case value_t::boolean: + return header_size + 1ul; + + case value_t::number_float: + return header_size + 8ul; + + case value_t::number_integer: + return header_size + calc_bson_integer_size(j.m_value.number_integer); + + case value_t::number_unsigned: + return header_size + calc_bson_unsigned_size(j.m_value.number_unsigned); + + case value_t::string: + return header_size + calc_bson_string_size(*j.m_value.string); + + case value_t::null: + return header_size + 0ul; + + // LCOV_EXCL_START + default: + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) + return 0ul; + // LCOV_EXCL_STOP + } + } + + /*! + @brief Serializes the JSON value @a j to BSON and associates it with the + key @a name. + @param name The name to associate with the JSON entity @a j within the + current BSON document + */ + void write_bson_element(const string_t& name, + const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::object: + return write_bson_object_entry(name, *j.m_value.object); + + case value_t::array: + return write_bson_array(name, *j.m_value.array); + + case value_t::binary: + return write_bson_binary(name, *j.m_value.binary); + + case value_t::boolean: + return write_bson_boolean(name, j.m_value.boolean); + + case value_t::number_float: + return write_bson_double(name, j.m_value.number_float); + + case value_t::number_integer: + return write_bson_integer(name, j.m_value.number_integer); + + case value_t::number_unsigned: + return write_bson_unsigned(name, j); + + case value_t::string: + return write_bson_string(name, *j.m_value.string); + + case value_t::null: + return write_bson_null(name); + + // LCOV_EXCL_START + default: + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) + return; + // LCOV_EXCL_STOP + } + } + + /*! + @brief Calculates the size of the BSON serialization of the given + JSON-object @a j. + @param[in] value JSON value to serialize + @pre value.type() == value_t::object + */ + static std::size_t calc_bson_object_size(const typename BasicJsonType::object_t& value) + { + std::size_t document_size = std::accumulate(value.begin(), value.end(), std::size_t(0), + [](size_t result, const typename BasicJsonType::object_t::value_type & el) + { + return result += calc_bson_element_size(el.first, el.second); + }); + + return sizeof(std::int32_t) + document_size + 1ul; + } + + /*! + @param[in] value JSON value to serialize + @pre value.type() == value_t::object + */ + void write_bson_object(const typename BasicJsonType::object_t& value) + { + write_number(static_cast(calc_bson_object_size(value))); + + for (const auto& el : value) + { + write_bson_element(el.first, el.second); + } + + oa->write_character(to_char_type(0x00)); + } + + ////////// + // CBOR // + ////////// + + static constexpr CharType get_cbor_float_prefix(float /*unused*/) + { + return to_char_type(0xFA); // Single-Precision Float + } + + static constexpr CharType get_cbor_float_prefix(double /*unused*/) + { + return to_char_type(0xFB); // Double-Precision Float + } + + ///////////// + // MsgPack // + ///////////// + + static constexpr CharType get_msgpack_float_prefix(float /*unused*/) + { + return to_char_type(0xCA); // float 32 + } + + static constexpr CharType get_msgpack_float_prefix(double /*unused*/) + { + return to_char_type(0xCB); // float 64 + } + + //////////// + // UBJSON // + //////////// + + // UBJSON: write number (floating point) + template::value, int>::type = 0> + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix) + { + if (add_prefix) + { + oa->write_character(get_ubjson_float_prefix(n)); + } + write_number(n); + } + + // UBJSON: write number (unsigned integer) + template::value, int>::type = 0> + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix) + { + if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('i')); // int8 + } + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('U')); // uint8 + } + write_number(static_cast(n)); + } + else if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('I')); // int16 + } + write_number(static_cast(n)); + } + else if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('l')); // int32 + } + write_number(static_cast(n)); + } + else if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('L')); // int64 + } + write_number(static_cast(n)); + } + else + { + if (add_prefix) + { + oa->write_character(to_char_type('H')); // high-precision number + } + + const auto number = BasicJsonType(n).dump(); + write_number_with_ubjson_prefix(number.size(), true); + for (std::size_t i = 0; i < number.size(); ++i) + { + oa->write_character(to_char_type(static_cast(number[i]))); + } + } + } + + // UBJSON: write number (signed integer) + template < typename NumberType, typename std::enable_if < + std::is_signed::value&& + !std::is_floating_point::value, int >::type = 0 > + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix) + { + if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('i')); // int8 + } + write_number(static_cast(n)); + } + else if (static_cast((std::numeric_limits::min)()) <= n && n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('U')); // uint8 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('I')); // int16 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('l')); // int32 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('L')); // int64 + } + write_number(static_cast(n)); + } + // LCOV_EXCL_START + else + { + if (add_prefix) + { + oa->write_character(to_char_type('H')); // high-precision number + } + + const auto number = BasicJsonType(n).dump(); + write_number_with_ubjson_prefix(number.size(), true); + for (std::size_t i = 0; i < number.size(); ++i) + { + oa->write_character(to_char_type(static_cast(number[i]))); + } + } + // LCOV_EXCL_STOP + } + + /*! + @brief determine the type prefix of container values + */ + CharType 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::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'i'; + } + if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'U'; + } + if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'I'; + } + if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'l'; + } + if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'L'; + } + // anything else is treated as high-precision number + return 'H'; // LCOV_EXCL_LINE + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'i'; + } + if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'U'; + } + if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'I'; + } + if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'l'; + } + if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'L'; + } + // anything else is treated as high-precision number + return 'H'; // LCOV_EXCL_LINE + } + + case value_t::number_float: + return get_ubjson_float_prefix(j.m_value.number_float); + + case value_t::string: + return 'S'; + + case value_t::array: // fallthrough + case value_t::binary: + return '['; + + case value_t::object: + return '{'; + + default: // discarded values + return 'N'; + } + } + + static constexpr CharType get_ubjson_float_prefix(float /*unused*/) + { + return 'd'; // float 32 + } + + static constexpr CharType get_ubjson_float_prefix(double /*unused*/) + { + return 'D'; // float 64 + } + + /////////////////////// + // Utility functions // + /////////////////////// + + /* + @brief write a number to output input + @param[in] n number of type @a NumberType + @tparam NumberType the type of the number + @tparam OutputIsLittleEndian Set to true if output data is + required to be little endian + + @note This function needs to respect the system's endianess, because bytes + in CBOR, MessagePack, and UBJSON are stored in network order (big + endian) and therefore need reordering on little endian systems. + */ + template + void write_number(const NumberType n) + { + // step 1: write number to array of length NumberType + std::array vec{}; + std::memcpy(vec.data(), &n, sizeof(NumberType)); + + // step 2: write array to output (with possible reordering) + if (is_little_endian != OutputIsLittleEndian) + { + // reverse byte order prior to conversion if necessary + std::reverse(vec.begin(), vec.end()); + } + + oa->write_characters(vec.data(), sizeof(NumberType)); + } + + void write_compact_float(const number_float_t n, detail::input_format_t format) + { + if (static_cast(n) >= static_cast(std::numeric_limits::lowest()) && + static_cast(n) <= static_cast((std::numeric_limits::max)()) && + static_cast(static_cast(n)) == static_cast(n)) + { + oa->write_character(format == detail::input_format_t::cbor + ? get_cbor_float_prefix(static_cast(n)) + : get_msgpack_float_prefix(static_cast(n))); + write_number(static_cast(n)); + } + else + { + oa->write_character(format == detail::input_format_t::cbor + ? get_cbor_float_prefix(n) + : get_msgpack_float_prefix(n)); + write_number(n); + } + } + + public: + // The following to_char_type functions are implement the conversion + // between uint8_t and CharType. In case CharType is not unsigned, + // such a conversion is required to allow values greater than 128. + // See for a discussion. + template < typename C = CharType, + enable_if_t < std::is_signed::value && std::is_signed::value > * = nullptr > + static constexpr CharType to_char_type(std::uint8_t x) noexcept + { + return *reinterpret_cast(&x); + } + + template < typename C = CharType, + enable_if_t < std::is_signed::value && std::is_unsigned::value > * = nullptr > + static CharType to_char_type(std::uint8_t x) noexcept + { + static_assert(sizeof(std::uint8_t) == sizeof(CharType), "size of CharType must be equal to std::uint8_t"); + static_assert(std::is_trivial::value, "CharType must be trivial"); + CharType result; + std::memcpy(&result, &x, sizeof(x)); + return result; + } + + template::value>* = nullptr> + static constexpr CharType to_char_type(std::uint8_t x) noexcept + { + return x; + } + + template < typename InputCharType, typename C = CharType, + enable_if_t < + std::is_signed::value && + std::is_signed::value && + std::is_same::type>::value + > * = nullptr > + static constexpr CharType to_char_type(InputCharType x) noexcept + { + return x; + } + + private: + /// whether we can assume little endianess + const bool is_little_endian = little_endianess(); + + /// the output + output_adapter_t oa = nullptr; +}; +} // namespace detail +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/output/output_adapters.hpp b/lib/json/include/nlohmann/detail/output/output_adapters.hpp new file mode 100644 index 0000000..25886ad --- /dev/null +++ b/lib/json/include/nlohmann/detail/output/output_adapters.hpp @@ -0,0 +1,129 @@ +#pragma once + +#include // copy +#include // size_t +#include // streamsize +#include // back_inserter +#include // shared_ptr, make_shared +#include // basic_ostream +#include // basic_string +#include // vector +#include + +namespace nlohmann +{ +namespace detail +{ +/// abstract output adapter interface +template 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; + + output_adapter_protocol() = default; + output_adapter_protocol(const output_adapter_protocol&) = default; + output_adapter_protocol(output_adapter_protocol&&) noexcept = default; + output_adapter_protocol& operator=(const output_adapter_protocol&) = default; + output_adapter_protocol& operator=(output_adapter_protocol&&) noexcept = default; +}; + +/// a type to simplify interfaces +template +using output_adapter_t = std::shared_ptr>; + +/// output adapter for byte vectors +template +class output_vector_adapter : public output_adapter_protocol +{ + public: + explicit output_vector_adapter(std::vector& vec) noexcept + : v(vec) + {} + + void write_character(CharType c) override + { + v.push_back(c); + } + + JSON_HEDLEY_NON_NULL(2) + void write_characters(const CharType* s, std::size_t length) override + { + std::copy(s, s + length, std::back_inserter(v)); + } + + private: + std::vector& v; +}; + +/// output adapter for output streams +template +class output_stream_adapter : public output_adapter_protocol +{ + public: + explicit output_stream_adapter(std::basic_ostream& s) noexcept + : stream(s) + {} + + void write_character(CharType c) override + { + stream.put(c); + } + + JSON_HEDLEY_NON_NULL(2) + void write_characters(const CharType* s, std::size_t length) override + { + stream.write(s, static_cast(length)); + } + + private: + std::basic_ostream& stream; +}; + +/// output adapter for basic_string +template> +class output_string_adapter : public output_adapter_protocol +{ + public: + explicit output_string_adapter(StringType& s) noexcept + : str(s) + {} + + void write_character(CharType c) override + { + str.push_back(c); + } + + JSON_HEDLEY_NON_NULL(2) + void write_characters(const CharType* s, std::size_t length) override + { + str.append(s, length); + } + + private: + StringType& str; +}; + +template> +class output_adapter +{ + public: + output_adapter(std::vector& vec) + : oa(std::make_shared>(vec)) {} + + output_adapter(std::basic_ostream& s) + : oa(std::make_shared>(s)) {} + + output_adapter(StringType& s) + : oa(std::make_shared>(s)) {} + + operator output_adapter_t() + { + return oa; + } + + private: + output_adapter_t oa = nullptr; +}; +} // namespace detail +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/output/serializer.hpp b/lib/json/include/nlohmann/detail/output/serializer.hpp new file mode 100644 index 0000000..dfbe543 --- /dev/null +++ b/lib/json/include/nlohmann/detail/output/serializer.hpp @@ -0,0 +1,954 @@ +#pragma once + +#include // reverse, remove, fill, find, none_of +#include // array +#include // localeconv, lconv +#include // labs, isfinite, isnan, signbit +#include // size_t, ptrdiff_t +#include // uint8_t +#include // snprintf +#include // numeric_limits +#include // string, char_traits +#include // is_same +#include // move + +#include +#include +#include +#include +#include +#include +#include + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// serialization // +/////////////////// + +/// how to treat decoding errors +enum class error_handler_t +{ + strict, ///< throw a type_error exception in case of invalid UTF-8 + replace, ///< replace invalid UTF-8 sequences with U+FFFD + ignore ///< ignore invalid UTF-8 sequences +}; + +template +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; + using binary_char_t = typename BasicJsonType::binary_t::value_type; + static constexpr std::uint8_t UTF8_ACCEPT = 0; + static constexpr std::uint8_t UTF8_REJECT = 1; + + public: + /*! + @param[in] s output stream to serialize to + @param[in] ichar indentation character to use + @param[in] error_handler_ how to react on decoding errors + */ + serializer(output_adapter_t s, const char ichar, + error_handler_t error_handler_ = error_handler_t::strict) + : o(std::move(s)) + , loc(std::localeconv()) + , thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits::to_char_type(* (loc->thousands_sep))) + , decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits::to_char_type(* (loc->decimal_point))) + , indent_char(ichar) + , indent_string(512, indent_char) + , error_handler(error_handler_) + {} + + // delete because of pointer members + serializer(const serializer&) = delete; + serializer& operator=(const serializer&) = delete; + serializer(serializer&&) = delete; + serializer& operator=(serializer&&) = delete; + ~serializer() = default; + + /*! + @brief internal implementation of the serialization function + + This function is called by the public member function dump and organizes + the serialization internally. The indentation level is propagated as + additional parameter. In case of arrays and objects, the function is + called recursively. + + - strings and object keys are escaped using `escape_string()` + - integer numbers are converted implicitly via `operator<<` + - floating-point numbers are converted to a string using `"%g"` format + - binary values are serialized as objects containing the subtype and the + byte array + + @param[in] val value to serialize + @param[in] pretty_print whether the output shall be pretty-printed + @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters + in the output are escaped with `\uXXXX` sequences, and the result consists + of ASCII characters only. + @param[in] indent_step the indent level + @param[in] current_indent the current indent level (only used internally) + */ + 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); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + 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); + } + + // last element + JSON_ASSERT(i != val.m_value.object->cend()); + JSON_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('{'); + + // first n-1 elements + 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(','); + } + + // last element + JSON_ASSERT(i != val.m_value.object->cend()); + JSON_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); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + 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); + } + + // last element + JSON_ASSERT(!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('['); + + // first n-1 elements + 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(','); + } + + // last element + JSON_ASSERT(!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::binary: + { + if (pretty_print) + { + o->write_characters("{\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + o->write_characters(indent_string.c_str(), new_indent); + + o->write_characters("\"bytes\": [", 10); + + if (!val.m_value.binary->empty()) + { + for (auto i = val.m_value.binary->cbegin(); + i != val.m_value.binary->cend() - 1; ++i) + { + dump_integer(*i); + o->write_characters(", ", 2); + } + dump_integer(val.m_value.binary->back()); + } + + o->write_characters("],\n", 3); + o->write_characters(indent_string.c_str(), new_indent); + + o->write_characters("\"subtype\": ", 11); + if (val.m_value.binary->has_subtype()) + { + dump_integer(val.m_value.binary->subtype()); + } + else + { + o->write_characters("null", 4); + } + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character('}'); + } + else + { + o->write_characters("{\"bytes\":[", 10); + + if (!val.m_value.binary->empty()) + { + for (auto i = val.m_value.binary->cbegin(); + i != val.m_value.binary->cend() - 1; ++i) + { + dump_integer(*i); + o->write_character(','); + } + dump_integer(val.m_value.binary->back()); + } + + o->write_characters("],\"subtype\":", 12); + if (val.m_value.binary->has_subtype()) + { + dump_integer(val.m_value.binary->subtype()); + o->write_character('}'); + } + else + { + o->write_characters("null}", 5); + } + } + 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("", 11); + return; + } + + case value_t::null: + { + o->write_characters("null", 4); + return; + } + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } + } + + JSON_PRIVATE_UNLESS_TESTED: + /*! + @brief dump escaped string + + Escape a string by replacing certain special characters by a sequence of an + escape character (backslash) and another character and other control + characters by a sequence of "\u" followed by a four-digit hex + representation. The escaped string is written to output stream @a o. + + @param[in] s the string to escape + @param[in] ensure_ascii whether to escape non-ASCII characters with + \uXXXX sequences + + @complexity Linear in the length of string @a s. + */ + void dump_escaped(const string_t& s, const bool ensure_ascii) + { + std::uint32_t codepoint{}; + std::uint8_t state = UTF8_ACCEPT; + std::size_t bytes = 0; // number of bytes written to string_buffer + + // number of bytes written at the point of the last valid byte + std::size_t bytes_after_last_accept = 0; + std::size_t undumped_chars = 0; + + for (std::size_t i = 0; i < s.size(); ++i) + { + const auto byte = static_cast(s[i]); + + switch (decode(state, codepoint, byte)) + { + case UTF8_ACCEPT: // decode found a new code point + { + switch (codepoint) + { + case 0x08: // backspace + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'b'; + break; + } + + case 0x09: // horizontal tab + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 't'; + break; + } + + case 0x0A: // newline + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'n'; + break; + } + + case 0x0C: // formfeed + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'f'; + break; + } + + case 0x0D: // carriage return + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'r'; + break; + } + + case 0x22: // quotation mark + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\"'; + break; + } + + case 0x5C: // reverse solidus + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\\'; + break; + } + + default: + { + // escape control characters (0x00..0x1F) or, if + // ensure_ascii parameter is used, non-ASCII characters + if ((codepoint <= 0x1F) || (ensure_ascii && (codepoint >= 0x7F))) + { + if (codepoint <= 0xFFFF) + { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + (std::snprintf)(string_buffer.data() + bytes, 7, "\\u%04x", + static_cast(codepoint)); + bytes += 6; + } + else + { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + (std::snprintf)(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x", + static_cast(0xD7C0u + (codepoint >> 10u)), + static_cast(0xDC00u + (codepoint & 0x3FFu))); + bytes += 12; + } + } + else + { + // copy byte to buffer (all previous bytes + // been copied have in default case above) + string_buffer[bytes++] = s[i]; + } + break; + } + } + + // write buffer and reset index; there must be 13 bytes + // left, as this is the maximal number of bytes to be + // written ("\uxxxx\uxxxx\0") for one code point + if (string_buffer.size() - bytes < 13) + { + o->write_characters(string_buffer.data(), bytes); + bytes = 0; + } + + // remember the byte position of this accept + bytes_after_last_accept = bytes; + undumped_chars = 0; + break; + } + + case UTF8_REJECT: // decode found invalid UTF-8 byte + { + switch (error_handler) + { + case error_handler_t::strict: + { + std::string sn(3, '\0'); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + (std::snprintf)(&sn[0], sn.size(), "%.2X", byte); + JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + sn, BasicJsonType())); + } + + case error_handler_t::ignore: + case error_handler_t::replace: + { + // in case we saw this character the first time, we + // would like to read it again, because the byte + // may be OK for itself, but just not OK for the + // previous sequence + if (undumped_chars > 0) + { + --i; + } + + // reset length buffer to the last accepted index; + // thus removing/ignoring the invalid characters + bytes = bytes_after_last_accept; + + if (error_handler == error_handler_t::replace) + { + // add a replacement character + if (ensure_ascii) + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'u'; + string_buffer[bytes++] = 'f'; + string_buffer[bytes++] = 'f'; + string_buffer[bytes++] = 'f'; + string_buffer[bytes++] = 'd'; + } + else + { + string_buffer[bytes++] = detail::binary_writer::to_char_type('\xEF'); + string_buffer[bytes++] = detail::binary_writer::to_char_type('\xBF'); + string_buffer[bytes++] = detail::binary_writer::to_char_type('\xBD'); + } + + // write buffer and reset index; there must be 13 bytes + // left, as this is the maximal number of bytes to be + // written ("\uxxxx\uxxxx\0") for one code point + if (string_buffer.size() - bytes < 13) + { + o->write_characters(string_buffer.data(), bytes); + bytes = 0; + } + + bytes_after_last_accept = bytes; + } + + undumped_chars = 0; + + // continue processing the string + state = UTF8_ACCEPT; + break; + } + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } + break; + } + + default: // decode found yet incomplete multi-byte code point + { + if (!ensure_ascii) + { + // code point will not be escaped - copy byte to buffer + string_buffer[bytes++] = s[i]; + } + ++undumped_chars; + break; + } + } + } + + // we finished processing the string + if (JSON_HEDLEY_LIKELY(state == UTF8_ACCEPT)) + { + // write buffer + if (bytes > 0) + { + o->write_characters(string_buffer.data(), bytes); + } + } + else + { + // we finish reading, but do not accept: string was incomplete + switch (error_handler) + { + case error_handler_t::strict: + { + std::string sn(3, '\0'); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + (std::snprintf)(&sn[0], sn.size(), "%.2X", static_cast(s.back())); + JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + sn, BasicJsonType())); + } + + case error_handler_t::ignore: + { + // write all accepted bytes + o->write_characters(string_buffer.data(), bytes_after_last_accept); + break; + } + + case error_handler_t::replace: + { + // write all accepted bytes + o->write_characters(string_buffer.data(), bytes_after_last_accept); + // add a replacement character + if (ensure_ascii) + { + o->write_characters("\\ufffd", 6); + } + else + { + o->write_characters("\xEF\xBF\xBD", 3); + } + break; + } + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } + } + } + + private: + /*! + @brief count digits + + Count the number of decimal (base 10) digits for an input unsigned integer. + + @param[in] x unsigned integer number to count its digits + @return number of decimal digits + */ + inline unsigned int count_digits(number_unsigned_t x) noexcept + { + unsigned int n_digits = 1; + for (;;) + { + if (x < 10) + { + return n_digits; + } + if (x < 100) + { + return n_digits + 1; + } + if (x < 1000) + { + return n_digits + 2; + } + if (x < 10000) + { + return n_digits + 3; + } + x = x / 10000u; + n_digits += 4; + } + } + + /*! + @brief dump an integer + + Dump a given integer to output stream @a o. Works internally with + @a number_buffer. + + @param[in] x integer number (signed or unsigned) to dump + @tparam NumberType either @a number_integer_t or @a number_unsigned_t + */ + template < typename NumberType, detail::enable_if_t < + std::is_same::value || + std::is_same::value || + std::is_same::value, + int > = 0 > + void dump_integer(NumberType x) + { + static constexpr std::array, 100> digits_to_99 + { + { + {{'0', '0'}}, {{'0', '1'}}, {{'0', '2'}}, {{'0', '3'}}, {{'0', '4'}}, {{'0', '5'}}, {{'0', '6'}}, {{'0', '7'}}, {{'0', '8'}}, {{'0', '9'}}, + {{'1', '0'}}, {{'1', '1'}}, {{'1', '2'}}, {{'1', '3'}}, {{'1', '4'}}, {{'1', '5'}}, {{'1', '6'}}, {{'1', '7'}}, {{'1', '8'}}, {{'1', '9'}}, + {{'2', '0'}}, {{'2', '1'}}, {{'2', '2'}}, {{'2', '3'}}, {{'2', '4'}}, {{'2', '5'}}, {{'2', '6'}}, {{'2', '7'}}, {{'2', '8'}}, {{'2', '9'}}, + {{'3', '0'}}, {{'3', '1'}}, {{'3', '2'}}, {{'3', '3'}}, {{'3', '4'}}, {{'3', '5'}}, {{'3', '6'}}, {{'3', '7'}}, {{'3', '8'}}, {{'3', '9'}}, + {{'4', '0'}}, {{'4', '1'}}, {{'4', '2'}}, {{'4', '3'}}, {{'4', '4'}}, {{'4', '5'}}, {{'4', '6'}}, {{'4', '7'}}, {{'4', '8'}}, {{'4', '9'}}, + {{'5', '0'}}, {{'5', '1'}}, {{'5', '2'}}, {{'5', '3'}}, {{'5', '4'}}, {{'5', '5'}}, {{'5', '6'}}, {{'5', '7'}}, {{'5', '8'}}, {{'5', '9'}}, + {{'6', '0'}}, {{'6', '1'}}, {{'6', '2'}}, {{'6', '3'}}, {{'6', '4'}}, {{'6', '5'}}, {{'6', '6'}}, {{'6', '7'}}, {{'6', '8'}}, {{'6', '9'}}, + {{'7', '0'}}, {{'7', '1'}}, {{'7', '2'}}, {{'7', '3'}}, {{'7', '4'}}, {{'7', '5'}}, {{'7', '6'}}, {{'7', '7'}}, {{'7', '8'}}, {{'7', '9'}}, + {{'8', '0'}}, {{'8', '1'}}, {{'8', '2'}}, {{'8', '3'}}, {{'8', '4'}}, {{'8', '5'}}, {{'8', '6'}}, {{'8', '7'}}, {{'8', '8'}}, {{'8', '9'}}, + {{'9', '0'}}, {{'9', '1'}}, {{'9', '2'}}, {{'9', '3'}}, {{'9', '4'}}, {{'9', '5'}}, {{'9', '6'}}, {{'9', '7'}}, {{'9', '8'}}, {{'9', '9'}}, + } + }; + + // special case for "0" + if (x == 0) + { + o->write_character('0'); + return; + } + + // use a pointer to fill the buffer + auto buffer_ptr = number_buffer.begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto,cppcoreguidelines-pro-type-vararg,hicpp-vararg) + + const bool is_negative = std::is_same::value && !(x >= 0); // see issue #755 + number_unsigned_t abs_value; + + unsigned int n_chars{}; + + if (is_negative) + { + *buffer_ptr = '-'; + abs_value = remove_sign(static_cast(x)); + + // account one more byte for the minus sign + n_chars = 1 + count_digits(abs_value); + } + else + { + abs_value = static_cast(x); + n_chars = count_digits(abs_value); + } + + // spare 1 byte for '\0' + JSON_ASSERT(n_chars < number_buffer.size() - 1); + + // jump to the end to generate the string from backward + // so we later avoid reversing the result + buffer_ptr += n_chars; + + // Fast int2ascii implementation inspired by "Fastware" talk by Andrei Alexandrescu + // See: https://www.youtube.com/watch?v=o4-CwDo2zpg + while (abs_value >= 100) + { + const auto digits_index = static_cast((abs_value % 100)); + abs_value /= 100; + *(--buffer_ptr) = digits_to_99[digits_index][1]; + *(--buffer_ptr) = digits_to_99[digits_index][0]; + } + + if (abs_value >= 10) + { + const auto digits_index = static_cast(abs_value); + *(--buffer_ptr) = digits_to_99[digits_index][1]; + *(--buffer_ptr) = digits_to_99[digits_index][0]; + } + else + { + *(--buffer_ptr) = static_cast('0' + abs_value); + } + + o->write_characters(number_buffer.data(), n_chars); + } + + /*! + @brief dump a floating-point number + + Dump a given floating-point number to output stream @a o. Works internally + with @a number_buffer. + + @param[in] x floating-point number to dump + */ + void dump_float(number_float_t x) + { + // NaN / inf + if (!std::isfinite(x)) + { + o->write_characters("null", 4); + return; + } + + // If number_float_t is an IEEE-754 single or double precision number, + // use the Grisu2 algorithm to produce short numbers which are + // guaranteed to round-trip, using strtof and strtod, resp. + // + // NB: The test below works if == . + static constexpr bool is_ieee_single_or_double + = (std::numeric_limits::is_iec559 && std::numeric_limits::digits == 24 && std::numeric_limits::max_exponent == 128) || + (std::numeric_limits::is_iec559 && std::numeric_limits::digits == 53 && std::numeric_limits::max_exponent == 1024); + + dump_float(x, std::integral_constant()); + } + + void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) + { + auto* begin = number_buffer.data(); + auto* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x); + + o->write_characters(begin, static_cast(end - begin)); + } + + void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) + { + // get number of digits for a float -> text -> float round-trip + static constexpr auto d = std::numeric_limits::max_digits10; + + // the actual conversion + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + std::ptrdiff_t len = (std::snprintf)(number_buffer.data(), number_buffer.size(), "%.*g", d, x); + + // negative value indicates an error + JSON_ASSERT(len > 0); + // check if buffer was large enough + JSON_ASSERT(static_cast(len) < number_buffer.size()); + + // erase thousands separator + if (thousands_sep != '\0') + { + auto* const end = std::remove(number_buffer.begin(), + number_buffer.begin() + len, thousands_sep); + std::fill(end, number_buffer.end(), '\0'); + JSON_ASSERT((end - number_buffer.begin()) <= len); + len = (end - number_buffer.begin()); + } + + // convert decimal point to '.' + if (decimal_point != '\0' && decimal_point != '.') + { + auto* const 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(len)); + + // determine if need to append ".0" + const bool value_is_int_like = + std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1, + [](char c) + { + return c == '.' || c == 'e'; + }); + + if (value_is_int_like) + { + o->write_characters(".0", 2); + } + } + + /*! + @brief check whether a string is UTF-8 encoded + + The function checks each byte of a string whether it is UTF-8 encoded. The + result of the check is stored in the @a state parameter. The function must + be called initially with state 0 (accept). State 1 means the string must + be rejected, because the current byte is not allowed. If the string is + completely processed, but the state is non-zero, the string ended + prematurely; that is, the last byte indicated more bytes should have + followed. + + @param[in,out] state the state of the decoding + @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) + @param[in] byte next byte to decode + @return new state + + @note The function has been edited: a std::array is used. + + @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann + @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + */ + static std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std::uint8_t byte) noexcept + { + static const std::array 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, // 00..1F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F + 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, // 80..9F + 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, // A0..BF + 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, // C0..DF + 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF + 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF + 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 + 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, // s1..s2 + 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, // s3..s4 + 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, // s5..s6 + 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 // s7..s8 + } + }; + + JSON_ASSERT(byte < utf8d.size()); + const std::uint8_t type = utf8d[byte]; + + codep = (state != UTF8_ACCEPT) + ? (byte & 0x3fu) | (codep << 6u) + : (0xFFu >> type) & (byte); + + std::size_t index = 256u + static_cast(state) * 16u + static_cast(type); + JSON_ASSERT(index < 400); + state = utf8d[index]; + return state; + } + + /* + * Overload to make the compiler happy while it is instantiating + * dump_integer for number_unsigned_t. + * Must never be called. + */ + number_unsigned_t remove_sign(number_unsigned_t x) + { + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + return x; // LCOV_EXCL_LINE + } + + /* + * Helper function for dump_integer + * + * This function takes a negative signed integer and returns its absolute + * value as unsigned integer. The plus/minus shuffling is necessary as we can + * not directly remove the sign of an arbitrary signed integer as the + * absolute values of INT_MIN and INT_MAX are usually not the same. See + * #1708 for details. + */ + inline number_unsigned_t remove_sign(number_integer_t x) noexcept + { + JSON_ASSERT(x < 0 && x < (std::numeric_limits::max)()); // NOLINT(misc-redundant-expression) + return static_cast(-(x + 1)) + 1; + } + + private: + /// the output of the serializer + output_adapter_t o = nullptr; + + /// a (hopefully) large enough character buffer + std::array number_buffer{{}}; + + /// the locale + const std::lconv* loc = nullptr; + /// the locale's thousand separator character + const char thousands_sep = '\0'; + /// the locale's decimal point character + const char decimal_point = '\0'; + + /// string buffer + std::array string_buffer{{}}; + + /// the indentation character + const char indent_char; + /// the indentation string + string_t indent_string; + + /// error_handler how to react on decoding errors + const error_handler_t error_handler; +}; +} // namespace detail +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/string_escape.hpp b/lib/json/include/nlohmann/detail/string_escape.hpp new file mode 100644 index 0000000..84f7da5 --- /dev/null +++ b/lib/json/include/nlohmann/detail/string_escape.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include +#include + +namespace nlohmann +{ +namespace detail +{ + +/*! +@brief replace all occurrences of a substring by another string + +@param[in,out] s the string to manipulate; changed so that all + occurrences of @a f are replaced with @a t +@param[in] f the substring to replace with @a t +@param[in] t the string to replace @a f + +@pre The search string @a f must not be empty. **This precondition is +enforced with an assertion.** + +@since version 2.0.0 +*/ +inline void replace_substring(std::string& s, const std::string& f, + const std::string& t) +{ + JSON_ASSERT(!f.empty()); + for (auto pos = s.find(f); // find first occurrence of f + pos != std::string::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t, and + pos = s.find(f, pos + t.size())) // find next occurrence of f + {} +} + +/*! + * @brief string escaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to escape + * @return escaped string + * + * Note the order of escaping "~" to "~0" and "/" to "~1" is important. + */ +inline std::string escape(std::string s) +{ + replace_substring(s, "~", "~0"); + replace_substring(s, "/", "~1"); + return s; +} + +/*! + * @brief string unescaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to unescape + * @return unescaped string + * + * Note the order of escaping "~1" to "/" and "~0" to "~" is important. + */ +static void unescape(std::string& s) +{ + replace_substring(s, "~1", "/"); + replace_substring(s, "~0", "~"); +} + +} // namespace detail +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/detail/value_t.hpp b/lib/json/include/nlohmann/detail/value_t.hpp new file mode 100644 index 0000000..a98c435 --- /dev/null +++ b/lib/json/include/nlohmann/detail/value_t.hpp @@ -0,0 +1,81 @@ +#pragma once + +#include // array +#include // size_t +#include // uint8_t +#include // string + +namespace nlohmann +{ +namespace detail +{ +/////////////////////////// +// JSON type enumeration // +/////////////////////////// + +/*! +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type + +@since version 1.0.0 +*/ +enum class value_t : std::uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + binary, ///< binary array (ordered collection of bytes) + discarded ///< discarded by the parser callback function +}; + +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string < binary +- furthermore, each type is not smaller than itself +- discarded values are not comparable +- binary is represented as a b"" string in python and directly comparable to a + string; however, making a binary array directly comparable with a string would + be surprising behavior in a JSON file. + +@since version 1.0.0 +*/ +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + static constexpr std::array order = {{ + 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, + 6 /* binary */ + } + }; + + const auto l_index = static_cast(lhs); + const auto r_index = static_cast(rhs); + return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; +} +} // namespace detail +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/json.hpp b/lib/json/include/nlohmann/json.hpp new file mode 100644 index 0000000..0bbaa95 --- /dev/null +++ b/lib/json/include/nlohmann/json.hpp @@ -0,0 +1,8942 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ +| | |__ | | | | | | version 3.9.1 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +SPDX-License-Identifier: MIT +Copyright (c) 2013-2019 Niels Lohmann . + +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. +*/ + +#ifndef INCLUDE_NLOHMANN_JSON_HPP_ +#define INCLUDE_NLOHMANN_JSON_HPP_ + +#define NLOHMANN_JSON_VERSION_MAJOR 3 +#define NLOHMANN_JSON_VERSION_MINOR 9 +#define NLOHMANN_JSON_VERSION_PATCH 1 + +#include // all_of, find, for_each +#include // nullptr_t, ptrdiff_t, size_t +#include // hash, less +#include // initializer_list +#include // istream, ostream +#include // random_access_iterator_tag +#include // unique_ptr +#include // accumulate +#include // string, stoi, to_string +#include // declval, forward, move, pair, swap +#include // vector + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(JSON_HAS_CPP_17) + #include +#endif + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ + +/*! +@brief a class to store JSON values + +@tparam ObjectType type for JSON objects (`std::map` by default; will be used +in @ref object_t) +@tparam ArrayType type for JSON arrays (`std::vector` by default; will be used +in @ref array_t) +@tparam StringType type for JSON strings and object keys (`std::string` by +default; will be used in @ref string_t) +@tparam BooleanType type for JSON booleans (`bool` by default; will be used +in @ref boolean_t) +@tparam NumberIntegerType type for JSON integer numbers (`int64_t` by +default; will be used in @ref number_integer_t) +@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c +`uint64_t` by default; will be used in @ref number_unsigned_t) +@tparam NumberFloatType type for JSON floating-point numbers (`double` by +default; will be used in @ref number_float_t) +@tparam BinaryType type for packed binary data for compatibility with binary +serialization formats (`std::vector` by default; will be used in +@ref binary_t) +@tparam AllocatorType type of the allocator to use (`std::allocator` by +default) +@tparam JSONSerializer the serializer to resolve internal calls to `to_json()` +and `from_json()` (@ref adl_serializer by default) + +@requirement The class satisfies the following concept requirements: +- Basic + - [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible): + JSON values can be default constructed. The result will be a JSON null + value. + - [MoveConstructible](https://en.cppreference.com/w/cpp/named_req/MoveConstructible): + A JSON value can be constructed from an rvalue argument. + - [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible): + A JSON value can be copy-constructed from an lvalue expression. + - [MoveAssignable](https://en.cppreference.com/w/cpp/named_req/MoveAssignable): + A JSON value van be assigned from an rvalue argument. + - [CopyAssignable](https://en.cppreference.com/w/cpp/named_req/CopyAssignable): + A JSON value can be copy-assigned from an lvalue expression. + - [Destructible](https://en.cppreference.com/w/cpp/named_req/Destructible): + JSON values can be destructed. +- Layout + - [StandardLayoutType](https://en.cppreference.com/w/cpp/named_req/StandardLayoutType): + JSON values have + [standard layout](https://en.cppreference.com/w/cpp/language/data_members#Standard_layout): + All non-static data members are private and standard layout types, the + class has no virtual functions or (virtual) base classes. +- Library-wide + - [EqualityComparable](https://en.cppreference.com/w/cpp/named_req/EqualityComparable): + JSON values can be compared with `==`, see @ref + operator==(const_reference,const_reference). + - [LessThanComparable](https://en.cppreference.com/w/cpp/named_req/LessThanComparable): + JSON values can be compared with `<`, see @ref + operator<(const_reference,const_reference). + - [Swappable](https://en.cppreference.com/w/cpp/named_req/Swappable): + Any JSON lvalue or rvalue of can be swapped with any lvalue or rvalue of + other compatible types, using unqualified function call @ref swap(). + - [NullablePointer](https://en.cppreference.com/w/cpp/named_req/NullablePointer): + JSON values can be compared against `std::nullptr_t` objects which are used + to model the `null` value. +- Container + - [Container](https://en.cppreference.com/w/cpp/named_req/Container): + JSON values can be used like STL containers and provide iterator access. + - [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer); + JSON values can be used like STL containers and provide reverse iterator + access. + +@invariant The member variables @a m_value and @a m_type have the following +relationship: +- If `m_type == value_t::object`, then `m_value.object != nullptr`. +- If `m_type == value_t::array`, then `m_value.array != nullptr`. +- If `m_type == value_t::string`, then `m_value.string != nullptr`. +The invariants are checked by member function assert_invariant(). + +@internal +@note ObjectType trick from https://stackoverflow.com/a/9860911 +@endinternal + +@see [RFC 8259: The JavaScript Object Notation (JSON) Data Interchange +Format](https://tools.ietf.org/html/rfc8259) + +@since version 1.0.0 + +@nosubgrouping +*/ +NLOHMANN_BASIC_JSON_TPL_DECLARATION +class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) +{ + private: + template friend struct detail::external_constructor; + friend ::nlohmann::json_pointer; + + template + friend class ::nlohmann::detail::parser; + friend ::nlohmann::detail::serializer; + template + friend class ::nlohmann::detail::iter_impl; + template + friend class ::nlohmann::detail::binary_writer; + template + friend class ::nlohmann::detail::binary_reader; + template + friend class ::nlohmann::detail::json_sax_dom_parser; + template + friend class ::nlohmann::detail::json_sax_dom_callback_parser; + friend class ::nlohmann::detail::exception; + + /// workaround type for MSVC + using basic_json_t = NLOHMANN_BASIC_JSON_TPL; + + JSON_PRIVATE_UNLESS_TESTED: + // convenience aliases for types residing in namespace detail; + using lexer = ::nlohmann::detail::lexer_base; + + template + static ::nlohmann::detail::parser parser( + InputAdapterType adapter, + detail::parser_callback_tcb = nullptr, + const bool allow_exceptions = true, + const bool ignore_comments = false + ) + { + return ::nlohmann::detail::parser(std::move(adapter), + std::move(cb), allow_exceptions, ignore_comments); + } + + private: + using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t; + template + using internal_iterator = ::nlohmann::detail::internal_iterator; + template + using iter_impl = ::nlohmann::detail::iter_impl; + template + using iteration_proxy = ::nlohmann::detail::iteration_proxy; + template using json_reverse_iterator = ::nlohmann::detail::json_reverse_iterator; + + template + using output_adapter_t = ::nlohmann::detail::output_adapter_t; + + template + using binary_reader = ::nlohmann::detail::binary_reader; + template using binary_writer = ::nlohmann::detail::binary_writer; + + JSON_PRIVATE_UNLESS_TESTED: + using serializer = ::nlohmann::detail::serializer; + + public: + using value_t = detail::value_t; + /// JSON Pointer, see @ref nlohmann::json_pointer + using json_pointer = ::nlohmann::json_pointer; + template + using json_serializer = JSONSerializer; + /// how to treat decoding errors + using error_handler_t = detail::error_handler_t; + /// how to treat CBOR tags + using cbor_tag_handler_t = detail::cbor_tag_handler_t; + /// helper type for initializer lists of basic_json values + using initializer_list_t = std::initializer_list>; + + using input_format_t = detail::input_format_t; + /// SAX interface type, see @ref nlohmann::json_sax + using json_sax_t = json_sax; + + //////////////// + // exceptions // + //////////////// + + /// @name exceptions + /// Classes to implement user-defined exceptions. + /// @{ + + /// @copydoc detail::exception + using exception = detail::exception; + /// @copydoc detail::parse_error + using parse_error = detail::parse_error; + /// @copydoc detail::invalid_iterator + using invalid_iterator = detail::invalid_iterator; + /// @copydoc detail::type_error + using type_error = detail::type_error; + /// @copydoc detail::out_of_range + using out_of_range = detail::out_of_range; + /// @copydoc detail::other_error + using other_error = detail::other_error; + + /// @} + + + ///////////////////// + // container types // + ///////////////////// + + /// @name container types + /// The canonic container types to use @ref basic_json like any other STL + /// container. + /// @{ + + /// the type of elements in a basic_json container + using value_type = basic_json; + + /// the type of an element reference + using reference = value_type&; + /// the type of an element const reference + using const_reference = const value_type&; + + /// a type to represent differences between iterators + using difference_type = std::ptrdiff_t; + /// a type to represent container sizes + using size_type = std::size_t; + + /// the allocator type + using allocator_type = AllocatorType; + + /// the type of an element pointer + using pointer = typename std::allocator_traits::pointer; + /// the type of an element const pointer + using const_pointer = typename std::allocator_traits::const_pointer; + + /// an iterator for a basic_json container + using iterator = iter_impl; + /// a const iterator for a basic_json container + using const_iterator = iter_impl; + /// a reverse iterator for a basic_json container + using reverse_iterator = json_reverse_iterator; + /// a const reverse iterator for a basic_json container + using const_reverse_iterator = json_reverse_iterator; + + /// @} + + + /*! + @brief returns the allocator associated with the container + */ + static allocator_type get_allocator() + { + return allocator_type(); + } + + /*! + @brief returns version information on the library + + This function returns a JSON object with information about the library, + including the version number and information on the platform and compiler. + + @return JSON object holding version information + key | description + ----------- | --------------- + `compiler` | Information on the used compiler. It is an object with the following keys: `c++` (the used C++ standard), `family` (the compiler family; possible values are `clang`, `icc`, `gcc`, `ilecpp`, `msvc`, `pgcpp`, `sunpro`, and `unknown`), and `version` (the compiler version). + `copyright` | The copyright line for the library as string. + `name` | The name of the library as string. + `platform` | The used platform as string. Possible values are `win32`, `linux`, `apple`, `unix`, and `unknown`. + `url` | The URL of the project as string. + `version` | The version of the library. It is an object with the following keys: `major`, `minor`, and `patch` as defined by [Semantic Versioning](http://semver.org), and `string` (the version string). + + @liveexample{The following code shows an example output of the `meta()` + function.,meta} + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @complexity Constant. + + @since 2.1.0 + */ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json meta() + { + basic_json result; + + result["copyright"] = "(C) 2013-2021 Niels Lohmann"; + result["name"] = "JSON for Modern C++"; + result["url"] = "https://github.com/nlohmann/json"; + 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; + } + + + /////////////////////////// + // JSON value data types // + /////////////////////////// + + /// @name JSON value data types + /// The data types to store a JSON value. These types are derived from + /// the template arguments passed to class @ref basic_json. + /// @{ + +#if defined(JSON_HAS_CPP_14) + // Use transparent comparator if possible, combined with perfect forwarding + // on find() and count() calls prevents unnecessary string construction. + using object_comparator_t = std::less<>; +#else + using object_comparator_t = std::less; +#endif + + /*! + @brief a type for an object + + [RFC 8259](https://tools.ietf.org/html/rfc8259) describes JSON objects as follows: + > An object is an unordered collection of zero or more name/value pairs, + > where a name is a string and a value is a string, number, boolean, null, + > object, or array. + + To store objects in C++, a type is defined by the template parameters + described below. + + @tparam ObjectType the container to store objects (e.g., `std::map` or + `std::unordered_map`) + @tparam StringType the type of the keys or names (e.g., `std::string`). + The comparison function `std::less` is used to order elements + inside the container. + @tparam AllocatorType the allocator to use for objects (e.g., + `std::allocator`) + + #### Default type + + With the default values for @a ObjectType (`std::map`), @a StringType + (`std::string`), and @a AllocatorType (`std::allocator`), the default + value for @a object_t is: + + @code {.cpp} + std::map< + std::string, // key_type + basic_json, // value_type + std::less, // key_compare + std::allocator> // allocator_type + > + @endcode + + #### Behavior + + The choice of @a object_t influences the behavior of the JSON class. With + the default type, objects have the following behavior: + + - When all names are unique, objects will be interoperable in the sense + that all software implementations receiving that object will agree on + the name-value mappings. + - When the names within an object are not unique, it is unspecified which + one of the values for a given key will be chosen. For instance, + `{"key": 2, "key": 1}` could be equal to either `{"key": 1}` or + `{"key": 2}`. + - Internally, name/value pairs are stored in lexicographical order of the + names. Objects will also be serialized (see @ref dump) in this order. + For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored + and serialized as `{"a": 2, "b": 1}`. + - When comparing objects, the order of the name/value pairs is irrelevant. + This makes objects interoperable in the sense that they will not be + affected by these differences. For instance, `{"b": 1, "a": 2}` and + `{"a": 2, "b": 1}` will be treated as equal. + + #### Limits + + [RFC 8259](https://tools.ietf.org/html/rfc8259) specifies: + > An implementation may set limits on the maximum depth of nesting. + + In this class, the object's limit of nesting is not explicitly constrained. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON object. + + #### Storage + + Objects are stored as pointers in a @ref basic_json type. That is, for any + access to object values, a pointer of type `object_t*` must be + dereferenced. + + @sa see @ref array_t -- type for an array value + + @since version 1.0.0 + + @note The order name/value pairs are added to the object is *not* + preserved by the library. Therefore, iterating an object may return + name/value pairs in a different order than they were originally stored. In + fact, keys will be traversed in alphabetical order as `std::map` with + `std::less` is used by default. Please note this behavior conforms to [RFC + 8259](https://tools.ietf.org/html/rfc8259), because any order implements the + specified "unordered" nature of JSON objects. + */ + using object_t = ObjectType>>; + + /*! + @brief a type for an array + + [RFC 8259](https://tools.ietf.org/html/rfc8259) describes JSON arrays as follows: + > An array is an ordered sequence of zero or more values. + + To store objects in C++, a type is defined by the template parameters + explained below. + + @tparam ArrayType container type to store arrays (e.g., `std::vector` or + `std::list`) + @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`) + + #### Default type + + With the default values for @a ArrayType (`std::vector`) and @a + AllocatorType (`std::allocator`), the default value for @a array_t is: + + @code {.cpp} + std::vector< + basic_json, // value_type + std::allocator // allocator_type + > + @endcode + + #### Limits + + [RFC 8259](https://tools.ietf.org/html/rfc8259) specifies: + > An implementation may set limits on the maximum depth of nesting. + + In this class, the array's limit of nesting is not explicitly constrained. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON array. + + #### Storage + + Arrays are stored as pointers in a @ref basic_json type. That is, for any + access to array values, a pointer of type `array_t*` must be dereferenced. + + @sa see @ref object_t -- type for an object value + + @since version 1.0.0 + */ + using array_t = ArrayType>; + + /*! + @brief a type for a string + + [RFC 8259](https://tools.ietf.org/html/rfc8259) describes JSON strings as follows: + > A string is a sequence of zero or more Unicode characters. + + To store objects in C++, a type is defined by the template parameter + described below. Unicode values are split by the JSON class into + byte-sized characters during deserialization. + + @tparam StringType the container to store strings (e.g., `std::string`). + Note this container is used for keys/names in objects, see @ref object_t. + + #### Default type + + With the default values for @a StringType (`std::string`), the default + value for @a string_t is: + + @code {.cpp} + std::string + @endcode + + #### Encoding + + Strings are stored in UTF-8 encoding. Therefore, functions like + `std::string::size()` or `std::string::length()` return the number of + bytes in the string rather than the number of characters or glyphs. + + #### String comparison + + [RFC 8259](https://tools.ietf.org/html/rfc8259) states: + > Software implementations are typically required to test names of object + > members for equality. Implementations that transform the textual + > representation into sequences of Unicode code units and then perform the + > comparison numerically, code unit by code unit, are interoperable in the + > sense that implementations will agree in all cases on equality or + > inequality of two strings. For example, implementations that compare + > strings with escaped characters unconverted may incorrectly find that + > `"a\\b"` and `"a\u005Cb"` are not equal. + + This implementation is interoperable as it does compare strings code unit + by code unit. + + #### Storage + + String values are stored as pointers in a @ref basic_json type. That is, + for any access to string values, a pointer of type `string_t*` must be + dereferenced. + + @since version 1.0.0 + */ + using string_t = StringType; + + /*! + @brief a type for a boolean + + [RFC 8259](https://tools.ietf.org/html/rfc8259) implicitly describes a boolean as a + type which differentiates the two literals `true` and `false`. + + To store objects in C++, a type is defined by the template parameter @a + BooleanType which chooses the type to use. + + #### Default type + + With the default values for @a BooleanType (`bool`), the default value for + @a boolean_t is: + + @code {.cpp} + bool + @endcode + + #### Storage + + Boolean values are stored directly inside a @ref basic_json type. + + @since version 1.0.0 + */ + using boolean_t = BooleanType; + + /*! + @brief a type for a number (integer) + + [RFC 8259](https://tools.ietf.org/html/rfc8259) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store integer numbers in C++, a type is defined by the template + parameter @a NumberIntegerType which chooses the type to use. + + #### Default type + + With the default values for @a NumberIntegerType (`int64_t`), the default + value for @a number_integer_t is: + + @code {.cpp} + int64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 8259](https://tools.ietf.org/html/rfc8259) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `9223372036854775807` (INT64_MAX) and the minimal integer number + that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers + that are out of range will yield over/underflow when used in a + constructor. During deserialization, too large or small integer numbers + will be automatically be stored as @ref number_unsigned_t or @ref + number_float_t. + + [RFC 8259](https://tools.ietf.org/html/rfc8259) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange of the exactly supported range [INT64_MIN, + INT64_MAX], this class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa see @ref number_float_t -- type for number values (floating-point) + + @sa see @ref number_unsigned_t -- type for number values (unsigned integer) + + @since version 1.0.0 + */ + using number_integer_t = NumberIntegerType; + + /*! + @brief a type for a number (unsigned) + + [RFC 8259](https://tools.ietf.org/html/rfc8259) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store unsigned integer numbers in C++, a type is defined by the + template parameter @a NumberUnsignedType which chooses the type to use. + + #### Default type + + With the default values for @a NumberUnsignedType (`uint64_t`), the + default value for @a number_unsigned_t is: + + @code {.cpp} + uint64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 8259](https://tools.ietf.org/html/rfc8259) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `18446744073709551615` (UINT64_MAX) and the minimal integer + number that can be stored is `0`. Integer numbers that are out of range + will yield over/underflow when used in a constructor. During + deserialization, too large or small integer numbers will be automatically + be stored as @ref number_integer_t or @ref number_float_t. + + [RFC 8259](https://tools.ietf.org/html/rfc8259) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange (when considered in conjunction with the + number_integer_t type) of the exactly supported range [0, UINT64_MAX], + this class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa see @ref number_float_t -- type for number values (floating-point) + @sa see @ref number_integer_t -- type for number values (integer) + + @since version 2.0.0 + */ + using number_unsigned_t = NumberUnsignedType; + + /*! + @brief a type for a number (floating-point) + + [RFC 8259](https://tools.ietf.org/html/rfc8259) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store floating-point numbers in C++, a type is defined by the template + parameter @a NumberFloatType which chooses the type to use. + + #### Default type + + With the default values for @a NumberFloatType (`double`), the default + value for @a number_float_t is: + + @code {.cpp} + double + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in floating-point literals will be ignored. Internally, + the value will be stored as decimal number. For instance, the C++ + floating-point literal `01.2` will be serialized to `1.2`. During + deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 8259](https://tools.ietf.org/html/rfc8259) states: + > This specification allows implementations to set limits on the range and + > precision of numbers accepted. Since software that implements IEEE + > 754-2008 binary64 (double precision) numbers is generally available and + > widely used, good interoperability can be achieved by implementations + > that expect no more precision or range than these provide, in the sense + > that implementations will approximate JSON numbers within the expected + > precision. + + This implementation does exactly follow this approach, as it uses double + precision floating-point numbers. Note values smaller than + `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` + will be stored as NaN internally and be serialized to `null`. + + #### Storage + + Floating-point number values are stored directly inside a @ref basic_json + type. + + @sa see @ref number_integer_t -- type for number values (integer) + + @sa see @ref number_unsigned_t -- type for number values (unsigned integer) + + @since version 1.0.0 + */ + using number_float_t = NumberFloatType; + + /*! + @brief a type for a packed binary type + + This type is a type designed to carry binary data that appears in various + serialized formats, such as CBOR's Major Type 2, MessagePack's bin, and + BSON's generic binary subtype. This type is NOT a part of standard JSON and + exists solely for compatibility with these binary types. As such, it is + simply defined as an ordered sequence of zero or more byte values. + + Additionally, as an implementation detail, the subtype of the binary data is + carried around as a `std::uint8_t`, which is compatible with both of the + binary data formats that use binary subtyping, (though the specific + numbering is incompatible with each other, and it is up to the user to + translate between them). + + [CBOR's RFC 7049](https://tools.ietf.org/html/rfc7049) describes this type + as: + > Major type 2: a byte string. The string's length in bytes is represented + > following the rules for positive integers (major type 0). + + [MessagePack's documentation on the bin type + family](https://github.com/msgpack/msgpack/blob/master/spec.md#bin-format-family) + describes this type as: + > Bin format family stores an byte array in 2, 3, or 5 bytes of extra bytes + > in addition to the size of the byte array. + + [BSON's specifications](http://bsonspec.org/spec.html) describe several + binary types; however, this type is intended to represent the generic binary + type which has the description: + > Generic binary subtype - This is the most commonly used binary subtype and + > should be the 'default' for drivers and tools. + + None of these impose any limitations on the internal representation other + than the basic unit of storage be some type of array whose parts are + decomposable into bytes. + + The default representation of this binary format is a + `std::vector`, which is a very common way to represent a byte + array in modern C++. + + #### Default type + + The default values for @a BinaryType is `std::vector` + + #### Storage + + Binary Arrays are stored as pointers in a @ref basic_json type. That is, + for any access to array values, a pointer of the type `binary_t*` must be + dereferenced. + + #### Notes on subtypes + + - CBOR + - Binary values are represented as byte strings. No subtypes are + supported and will be ignored when CBOR is written. + - MessagePack + - If a subtype is given and the binary array contains exactly 1, 2, 4, 8, + or 16 elements, the fixext family (fixext1, fixext2, fixext4, fixext8) + is used. For other sizes, the ext family (ext8, ext16, ext32) is used. + The subtype is then added as singed 8-bit integer. + - If no subtype is given, the bin family (bin8, bin16, bin32) is used. + - BSON + - If a subtype is given, it is used and added as unsigned 8-bit integer. + - If no subtype is given, the generic binary subtype 0x00 is used. + + @sa see @ref binary -- create a binary array + + @since version 3.8.0 + */ + using binary_t = nlohmann::byte_container_with_subtype; + /// @} + + private: + + /// helper for exception-safe object creation + template + JSON_HEDLEY_RETURNS_NON_NULL + static T* create(Args&& ... args) + { + AllocatorType alloc; + using AllocatorTraits = std::allocator_traits>; + + auto deleter = [&](T * obj) + { + AllocatorTraits::deallocate(alloc, obj, 1); + }; + std::unique_ptr obj(AllocatorTraits::allocate(alloc, 1), deleter); + AllocatorTraits::construct(alloc, obj.get(), std::forward(args)...); + JSON_ASSERT(obj != nullptr); + return obj.release(); + } + + //////////////////////// + // JSON value storage // + //////////////////////// + + JSON_PRIVATE_UNLESS_TESTED: + /*! + @brief a JSON value + + The actual storage for a JSON value of the @ref basic_json class. This + union combines the different storage types for the JSON value types + defined in @ref value_t. + + JSON type | value_t type | used type + --------- | --------------- | ------------------------ + object | object | pointer to @ref object_t + array | array | pointer to @ref array_t + string | string | pointer to @ref string_t + boolean | boolean | @ref boolean_t + number | number_integer | @ref number_integer_t + number | number_unsigned | @ref number_unsigned_t + number | number_float | @ref number_float_t + binary | binary | pointer to @ref binary_t + null | null | *no value is stored* + + @note Variable-length types (objects, arrays, and strings) are stored as + pointers. The size of the union should not exceed 64 bits if the default + value types are used. + + @since version 1.0.0 + */ + union json_value + { + /// object (stored with pointer to save storage) + object_t* object; + /// array (stored with pointer to save storage) + array_t* array; + /// string (stored with pointer to save storage) + string_t* string; + /// binary (stored with pointer to save storage) + binary_t* binary; + /// boolean + boolean_t boolean; + /// number (integer) + number_integer_t number_integer; + /// number (unsigned integer) + number_unsigned_t number_unsigned; + /// number (floating-point) + number_float_t number_float; + + /// default constructor (for null values) + json_value() = default; + /// constructor for booleans + json_value(boolean_t v) noexcept : boolean(v) {} + /// constructor for numbers (integer) + json_value(number_integer_t v) noexcept : number_integer(v) {} + /// constructor for numbers (unsigned) + json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} + /// constructor for numbers (floating-point) + json_value(number_float_t v) noexcept : number_float(v) {} + /// constructor for empty values of a given type + json_value(value_t t) + { + switch (t) + { + case value_t::object: + { + object = create(); + break; + } + + case value_t::array: + { + array = create(); + break; + } + + case value_t::string: + { + string = create(""); + break; + } + + case value_t::binary: + { + binary = create(); + 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; // silence warning, see #821 + break; + } + + default: + { + object = nullptr; // silence warning, see #821 + if (JSON_HEDLEY_UNLIKELY(t == value_t::null)) + { + JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.9.1", basic_json())); // LCOV_EXCL_LINE + } + break; + } + } + } + + /// constructor for strings + json_value(const string_t& value) + { + string = create(value); + } + + /// constructor for rvalue strings + json_value(string_t&& value) + { + string = create(std::move(value)); + } + + /// constructor for objects + json_value(const object_t& value) + { + object = create(value); + } + + /// constructor for rvalue objects + json_value(object_t&& value) + { + object = create(std::move(value)); + } + + /// constructor for arrays + json_value(const array_t& value) + { + array = create(value); + } + + /// constructor for rvalue arrays + json_value(array_t&& value) + { + array = create(std::move(value)); + } + + /// constructor for binary arrays + json_value(const typename binary_t::container_type& value) + { + binary = create(value); + } + + /// constructor for rvalue binary arrays + json_value(typename binary_t::container_type&& value) + { + binary = create(std::move(value)); + } + + /// constructor for binary arrays (internal type) + json_value(const binary_t& value) + { + binary = create(value); + } + + /// constructor for rvalue binary arrays (internal type) + json_value(binary_t&& value) + { + binary = create(std::move(value)); + } + + void destroy(value_t t) noexcept + { + // flatten the current json_value to a heap-allocated stack + std::vector stack; + + // move the top-level items to stack + if (t == value_t::array) + { + stack.reserve(array->size()); + std::move(array->begin(), array->end(), std::back_inserter(stack)); + } + else if (t == value_t::object) + { + stack.reserve(object->size()); + for (auto&& it : *object) + { + stack.push_back(std::move(it.second)); + } + } + + while (!stack.empty()) + { + // move the last item to local variable to be processed + basic_json current_item(std::move(stack.back())); + stack.pop_back(); + + // if current_item is array/object, move + // its children to the stack to be processed later + if (current_item.is_array()) + { + std::move(current_item.m_value.array->begin(), current_item.m_value.array->end(), + std::back_inserter(stack)); + + current_item.m_value.array->clear(); + } + else if (current_item.is_object()) + { + for (auto&& it : *current_item.m_value.object) + { + stack.push_back(std::move(it.second)); + } + + current_item.m_value.object->clear(); + } + + // it's now safe that current_item get destructed + // since it doesn't have any children + } + + switch (t) + { + case value_t::object: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, object); + std::allocator_traits::deallocate(alloc, object, 1); + break; + } + + case value_t::array: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, array); + std::allocator_traits::deallocate(alloc, array, 1); + break; + } + + case value_t::string: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, string); + std::allocator_traits::deallocate(alloc, string, 1); + break; + } + + case value_t::binary: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, binary); + std::allocator_traits::deallocate(alloc, binary, 1); + break; + } + + default: + { + break; + } + } + } + }; + + private: + /*! + @brief checks the class invariants + + This function asserts the class invariants. It needs to be called at the + end of every constructor to make sure that created objects respect the + invariant. Furthermore, it has to be called each time the type of a JSON + value is changed, because the invariant expresses a relationship between + @a m_type and @a m_value. + + Furthermore, the parent relation is checked for arrays and objects: If + @a check_parents true and the value is an array or object, then the + container's elements must have the current value as parent. + + @param[in] check_parents whether the parent relation should be checked. + The value is true by default and should only be set to false + during destruction of objects when the invariant does not + need to hold. + */ + void assert_invariant(bool check_parents = true) const noexcept + { + JSON_ASSERT(m_type != value_t::object || m_value.object != nullptr); + JSON_ASSERT(m_type != value_t::array || m_value.array != nullptr); + JSON_ASSERT(m_type != value_t::string || m_value.string != nullptr); + JSON_ASSERT(m_type != value_t::binary || m_value.binary != nullptr); + +#if JSON_DIAGNOSTICS + JSON_TRY + { + // cppcheck-suppress assertWithSideEffect + JSON_ASSERT(!check_parents || !is_structured() || std::all_of(begin(), end(), [this](const basic_json & j) + { + return j.m_parent == this; + })); + } + JSON_CATCH(...) {} // LCOV_EXCL_LINE +#endif + static_cast(check_parents); + } + + void set_parents() + { +#if JSON_DIAGNOSTICS + switch (m_type) + { + case value_t::array: + { + for (auto& element : *m_value.array) + { + element.m_parent = this; + } + break; + } + + case value_t::object: + { + for (auto& element : *m_value.object) + { + element.second.m_parent = this; + } + break; + } + + default: + break; + } +#endif + } + + iterator set_parents(iterator it, typename iterator::difference_type count) + { +#if JSON_DIAGNOSTICS + for (typename iterator::difference_type i = 0; i < count; ++i) + { + (it + i)->m_parent = this; + } +#else + static_cast(count); +#endif + return it; + } + + reference set_parent(reference j) + { +#if JSON_DIAGNOSTICS + j.m_parent = this; +#else + static_cast(j); +#endif + return j; + } + + public: + ////////////////////////// + // JSON parser callback // + ////////////////////////// + + /*! + @brief parser event types + + The parser callback distinguishes the following events: + - `object_start`: the parser read `{` and started to process a JSON object + - `key`: the parser read a key of a value in an object + - `object_end`: the parser read `}` and finished processing a JSON object + - `array_start`: the parser read `[` and started to process a JSON array + - `array_end`: the parser read `]` and finished processing a JSON array + - `value`: the parser finished reading a JSON value + + @image html callback_events.png "Example when certain parse events are triggered" + + @sa see @ref parser_callback_t for more information and examples + */ + using parse_event_t = detail::parse_event_t; + + /*! + @brief per-element parser callback type + + With a parser callback function, the result of parsing a JSON text can be + influenced. When passed to @ref parse, it is called on certain events + (passed as @ref parse_event_t via parameter @a event) with a set recursion + depth @a depth and context JSON value @a parsed. The return value of the + callback function is a boolean indicating whether the element that emitted + the callback shall be kept or not. + + We distinguish six scenarios (determined by the event type) in which the + callback function can be called. The following table describes the values + of the parameters @a depth, @a event, and @a parsed. + + parameter @a event | description | parameter @a depth | parameter @a parsed + ------------------ | ----------- | ------------------ | ------------------- + parse_event_t::object_start | the parser read `{` and started to process a JSON object | depth of the parent of the JSON object | a JSON value with type discarded + parse_event_t::key | the parser read a key of a value in an object | depth of the currently parsed JSON object | a JSON string containing the key + parse_event_t::object_end | the parser read `}` and finished processing a JSON object | depth of the parent of the JSON object | the parsed JSON object + parse_event_t::array_start | the parser read `[` and started to process a JSON array | depth of the parent of the JSON array | a JSON value with type discarded + parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array + parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value + + @image html callback_events.png "Example when certain parse events are triggered" + + Discarding a value (i.e., returning `false`) has different effects + depending on the context in which function was called: + + - Discarded values in structured types are skipped. That is, the parser + will behave as if the discarded value was never read. + - In case a value outside a structured type is skipped, it is replaced + with `null`. This case happens if the top-level element is skipped. + + @param[in] depth the depth of the recursion during parsing + + @param[in] event an event of type parse_event_t indicating the context in + the callback function has been called + + @param[in,out] parsed the current intermediate parse result; note that + writing to this value has no effect for parse_event_t::key events + + @return Whether the JSON value which called the function during parsing + should be kept (`true`) or not (`false`). In the latter case, it is either + skipped completely or replaced by an empty discarded object. + + @sa see @ref parse for examples + + @since version 1.0.0 + */ + using parser_callback_t = detail::parser_callback_t; + + ////////////////// + // constructors // + ////////////////// + + /// @name constructors and destructors + /// Constructors of class @ref basic_json, copy/move constructor, copy + /// assignment, static functions creating objects, and the destructor. + /// @{ + + /*! + @brief create an empty value with a given type + + Create an empty JSON value with a given type. The value will be default + initialized with an empty value which depends on the type: + + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + object | `{}` + array | `[]` + binary | empty array + + @param[in] v the type of the value to create + + @complexity Constant. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows the constructor for different @ref + value_t values,basic_json__value_t} + + @sa see @ref clear() -- restores the postcondition of this constructor + + @since version 1.0.0 + */ + basic_json(const value_t v) + : m_type(v), m_value(v) + { + assert_invariant(); + } + + /*! + @brief create a null object + + Create a `null` JSON value. It either takes a null pointer as parameter + (explicitly creating `null`) or no parameter (implicitly creating `null`). + The passed null pointer itself is not read -- it is only used to choose + the right constructor. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. + + @liveexample{The following code shows the constructor with and without a + null pointer parameter.,basic_json__nullptr_t} + + @since version 1.0.0 + */ + basic_json(std::nullptr_t = nullptr) noexcept + : basic_json(value_t::null) + { + assert_invariant(); + } + + /*! + @brief create a JSON value + + This is a "catch all" constructor for all compatible JSON types; that is, + types for which a `to_json()` method exists. The constructor forwards the + parameter @a val to that method (to `json_serializer::to_json` method + with `U = uncvref_t`, to be exact). + + Template type @a CompatibleType includes, but is not limited to, the + following types: + - **arrays**: @ref array_t and all kinds of compatible containers such as + `std::vector`, `std::deque`, `std::list`, `std::forward_list`, + `std::array`, `std::valarray`, `std::set`, `std::unordered_set`, + `std::multiset`, and `std::unordered_multiset` with a `value_type` from + which a @ref basic_json value can be constructed. + - **objects**: @ref object_t and all kinds of compatible associative + containers such as `std::map`, `std::unordered_map`, `std::multimap`, + and `std::unordered_multimap` with a `key_type` compatible to + @ref string_t and a `value_type` from which a @ref basic_json value can + be constructed. + - **strings**: @ref string_t, string literals, and all compatible string + containers can be used. + - **numbers**: @ref number_integer_t, @ref number_unsigned_t, + @ref number_float_t, and all convertible number types such as `int`, + `size_t`, `int64_t`, `float` or `double` can be used. + - **boolean**: @ref boolean_t / `bool` can be used. + - **binary**: @ref binary_t / `std::vector` may be used, + unfortunately because string literals cannot be distinguished from binary + character arrays by the C++ type system, all types compatible with `const + char*` will be directed to the string constructor instead. This is both + for backwards compatibility, and due to the fact that a binary type is not + a standard JSON type. + + See the examples below. + + @tparam CompatibleType a type such that: + - @a CompatibleType is not derived from `std::istream`, + - @a CompatibleType is not @ref basic_json (to avoid hijacking copy/move + constructors), + - @a CompatibleType is not a different @ref basic_json type (i.e. with different template arguments) + - @a CompatibleType is not a @ref basic_json nested type (e.g., + @ref json_pointer, @ref iterator, etc ...) + - `json_serializer` has a `to_json(basic_json_t&, CompatibleType&&)` method + + @tparam U = `uncvref_t` + + @param[in] val the value to be forwarded to the respective constructor + + @complexity Usually linear in the size of the passed @a val, also + depending on the implementation of the called `to_json()` + method. + + @exceptionsafety Depends on the called constructor. For types directly + supported by the library (i.e., all types for which no `to_json()` function + was provided), strong guarantee holds: if an exception is thrown, there are + no changes to any JSON value. + + @liveexample{The following code shows the constructor with several + compatible types.,basic_json__CompatibleType} + + @since version 2.1.0 + */ + template < typename CompatibleType, + typename U = detail::uncvref_t, + detail::enable_if_t < + !detail::is_basic_json::value && detail::is_compatible_type::value, int > = 0 > + basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape) + JSONSerializer::to_json(std::declval(), + std::forward(val)))) + { + JSONSerializer::to_json(*this, std::forward(val)); + set_parents(); + assert_invariant(); + } + + /*! + @brief create a JSON value from an existing one + + This is a constructor for existing @ref basic_json types. + It does not hijack copy/move constructors, since the parameter has different + template arguments than the current ones. + + The constructor tries to convert the internal @ref m_value of the parameter. + + @tparam BasicJsonType a type such that: + - @a BasicJsonType is a @ref basic_json type. + - @a BasicJsonType has different template arguments than @ref basic_json_t. + + @param[in] val the @ref basic_json value to be converted. + + @complexity Usually linear in the size of the passed @a val, also + depending on the implementation of the called `to_json()` + method. + + @exceptionsafety Depends on the called constructor. For types directly + supported by the library (i.e., all types for which no `to_json()` function + was provided), strong guarantee holds: if an exception is thrown, there are + no changes to any JSON value. + + @since version 3.2.0 + */ + template < typename BasicJsonType, + detail::enable_if_t < + detail::is_basic_json::value&& !std::is_same::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; + using other_binary_t = typename BasicJsonType::binary_t; + + switch (val.type()) + { + case value_t::boolean: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::number_float: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::number_integer: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::number_unsigned: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::string: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::object: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::array: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::binary: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::null: + *this = nullptr; + break; + case value_t::discarded: + m_type = value_t::discarded; + break; + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } + set_parents(); + assert_invariant(); + } + + /*! + @brief create a container (array or object) from an initializer list + + Creates a JSON value of type array or object from the passed initializer + list @a init. In case @a type_deduction is `true` (default), the type of + the JSON value to be created is deducted from the initializer list @a init + according to the following rules: + + 1. If the list is empty, an empty JSON object value `{}` is created. + 2. If the list consists of pairs whose first element is a string, a JSON + object value is created where the first elements of the pairs are + treated as keys and the second elements are as values. + 3. In all other cases, an array is created. + + The rules aim to create the best fit between a C++ initializer list and + JSON values. The rationale is as follows: + + 1. The empty initializer list is written as `{}` which is exactly an empty + JSON object. + 2. C++ has no way of describing mapped types other than to list a list of + pairs. As JSON requires that keys must be of type string, rule 2 is the + weakest constraint one can pose on initializer lists to interpret them + as an object. + 3. In all other cases, the initializer list could not be interpreted as + JSON object type, so interpreting it as JSON array type is safe. + + With the rules described above, the following JSON values cannot be + expressed by an initializer list: + + - the empty array (`[]`): use @ref array(initializer_list_t) + with an empty initializer list in this case + - arrays whose elements satisfy rule 2: use @ref + array(initializer_list_t) with the same initializer list + in this case + + @note When used without parentheses around an empty initializer list, @ref + basic_json() is called instead of this function, yielding the JSON null + value. + + @param[in] init initializer list with JSON values + + @param[in] type_deduction internal parameter; when set to `true`, the type + of the JSON value is deducted from the initializer list @a init; when set + to `false`, the type provided via @a manual_type is forced. This mode is + used by the functions @ref array(initializer_list_t) and + @ref object(initializer_list_t). + + @param[in] manual_type internal parameter; when @a type_deduction is set + to `false`, the created JSON value will use the provided type (only @ref + value_t::array and @ref value_t::object are valid); when @a type_deduction + is set to `true`, this parameter has no effect + + @throw type_error.301 if @a type_deduction is `false`, @a manual_type is + `value_t::object`, but @a init contains an element which is not a pair + whose first element is a string. In this case, the constructor could not + create an object. If @a type_deduction would have be `true`, an array + would have been created. See @ref object(initializer_list_t) + for an example. + + @complexity Linear in the size of the initializer list @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The example below shows how JSON values are created from + initializer lists.,basic_json__list_init_t} + + @sa see @ref array(initializer_list_t) -- create a JSON array + value from an initializer list + @sa see @ref object(initializer_list_t) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + basic_json(initializer_list_t init, + bool type_deduction = true, + value_t manual_type = value_t::array) + { + // check if each element is an array with two elements whose first + // element is a string + bool is_an_object = std::all_of(init.begin(), init.end(), + [](const detail::json_ref& element_ref) + { + return element_ref->is_array() && element_ref->size() == 2 && (*element_ref)[0].is_string(); + }); + + // adjust type if type deduction is not wanted + if (!type_deduction) + { + // if array is wanted, do not create an object though possible + if (manual_type == value_t::array) + { + is_an_object = false; + } + + // if object is wanted but impossible, throw an exception + if (JSON_HEDLEY_UNLIKELY(manual_type == value_t::object && !is_an_object)) + { + JSON_THROW(type_error::create(301, "cannot create object from initializer list", basic_json())); + } + } + + if (is_an_object) + { + // the initializer list is a list of pairs -> create object + m_type = value_t::object; + m_value = value_t::object; + + for (auto& element_ref : init) + { + 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 + { + // the initializer list describes an array -> create array + m_type = value_t::array; + m_value.array = create(init.begin(), init.end()); + } + + set_parents(); + assert_invariant(); + } + + /*! + @brief explicitly create a binary array (without subtype) + + Creates a JSON binary array value from a given binary container. Binary + values are part of various binary formats, such as CBOR, MessagePack, and + BSON. This constructor is used to create a value for serialization to those + formats. + + @note Note, this function exists because of the difficulty in correctly + specifying the correct template overload in the standard value ctor, as both + JSON arrays and JSON binary arrays are backed with some form of a + `std::vector`. Because JSON binary arrays are a non-standard extension it + was decided that it would be best to prevent automatic initialization of a + binary array type, for backwards compatibility and so it does not happen on + accident. + + @param[in] init container containing bytes to use as binary type + + @return JSON binary array value + + @complexity Linear in the size of @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @since version 3.8.0 + */ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json binary(const typename binary_t::container_type& init) + { + auto res = basic_json(); + res.m_type = value_t::binary; + res.m_value = init; + return res; + } + + /*! + @brief explicitly create a binary array (with subtype) + + Creates a JSON binary array value from a given binary container. Binary + values are part of various binary formats, such as CBOR, MessagePack, and + BSON. This constructor is used to create a value for serialization to those + formats. + + @note Note, this function exists because of the difficulty in correctly + specifying the correct template overload in the standard value ctor, as both + JSON arrays and JSON binary arrays are backed with some form of a + `std::vector`. Because JSON binary arrays are a non-standard extension it + was decided that it would be best to prevent automatic initialization of a + binary array type, for backwards compatibility and so it does not happen on + accident. + + @param[in] init container containing bytes to use as binary type + @param[in] subtype subtype to use in MessagePack and BSON + + @return JSON binary array value + + @complexity Linear in the size of @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @since version 3.8.0 + */ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json binary(const typename binary_t::container_type& init, std::uint8_t subtype) + { + auto res = basic_json(); + res.m_type = value_t::binary; + res.m_value = binary_t(init, subtype); + return res; + } + + /// @copydoc binary(const typename binary_t::container_type&) + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json binary(typename binary_t::container_type&& init) + { + auto res = basic_json(); + res.m_type = value_t::binary; + res.m_value = std::move(init); + return res; + } + + /// @copydoc binary(const typename binary_t::container_type&, std::uint8_t) + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json binary(typename binary_t::container_type&& init, std::uint8_t subtype) + { + auto res = basic_json(); + res.m_type = value_t::binary; + res.m_value = binary_t(std::move(init), subtype); + return res; + } + + /*! + @brief explicitly create an array from an initializer list + + Creates a JSON array value from a given initializer list. That is, given a + list of values `a, b, c`, creates the JSON value `[a, b, c]`. If the + initializer list is empty, the empty array `[]` is created. + + @note This function is only needed to express two edge cases that cannot + be realized with the initializer list constructor (@ref + basic_json(initializer_list_t, bool, value_t)). These cases + are: + 1. creating an array whose elements are all pairs whose first element is a + string -- in this case, the initializer list constructor would create an + object, taking the first elements as keys + 2. creating an empty array -- passing the empty initializer list to the + initializer list constructor yields an empty object + + @param[in] init initializer list with JSON values to create an array from + (optional) + + @return JSON array value + + @complexity Linear in the size of @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows an example for the `array` + function.,array} + + @sa see @ref basic_json(initializer_list_t, bool, value_t) -- + create a JSON value from an initializer list + @sa see @ref object(initializer_list_t) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json array(initializer_list_t init = {}) + { + return basic_json(init, false, value_t::array); + } + + /*! + @brief explicitly create an object from an initializer list + + Creates a JSON object value from a given initializer list. The initializer + lists elements must be pairs, and their first elements must be strings. If + the initializer list is empty, the empty object `{}` is created. + + @note This function is only added for symmetry reasons. In contrast to the + related function @ref array(initializer_list_t), there are + no cases which can only be expressed by this function. That is, any + initializer list @a init can also be passed to the initializer list + constructor @ref basic_json(initializer_list_t, bool, value_t). + + @param[in] init initializer list to create an object from (optional) + + @return JSON object value + + @throw type_error.301 if @a init is not a list of pairs whose first + elements are strings. In this case, no object can be created. When such a + value is passed to @ref basic_json(initializer_list_t, bool, value_t), + an array would have been created from the passed initializer list @a init. + See example below. + + @complexity Linear in the size of @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows an example for the `object` + function.,object} + + @sa see @ref basic_json(initializer_list_t, bool, value_t) -- + create a JSON value from an initializer list + @sa see @ref array(initializer_list_t) -- create a JSON array + value from an initializer list + + @since version 1.0.0 + */ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json object(initializer_list_t init = {}) + { + return basic_json(init, false, value_t::object); + } + + /*! + @brief construct an array with count copies of given value + + Constructs a JSON array value by creating @a cnt copies of a passed value. + In case @a cnt is `0`, an empty array is created. + + @param[in] cnt the number of JSON copies of @a val to create + @param[in] val the JSON value to copy + + @post `std::distance(begin(),end()) == cnt` holds. + + @complexity Linear in @a cnt. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows examples for the @ref + basic_json(size_type\, const basic_json&) + constructor.,basic_json__size_type_basic_json} + + @since version 1.0.0 + */ + basic_json(size_type cnt, const basic_json& val) + : m_type(value_t::array) + { + m_value.array = create(cnt, val); + set_parents(); + assert_invariant(); + } + + /*! + @brief construct a JSON container given an iterator range + + Constructs the JSON value with the contents of the range `[first, last)`. + The semantics depends on the different types a JSON value can have: + - In case of a null type, invalid_iterator.206 is thrown. + - In case of other primitive types (number, boolean, or string), @a first + must be `begin()` and @a last must be `end()`. In this case, the value is + copied. Otherwise, invalid_iterator.204 is thrown. + - In case of structured types (array, object), the constructor behaves as + similar versions for `std::vector` or `std::map`; that is, a JSON array + or object is constructed from the values in the range. + + @tparam InputIT an input iterator type (@ref iterator or @ref + const_iterator) + + @param[in] first begin of the range to copy from (included) + @param[in] last end of the range to copy from (excluded) + + @pre Iterators @a first and @a last must be initialized. **This + precondition is enforced with an assertion (see warning).** If + assertions are switched off, a violation of this precondition yields + undefined behavior. + + @pre Range `[first, last)` is valid. Usually, this precondition cannot be + checked efficiently. Only certain edge cases are detected; see the + description of the exceptions below. A violation of this precondition + yields undefined behavior. + + @warning A precondition is enforced with a runtime assertion that will + result in calling `std::abort` if this precondition is not met. + Assertions can be disabled by defining `NDEBUG` at compile time. + See https://en.cppreference.com/w/cpp/error/assert for more + information. + + @throw invalid_iterator.201 if iterators @a first and @a last are not + compatible (i.e., do not belong to the same JSON value). In this case, + the range `[first, last)` is undefined. + @throw invalid_iterator.204 if iterators @a first and @a last belong to a + primitive type (number, boolean, or string), but @a first does not point + to the first element any more. In this case, the range `[first, last)` is + undefined. See example code below. + @throw invalid_iterator.206 if iterators @a first and @a last belong to a + null value. In this case, the range `[first, last)` is undefined. + + @complexity Linear in distance between @a first and @a last. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The example below shows several ways to create JSON values by + specifying a subrange with iterators.,basic_json__InputIt_InputIt} + + @since version 1.0.0 + */ + template < class InputIT, typename std::enable_if < + std::is_same::value || + std::is_same::value, int >::type = 0 > + basic_json(InputIT first, InputIT last) + { + JSON_ASSERT(first.m_object != nullptr); + JSON_ASSERT(last.m_object != nullptr); + + // make sure iterator fits the current value + if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(201, "iterators are not compatible", basic_json())); + } + + // copy type from first iterator + m_type = first.m_object->m_type; + + // check if iterator range is complete for primitive values + 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_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin() + || !last.m_it.primitive_iterator.is_end())) + { + JSON_THROW(invalid_iterator::create(204, "iterators out of range", *first.m_object)); + } + 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(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + m_value.array = create(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + case value_t::binary: + { + m_value = *first.m_object->m_value.binary; + break; + } + + default: + JSON_THROW(invalid_iterator::create(206, "cannot construct with iterators from " + std::string(first.m_object->type_name()), *first.m_object)); + } + + set_parents(); + assert_invariant(); + } + + + /////////////////////////////////////// + // other constructors and destructor // + /////////////////////////////////////// + + template, + std::is_same>::value, int> = 0 > + basic_json(const JsonRef& ref) : basic_json(ref.moved_or_copied()) {} + + /*! + @brief copy constructor + + Creates a copy of a given JSON value. + + @param[in] other the JSON value to copy + + @post `*this == other` + + @complexity Linear in the size of @a other. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is linear. + - As postcondition, it holds: `other == basic_json(other)`. + + @liveexample{The following code shows an example for the copy + constructor.,basic_json__basic_json} + + @since version 1.0.0 + */ + basic_json(const basic_json& other) + : m_type(other.m_type) + { + // check of passed value is valid + 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; + } + + case value_t::binary: + { + m_value = *other.m_value.binary; + break; + } + + default: + break; + } + + set_parents(); + assert_invariant(); + } + + /*! + @brief move constructor + + Move constructor. Constructs a JSON value with the contents of the given + value @a other using move semantics. It "steals" the resources from @a + other and leaves it as JSON null value. + + @param[in,out] other value to move to this object + + @post `*this` has the same value as @a other before the call. + @post @a other is a JSON null value. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. + + @requirement This function helps `basic_json` satisfying the + [MoveConstructible](https://en.cppreference.com/w/cpp/named_req/MoveConstructible) + requirements. + + @liveexample{The code below shows the move constructor explicitly called + via std::move.,basic_json__moveconstructor} + + @since version 1.0.0 + */ + basic_json(basic_json&& other) noexcept + : m_type(std::move(other.m_type)), + m_value(std::move(other.m_value)) + { + // check that passed value is valid + other.assert_invariant(false); + + // invalidate payload + other.m_type = value_t::null; + other.m_value = {}; + + set_parents(); + assert_invariant(); + } + + /*! + @brief copy assignment + + Copy assignment operator. Copies a JSON value via the "copy and swap" + strategy: It is expressed in terms of the copy constructor, destructor, + and the `swap()` member function. + + @param[in] other value to copy from + + @complexity Linear. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is linear. + + @liveexample{The code below shows and example for the copy assignment. It + creates a copy of value `a` which is then swapped with `b`. Finally\, the + copy of `a` (which is the null value after the swap) is + destroyed.,basic_json__copyassignment} + + @since version 1.0.0 + */ + basic_json& operator=(basic_json other) noexcept ( + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_assignable::value&& + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_assignable::value + ) + { + // check that passed value is valid + other.assert_invariant(); + + using std::swap; + swap(m_type, other.m_type); + swap(m_value, other.m_value); + + set_parents(); + assert_invariant(); + return *this; + } + + /*! + @brief destructor + + Destroys the JSON value and frees all allocated memory. + + @complexity Linear. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is linear. + - All stored elements are destroyed and all memory is freed. + + @since version 1.0.0 + */ + ~basic_json() noexcept + { + assert_invariant(false); + m_value.destroy(m_type); + } + + /// @} + + public: + /////////////////////// + // object inspection // + /////////////////////// + + /// @name object inspection + /// Functions to inspect the type of a JSON value. + /// @{ + + /*! + @brief serialization + + Serialization function for JSON values. The function tries to mimic + Python's `json.dumps()` function, and currently supports its @a indent + and @a ensure_ascii parameters. + + @param[in] indent If indent is nonnegative, then array elements and object + members will be pretty-printed with that indent level. An indent level of + `0` will only insert newlines. `-1` (the default) selects the most compact + representation. + @param[in] indent_char The character to use for indentation if @a indent is + greater than `0`. The default is ` ` (space). + @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters + in the output are escaped with `\uXXXX` sequences, and the result consists + of ASCII characters only. + @param[in] error_handler how to react on decoding errors; there are three + possible values: `strict` (throws and exception in case a decoding error + occurs; default), `replace` (replace invalid UTF-8 sequences with U+FFFD), + and `ignore` (ignore invalid UTF-8 sequences during serialization; all + bytes are copied to the output unchanged). + + @return string containing the serialization of the JSON value + + @throw type_error.316 if a string stored inside the JSON value is not + UTF-8 encoded and @a error_handler is set to strict + + @note Binary values are serialized as object containing two keys: + - "bytes": an array of bytes as integers + - "subtype": the subtype as integer or "null" if the binary has no subtype + + @complexity Linear. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @liveexample{The following example shows the effect of different @a indent\, + @a indent_char\, and @a ensure_ascii parameters to the result of the + serialization.,dump} + + @see https://docs.python.org/2/library/json.html#json.dump + + @since version 1.0.0; indentation character @a indent_char, option + @a ensure_ascii and exceptions added in version 3.0.0; error + handlers added in version 3.4.0; serialization of binary values added + in version 3.8.0. + */ + string_t dump(const int indent = -1, + const char indent_char = ' ', + const bool ensure_ascii = false, + const error_handler_t error_handler = error_handler_t::strict) const + { + string_t result; + serializer s(detail::output_adapter(result), indent_char, error_handler); + + if (indent >= 0) + { + s.dump(*this, true, ensure_ascii, static_cast(indent)); + } + else + { + s.dump(*this, false, ensure_ascii, 0); + } + + return result; + } + + /*! + @brief return the type of the JSON value (explicit) + + Return the type of the JSON value as a value from the @ref value_t + enumeration. + + @return the type of the JSON value + Value type | return value + ------------------------- | ------------------------- + null | value_t::null + boolean | value_t::boolean + string | value_t::string + number (integer) | value_t::number_integer + number (unsigned integer) | value_t::number_unsigned + number (floating-point) | value_t::number_float + object | value_t::object + array | value_t::array + binary | value_t::binary + discarded | value_t::discarded + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `type()` for all JSON + types.,type} + + @sa see @ref operator value_t() -- return the type of the JSON value (implicit) + @sa see @ref type_name() -- return the type as string + + @since version 1.0.0 + */ + constexpr value_t type() const noexcept + { + return m_type; + } + + /*! + @brief return whether type is primitive + + This function returns true if and only if the JSON type is primitive + (string, number, boolean, or null). + + @return `true` if type is primitive (string, number, boolean, or null), + `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_primitive()` for all JSON + types.,is_primitive} + + @sa see @ref is_structured() -- returns whether JSON value is structured + @sa see @ref is_null() -- returns whether JSON value is `null` + @sa see @ref is_string() -- returns whether JSON value is a string + @sa see @ref is_boolean() -- returns whether JSON value is a boolean + @sa see @ref is_number() -- returns whether JSON value is a number + @sa see @ref is_binary() -- returns whether JSON value is a binary array + + @since version 1.0.0 + */ + constexpr bool is_primitive() const noexcept + { + return is_null() || is_string() || is_boolean() || is_number() || is_binary(); + } + + /*! + @brief return whether type is structured + + This function returns true if and only if the JSON type is structured + (array or object). + + @return `true` if type is structured (array or object), `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_structured()` for all JSON + types.,is_structured} + + @sa see @ref is_primitive() -- returns whether value is primitive + @sa see @ref is_array() -- returns whether value is an array + @sa see @ref is_object() -- returns whether value is an object + + @since version 1.0.0 + */ + constexpr bool is_structured() const noexcept + { + return is_array() || is_object(); + } + + /*! + @brief return whether value is null + + This function returns true if and only if the JSON value is null. + + @return `true` if type is null, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_null()` for all JSON + types.,is_null} + + @since version 1.0.0 + */ + constexpr bool is_null() const noexcept + { + return m_type == value_t::null; + } + + /*! + @brief return whether value is a boolean + + This function returns true if and only if the JSON value is a boolean. + + @return `true` if type is boolean, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_boolean()` for all JSON + types.,is_boolean} + + @since version 1.0.0 + */ + constexpr bool is_boolean() const noexcept + { + return m_type == value_t::boolean; + } + + /*! + @brief return whether value is a number + + This function returns true if and only if the JSON value is a number. This + includes both integer (signed and unsigned) and floating-point values. + + @return `true` if type is number (regardless whether integer, unsigned + integer or floating-type), `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number()` for all JSON + types.,is_number} + + @sa see @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa see @ref is_number_unsigned() -- check if value is an unsigned integer + number + @sa see @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 + */ + constexpr bool is_number() const noexcept + { + return is_number_integer() || is_number_float(); + } + + /*! + @brief return whether value is an integer number + + This function returns true if and only if the JSON value is a signed or + unsigned integer number. This excludes floating-point values. + + @return `true` if type is an integer or unsigned integer number, `false` + otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_integer()` for all + JSON types.,is_number_integer} + + @sa see @ref is_number() -- check if value is a number + @sa see @ref is_number_unsigned() -- check if value is an unsigned integer + number + @sa see @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 + */ + constexpr bool is_number_integer() const noexcept + { + return m_type == value_t::number_integer || m_type == value_t::number_unsigned; + } + + /*! + @brief return whether value is an unsigned integer number + + This function returns true if and only if the JSON value is an unsigned + integer number. This excludes floating-point and signed integer values. + + @return `true` if type is an unsigned integer number, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_unsigned()` for all + JSON types.,is_number_unsigned} + + @sa see @ref is_number() -- check if value is a number + @sa see @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa see @ref is_number_float() -- check if value is a floating-point number + + @since version 2.0.0 + */ + constexpr bool is_number_unsigned() const noexcept + { + return m_type == value_t::number_unsigned; + } + + /*! + @brief return whether value is a floating-point number + + This function returns true if and only if the JSON value is a + floating-point number. This excludes signed and unsigned integer values. + + @return `true` if type is a floating-point number, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_float()` for all + JSON types.,is_number_float} + + @sa see @ref is_number() -- check if value is number + @sa see @ref is_number_integer() -- check if value is an integer number + @sa see @ref is_number_unsigned() -- check if value is an unsigned integer + number + + @since version 1.0.0 + */ + constexpr bool is_number_float() const noexcept + { + return m_type == value_t::number_float; + } + + /*! + @brief return whether value is an object + + This function returns true if and only if the JSON value is an object. + + @return `true` if type is object, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_object()` for all JSON + types.,is_object} + + @since version 1.0.0 + */ + constexpr bool is_object() const noexcept + { + return m_type == value_t::object; + } + + /*! + @brief return whether value is an array + + This function returns true if and only if the JSON value is an array. + + @return `true` if type is array, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_array()` for all JSON + types.,is_array} + + @since version 1.0.0 + */ + constexpr bool is_array() const noexcept + { + return m_type == value_t::array; + } + + /*! + @brief return whether value is a string + + This function returns true if and only if the JSON value is a string. + + @return `true` if type is string, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_string()` for all JSON + types.,is_string} + + @since version 1.0.0 + */ + constexpr bool is_string() const noexcept + { + return m_type == value_t::string; + } + + /*! + @brief return whether value is a binary array + + This function returns true if and only if the JSON value is a binary array. + + @return `true` if type is binary array, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_binary()` for all JSON + types.,is_binary} + + @since version 3.8.0 + */ + constexpr bool is_binary() const noexcept + { + return m_type == value_t::binary; + } + + /*! + @brief return whether value is discarded + + This function returns true if and only if the JSON value was discarded + during parsing with a callback function (see @ref parser_callback_t). + + @note This function will always be `false` for JSON values after parsing. + That is, discarded values can only occur during parsing, but will be + removed when inside a structured value or replaced by null in other cases. + + @return `true` if type is discarded, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_discarded()` for all JSON + types.,is_discarded} + + @since version 1.0.0 + */ + constexpr bool is_discarded() const noexcept + { + return m_type == value_t::discarded; + } + + /*! + @brief return the type of the JSON value (implicit) + + Implicitly return the type of the JSON value as a value from the @ref + value_t enumeration. + + @return the type of the JSON value + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies the @ref value_t operator for + all JSON types.,operator__value_t} + + @sa see @ref type() -- return the type of the JSON value (explicit) + @sa see @ref type_name() -- return the type as string + + @since version 1.0.0 + */ + constexpr operator value_t() const noexcept + { + return m_type; + } + + /// @} + + private: + ////////////////// + // value access // + ////////////////// + + /// get a boolean (explicit) + boolean_t get_impl(boolean_t* /*unused*/) const + { + if (JSON_HEDLEY_LIKELY(is_boolean())) + { + return m_value.boolean; + } + + JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(type_name()), *this)); + } + + /// get a pointer to the value (object) + object_t* get_impl_ptr(object_t* /*unused*/) noexcept + { + return is_object() ? m_value.object : nullptr; + } + + /// get a pointer to the value (object) + constexpr const object_t* get_impl_ptr(const object_t* /*unused*/) const noexcept + { + return is_object() ? m_value.object : nullptr; + } + + /// get a pointer to the value (array) + array_t* get_impl_ptr(array_t* /*unused*/) noexcept + { + return is_array() ? m_value.array : nullptr; + } + + /// get a pointer to the value (array) + constexpr const array_t* get_impl_ptr(const array_t* /*unused*/) const noexcept + { + return is_array() ? m_value.array : nullptr; + } + + /// get a pointer to the value (string) + string_t* get_impl_ptr(string_t* /*unused*/) noexcept + { + return is_string() ? m_value.string : nullptr; + } + + /// get a pointer to the value (string) + constexpr const string_t* get_impl_ptr(const string_t* /*unused*/) const noexcept + { + return is_string() ? m_value.string : nullptr; + } + + /// get a pointer to the value (boolean) + boolean_t* get_impl_ptr(boolean_t* /*unused*/) noexcept + { + return is_boolean() ? &m_value.boolean : nullptr; + } + + /// get a pointer to the value (boolean) + constexpr const boolean_t* get_impl_ptr(const boolean_t* /*unused*/) const noexcept + { + return is_boolean() ? &m_value.boolean : nullptr; + } + + /// get a pointer to the value (integer number) + number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept + { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + /// get a pointer to the value (integer number) + constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept + { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + /// get a pointer to the value (unsigned number) + number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (unsigned number) + constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (floating-point number) + number_float_t* get_impl_ptr(number_float_t* /*unused*/) noexcept + { + return is_number_float() ? &m_value.number_float : nullptr; + } + + /// get a pointer to the value (floating-point number) + constexpr const number_float_t* get_impl_ptr(const number_float_t* /*unused*/) const noexcept + { + return is_number_float() ? &m_value.number_float : nullptr; + } + + /// get a pointer to the value (binary) + binary_t* get_impl_ptr(binary_t* /*unused*/) noexcept + { + return is_binary() ? m_value.binary : nullptr; + } + + /// get a pointer to the value (binary) + constexpr const binary_t* get_impl_ptr(const binary_t* /*unused*/) const noexcept + { + return is_binary() ? m_value.binary : nullptr; + } + + /*! + @brief helper function to implement get_ref() + + This function helps to implement get_ref() without code duplication for + const and non-const overloads + + @tparam ThisType will be deduced as `basic_json` or `const basic_json` + + @throw type_error.303 if ReferenceType does not match underlying value + type of the current JSON + */ + template + static ReferenceType get_ref_impl(ThisType& obj) + { + // delegate the call to get_ptr<>() + auto* ptr = obj.template get_ptr::type>(); + + if (JSON_HEDLEY_LIKELY(ptr != nullptr)) + { + return *ptr; + } + + JSON_THROW(type_error::create(303, "incompatible ReferenceType for get_ref, actual type is " + std::string(obj.type_name()), obj)); + } + + public: + /// @name value access + /// Direct access to the stored value of a JSON value. + /// @{ + + /*! + @brief get a pointer value (implicit) + + Implicit pointer access to the internally stored JSON value. No copies are + made. + + @warning Writing data to the pointee of the result yields an undefined + state. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. Enforced by a static + assertion. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get_ptr} + + @since version 1.0.0 + */ + template::value, int>::type = 0> + auto get_ptr() noexcept -> decltype(std::declval().get_impl_ptr(std::declval())) + { + // delegate the call to get_impl_ptr<>() + return get_impl_ptr(static_cast(nullptr)); + } + + /*! + @brief get a pointer value (implicit) + @copydoc get_ptr() + */ + template < typename PointerType, typename std::enable_if < + std::is_pointer::value&& + std::is_const::type>::value, int >::type = 0 > + constexpr auto get_ptr() const noexcept -> decltype(std::declval().get_impl_ptr(std::declval())) + { + // delegate the call to get_impl_ptr<>() const + return get_impl_ptr(static_cast(nullptr)); + } + + private: + /*! + @brief get a value (explicit) + + Explicit type conversion between the JSON value and a compatible value + which is [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) + and [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). + The value is converted by calling the @ref json_serializer + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + ValueType ret; + JSONSerializer::from_json(*this, ret); + return ret; + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json, + - @ref json_serializer has a `from_json()` method of the form + `void from_json(const basic_json&, ValueType&)`, and + - @ref json_serializer does not have a `from_json()` method of + the form `ValueType from_json(const basic_json&)` + + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer `from_json()` method throws + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,get__ValueType_const} + + @since version 2.1.0 + */ + template < typename ValueType, + detail::enable_if_t < + detail::is_default_constructible::value&& + detail::has_from_json::value, + int > = 0 > + ValueType get_impl(detail::priority_tag<0> /*unused*/) const noexcept(noexcept( + JSONSerializer::from_json(std::declval(), std::declval()))) + { + ValueType ret{}; + JSONSerializer::from_json(*this, ret); + return ret; + } + + /*! + @brief get a value (explicit); special case + + Explicit type conversion between the JSON value and a compatible value + which is **not** [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) + and **not** [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). + The value is converted by calling the @ref json_serializer + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + return JSONSerializer::from_json(*this); + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json and + - @ref json_serializer has a `from_json()` method of the form + `ValueType from_json(const basic_json&)` + + @note If @ref json_serializer has both overloads of + `from_json()`, this one is chosen. + + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer `from_json()` method throws + + @since version 2.1.0 + */ + template < typename ValueType, + detail::enable_if_t < + detail::has_non_default_from_json::value, + int > = 0 > + ValueType get_impl(detail::priority_tag<1> /*unused*/) const noexcept(noexcept( + JSONSerializer::from_json(std::declval()))) + { + return JSONSerializer::from_json(*this); + } + + /*! + @brief get special-case overload + + This overloads converts the current @ref basic_json in a different + @ref basic_json type + + @tparam BasicJsonType == @ref basic_json + + @return a copy of *this, converted into @a BasicJsonType + + @complexity Depending on the implementation of the called `from_json()` + method. + + @since version 3.2.0 + */ + template < typename BasicJsonType, + detail::enable_if_t < + detail::is_basic_json::value, + int > = 0 > + BasicJsonType get_impl(detail::priority_tag<2> /*unused*/) const + { + return *this; + } + + /*! + @brief get special-case overload + + This overloads avoids a lot of template boilerplate, it can be seen as the + identity method + + @tparam BasicJsonType == @ref basic_json + + @return a copy of *this + + @complexity Constant. + + @since version 2.1.0 + */ + template::value, + int> = 0> + basic_json get_impl(detail::priority_tag<3> /*unused*/) const + { + return *this; + } + + /*! + @brief get a pointer value (explicit) + @copydoc get() + */ + template::value, + int> = 0> + constexpr auto get_impl(detail::priority_tag<4> /*unused*/) const noexcept + -> decltype(std::declval().template get_ptr()) + { + // delegate the call to get_ptr + return get_ptr(); + } + + public: + /*! + @brief get a (pointer) value (explicit) + + Performs explicit type conversion between the JSON value and a compatible value if required. + + - If the requested type is a pointer to the internally stored JSON value that pointer is returned. + No copies are made. + + - If the requested type is the current @ref basic_json, or a different @ref basic_json convertible + from the current @ref basic_json. + + - Otherwise the value is converted by calling the @ref json_serializer `from_json()` + method. + + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @tparam ValueType if necessary + + @throw what @ref json_serializer `from_json()` method throws if conversion is required + + @since version 2.1.0 + */ + template < typename ValueTypeCV, typename ValueType = detail::uncvref_t> +#if defined(JSON_HAS_CPP_14) + constexpr +#endif + auto get() const noexcept( + noexcept(std::declval().template get_impl(detail::priority_tag<4> {}))) + -> decltype(std::declval().template get_impl(detail::priority_tag<4> {})) + { + // we cannot static_assert on ValueTypeCV being non-const, because + // there is support for get(), which is why we + // still need the uncvref + static_assert(!std::is_reference::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + return get_impl(detail::priority_tag<4> {}); + } + + /*! + @brief get a pointer value (explicit) + + Explicit pointer access to the internally stored JSON value. No copies are + made. + + @warning The pointer becomes invalid if the underlying JSON object + changes. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get__PointerType} + + @sa see @ref get_ptr() for explicit pointer-member access + + @since version 1.0.0 + */ + template::value, int>::type = 0> + auto get() noexcept -> decltype(std::declval().template get_ptr()) + { + // delegate the call to get_ptr + return get_ptr(); + } + + /*! + @brief get a value (explicit) + + Explicit type conversion between the JSON value and a compatible value. + The value is filled into the input parameter by calling the @ref json_serializer + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + ValueType v; + JSONSerializer::from_json(*this, v); + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json, + - @ref json_serializer has a `from_json()` method of the form + `void from_json(const basic_json&, ValueType&)`, and + + @tparam ValueType the input parameter type. + + @return the input parameter, allowing chaining calls. + + @throw what @ref json_serializer `from_json()` method throws + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,get_to} + + @since version 3.3.0 + */ + template < typename ValueType, + detail::enable_if_t < + !detail::is_basic_json::value&& + detail::has_from_json::value, + int > = 0 > + ValueType & get_to(ValueType& v) const noexcept(noexcept( + JSONSerializer::from_json(std::declval(), v))) + { + JSONSerializer::from_json(*this, v); + return v; + } + + // specialization to allow to call get_to with a basic_json value + // see https://github.com/nlohmann/json/issues/2175 + template::value, + int> = 0> + ValueType & get_to(ValueType& v) const + { + v = *this; + return v; + } + + template < + typename T, std::size_t N, + typename Array = T (&)[N], // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + detail::enable_if_t < + detail::has_from_json::value, int > = 0 > + Array get_to(T (&v)[N]) const // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + noexcept(noexcept(JSONSerializer::from_json( + std::declval(), v))) + { + JSONSerializer::from_json(*this, v); + return v; + } + + /*! + @brief get a reference value (implicit) + + Implicit reference access to the internally stored JSON value. No copies + are made. + + @warning Writing data to the referee of the result yields an undefined + state. + + @tparam ReferenceType reference type; must be a reference to @ref array_t, + @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or + @ref number_float_t. Enforced by static assertion. + + @return reference to the internally stored JSON value if the requested + reference type @a ReferenceType fits to the JSON value; throws + type_error.303 otherwise + + @throw type_error.303 in case passed type @a ReferenceType is incompatible + with the stored JSON value; see example below + + @complexity Constant. + + @liveexample{The example shows several calls to `get_ref()`.,get_ref} + + @since version 1.1.0 + */ + template::value, int>::type = 0> + ReferenceType get_ref() + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /*! + @brief get a reference value (implicit) + @copydoc get_ref() + */ + template < typename ReferenceType, typename std::enable_if < + std::is_reference::value&& + std::is_const::type>::value, int >::type = 0 > + ReferenceType get_ref() const + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /*! + @brief get a value (implicit) + + Implicit type conversion between the JSON value and a compatible value. + The call is realized by calling @ref get() const. + + @tparam ValueType non-pointer type compatible to the JSON value, for + instance `int` for JSON integer numbers, `bool` for JSON booleans, or + `std::vector` types for JSON arrays. The character type of @ref string_t + as well as an initializer list of this type is excluded to avoid + ambiguities as these types implicitly convert to `std::string`. + + @return copy of the JSON value, converted to type @a ValueType + + @throw type_error.302 in case passed type @a ValueType is incompatible + to the JSON value type (e.g., the JSON value is of type boolean, but a + string is requested); see example below + + @complexity Linear in the size of the JSON value. + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,operator__ValueType} + + @since version 1.0.0 + */ + template < typename ValueType, typename std::enable_if < + !std::is_pointer::value&& + !std::is_same>::value&& + !std::is_same::value&& + !detail::is_basic_json::value + && !std::is_same>::value +#if defined(JSON_HAS_CPP_17) && (defined(__GNUC__) || (defined(_MSC_VER) && _MSC_VER >= 1910 && _MSC_VER <= 1914)) + && !std::is_same::value +#endif + && detail::is_detected::value + , int >::type = 0 > + JSON_EXPLICIT operator ValueType() const + { + // delegate the call to get<>() const + return get(); + } + + /*! + @return reference to the binary value + + @throw type_error.302 if the value is not binary + + @sa see @ref is_binary() to check if the value is binary + + @since version 3.8.0 + */ + binary_t& get_binary() + { + if (!is_binary()) + { + JSON_THROW(type_error::create(302, "type must be binary, but is " + std::string(type_name()), *this)); + } + + return *get_ptr(); + } + + /// @copydoc get_binary() + const binary_t& get_binary() const + { + if (!is_binary()) + { + JSON_THROW(type_error::create(302, "type must be binary, but is " + std::string(type_name()), *this)); + } + + return *get_ptr(); + } + + /// @} + + + //////////////////// + // element access // + //////////////////// + + /// @name element access + /// Access to the JSON value. + /// @{ + + /*! + @brief access specified array element with bounds checking + + Returns a reference to the element at specified location @a idx, with + bounds checking. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw type_error.304 if the JSON value is not an array; in this case, + calling `at` with an index makes no sense. See example below. + @throw out_of_range.401 if the index @a idx is out of range of the array; + that is, `idx >= size()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 1.0.0 + + @liveexample{The example below shows how array elements can be read and + written using `at()`. It also demonstrates the different exceptions that + can be thrown.,at__size_type} + */ + reference at(size_type idx) + { + // at only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + JSON_TRY + { + return set_parent(m_value.array->at(idx)); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range", *this)); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()), *this)); + } + } + + /*! + @brief access specified array element with bounds checking + + Returns a const reference to the element at specified location @a idx, + with bounds checking. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw type_error.304 if the JSON value is not an array; in this case, + calling `at` with an index makes no sense. See example below. + @throw out_of_range.401 if the index @a idx is out of range of the array; + that is, `idx >= size()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 1.0.0 + + @liveexample{The example below shows how array elements can be read using + `at()`. It also demonstrates the different exceptions that can be thrown., + at__size_type_const} + */ + const_reference at(size_type idx) const + { + // at only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + JSON_TRY + { + return m_value.array->at(idx); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range", *this)); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()), *this)); + } + } + + /*! + @brief access specified object element with bounds checking + + Returns a reference to the element at with specified key @a key, with + bounds checking. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw type_error.304 if the JSON value is not an object; in this case, + calling `at` with a key makes no sense. See example below. + @throw out_of_range.403 if the key @a key is is not stored in the object; + that is, `find(key) == end()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Logarithmic in the size of the container. + + @sa see @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa see @ref value() for access by value with a default value + + @since version 1.0.0 + + @liveexample{The example below shows how object elements can be read and + written using `at()`. It also demonstrates the different exceptions that + can be thrown.,at__object_t_key_type} + */ + reference at(const typename object_t::key_type& key) + { + // at only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + JSON_TRY + { + return set_parent(m_value.object->at(key)); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(403, "key '" + key + "' not found", *this)); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()), *this)); + } + } + + /*! + @brief access specified object element with bounds checking + + Returns a const reference to the element at with specified key @a key, + with bounds checking. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @throw type_error.304 if the JSON value is not an object; in this case, + calling `at` with a key makes no sense. See example below. + @throw out_of_range.403 if the key @a key is is not stored in the object; + that is, `find(key) == end()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Logarithmic in the size of the container. + + @sa see @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa see @ref value() for access by value with a default value + + @since version 1.0.0 + + @liveexample{The example below shows how object elements can be read using + `at()`. It also demonstrates the different exceptions that can be thrown., + at__object_t_key_type_const} + */ + const_reference at(const typename object_t::key_type& key) const + { + // at only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + JSON_TRY + { + return m_value.object->at(key); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(403, "key '" + key + "' not found", *this)); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()), *this)); + } + } + + /*! + @brief access specified array element + + Returns a reference to the element at specified location @a idx. + + @note If @a idx is beyond the range of the array (i.e., `idx >= size()`), + then the array is silently filled up with `null` values to make `idx` a + valid reference to the last stored element. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw type_error.305 if the JSON value is not an array or null; in that + cases, using the [] operator with an index makes no sense. + + @complexity Constant if @a idx is in the range of the array. Otherwise + linear in `idx - size()`. + + @liveexample{The example below shows how array elements can be read and + written using `[]` operator. Note the addition of `null` + values.,operatorarray__size_type} + + @since version 1.0.0 + */ + reference operator[](size_type idx) + { + // implicitly convert null value to an empty array + if (is_null()) + { + m_type = value_t::array; + m_value.array = create(); + assert_invariant(); + } + + // operator[] only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + // fill up array with null values if given idx is outside range + if (idx >= m_value.array->size()) + { +#if JSON_DIAGNOSTICS + // remember array size before resizing + const auto previous_size = m_value.array->size(); +#endif + m_value.array->resize(idx + 1); + +#if JSON_DIAGNOSTICS + // set parent for values added above + set_parents(begin() + static_cast(previous_size), static_cast(idx + 1 - previous_size)); +#endif + } + + return m_value.array->operator[](idx); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with a numeric argument with " + std::string(type_name()), *this)); + } + + /*! + @brief access specified array element + + Returns a const reference to the element at specified location @a idx. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw type_error.305 if the JSON value is not an array; in that case, + using the [] operator with an index makes no sense. + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read using + the `[]` operator.,operatorarray__size_type_const} + + @since version 1.0.0 + */ + const_reference operator[](size_type idx) const + { + // const operator[] only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + return m_value.array->operator[](idx); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with a numeric argument with " + std::string(type_name()), *this)); + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw type_error.305 if the JSON value is not an object or null; in that + cases, using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa see @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa see @ref value() for access by value with a default value + + @since version 1.0.0 + */ + reference operator[](const typename object_t::key_type& key) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + // operator[] only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + return set_parent(m_value.object->operator[](key)); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()), *this)); + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @pre The element with key @a key must exist. **This precondition is + enforced with an assertion.** + + @throw type_error.305 if the JSON value is not an object; in that case, + using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa see @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa see @ref value() for access by value with a default value + + @since version 1.0.0 + */ + const_reference operator[](const typename object_t::key_type& key) const + { + // const operator[] only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + JSON_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 a string argument with " + std::string(type_name()), *this)); + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw type_error.305 if the JSON value is not an object or null; in that + cases, using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa see @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa see @ref value() for access by value with a default value + + @since version 1.1.0 + */ + template + JSON_HEDLEY_NON_NULL(2) + reference operator[](T* key) + { + // implicitly convert null to object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // at only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + return set_parent(m_value.object->operator[](key)); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()), *this)); + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @pre The element with key @a key must exist. **This precondition is + enforced with an assertion.** + + @throw type_error.305 if the JSON value is not an object; in that case, + using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa see @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa see @ref value() for access by value with a default value + + @since version 1.1.0 + */ + template + JSON_HEDLEY_NON_NULL(2) + const_reference operator[](T* key) const + { + // at only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + JSON_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 a string argument with " + std::string(type_name()), *this)); + } + + /*! + @brief access specified object element with default value + + Returns either a copy of an object's element at the specified key @a key + or a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(key); + } catch(out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const typename object_t::key_type&), this function + does not throw if the given key @a key was not found. + + @note Unlike @ref operator[](const typename object_t::key_type& key), this + function does not implicitly add an element to the position defined by @a + key. This function is furthermore also applicable to const objects. + + @param[in] key key of the element to access + @param[in] default_value the value to return if @a key is not found + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw type_error.302 if @a default_value does not match the type of the + value at @a key + @throw type_error.306 if the JSON value is not an object; in that case, + using `value()` with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value} + + @sa see @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa see @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + + @since version 1.0.0 + */ + // using std::is_convertible in a std::enable_if will fail when using explicit conversions + template < class ValueType, typename std::enable_if < + detail::is_getable::value + && !std::is_same::value, int >::type = 0 > + ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const + { + // at only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + // if key is found, return value and given default value otherwise + const auto it = find(key); + if (it != end()) + { + return it->template get(); + } + + return default_value; + } + + JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()), *this)); + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value(const typename object_t::key_type&, const ValueType&) const + */ + string_t value(const typename object_t::key_type& key, const char* default_value) const + { + return value(key, string_t(default_value)); + } + + /*! + @brief access specified object element via JSON Pointer with default value + + Returns either a copy of an object's element at the specified key @a key + or a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(ptr); + } catch(out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const json_pointer&), this function does not throw + if the given key @a key was not found. + + @param[in] ptr a JSON pointer to the element to access + @param[in] default_value the value to return if @a ptr found no value + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw type_error.302 if @a default_value does not match the type of the + value at @a ptr + @throw type_error.306 if the JSON value is not an object; in that case, + using `value()` with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value_ptr} + + @sa see @ref operator[](const json_pointer&) for unchecked access by reference + + @since version 2.0.2 + */ + template::value, int>::type = 0> + ValueType value(const json_pointer& ptr, const ValueType& default_value) const + { + // at only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + // if pointer resolves a value, return it or use default value + JSON_TRY + { + return ptr.get_checked(this).template get(); + } + JSON_INTERNAL_CATCH (out_of_range&) + { + return default_value; + } + } + + JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()), *this)); + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value(const json_pointer&, ValueType) const + */ + JSON_HEDLEY_NON_NULL(3) + string_t value(const json_pointer& ptr, const char* default_value) const + { + return value(ptr, string_t(default_value)); + } + + /*! + @brief access the first element + + Returns a reference to the first element in the container. For a JSON + container `c`, the expression `c.front()` is equivalent to `*c.begin()`. + + @return In case of a structured type (array or object), a reference to the + first element is returned. In case of number, string, boolean, or binary + values, a reference to the value is returned. + + @complexity Constant. + + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, **guarded by + assertions**). + @post The JSON value remains unchanged. + + @throw invalid_iterator.214 when called on `null` value + + @liveexample{The following code shows an example for `front()`.,front} + + @sa see @ref back() -- access the last element + + @since version 1.0.0 + */ + reference front() + { + return *begin(); + } + + /*! + @copydoc basic_json::front() + */ + const_reference front() const + { + return *cbegin(); + } + + /*! + @brief access the last element + + Returns a reference to the last element in the container. For a JSON + container `c`, the expression `c.back()` is equivalent to + @code {.cpp} + auto tmp = c.end(); + --tmp; + return *tmp; + @endcode + + @return In case of a structured type (array or object), a reference to the + last element is returned. In case of number, string, boolean, or binary + values, a reference to the value is returned. + + @complexity Constant. + + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, **guarded by + assertions**). + @post The JSON value remains unchanged. + + @throw invalid_iterator.214 when called on a `null` value. See example + below. + + @liveexample{The following code shows an example for `back()`.,back} + + @sa see @ref front() -- access the first element + + @since version 1.0.0 + */ + reference back() + { + auto tmp = end(); + --tmp; + return *tmp; + } + + /*! + @copydoc basic_json::back() + */ + const_reference back() const + { + auto tmp = cend(); + --tmp; + return *tmp; + } + + /*! + @brief remove element given an iterator + + Removes the element specified by iterator @a pos. The iterator @a pos must + be valid and dereferenceable. Thus the `end()` iterator (which is valid, + but is not dereferenceable) cannot be used as a value for @a pos. + + If called on a primitive type other than `null`, the resulting JSON value + will be `null`. + + @param[in] pos iterator to the element to remove + @return Iterator following the last removed element. If the iterator @a + pos refers to the last element, the `end()` iterator is returned. + + @tparam IteratorType an @ref iterator or @ref const_iterator + + @post Invalidates iterators and references at or after the point of the + erase, including the `end()` iterator. + + @throw type_error.307 if called on a `null` value; example: `"cannot use + erase() with null"` + @throw invalid_iterator.202 if called on an iterator which does not belong + to the current JSON value; example: `"iterator does not fit current + value"` + @throw invalid_iterator.205 if called on a primitive type with invalid + iterator (i.e., any iterator which is not `begin()`); example: `"iterator + out of range"` + + @complexity The complexity depends on the type: + - objects: amortized constant + - arrays: linear in distance between @a pos and the end of the container + - strings and binary: linear in the length of the member + - other types: constant + + @liveexample{The example shows the result of `erase()` for different JSON + types.,erase__IteratorType} + + @sa see @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa see @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa see @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + template < class IteratorType, typename std::enable_if < + std::is_same::value || + std::is_same::value, int >::type + = 0 > + IteratorType erase(IteratorType pos) + { + // make sure iterator fits the current value + if (JSON_HEDLEY_UNLIKELY(this != pos.m_object)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", *this)); + } + + 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: + case value_t::binary: + { + if (JSON_HEDLEY_UNLIKELY(!pos.m_it.primitive_iterator.is_begin())) + { + JSON_THROW(invalid_iterator::create(205, "iterator out of range", *this)); + } + + if (is_string()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_value.string); + std::allocator_traits::deallocate(alloc, m_value.string, 1); + m_value.string = nullptr; + } + else if (is_binary()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_value.binary); + std::allocator_traits::deallocate(alloc, m_value.binary, 1); + m_value.binary = 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()), *this)); + } + + return result; + } + + /*! + @brief remove elements given an iterator range + + Removes the element specified by the range `[first; last)`. The iterator + @a first does not need to be dereferenceable if `first == last`: erasing + an empty range is a no-op. + + If called on a primitive type other than `null`, the resulting JSON value + will be `null`. + + @param[in] first iterator to the beginning of the range to remove + @param[in] last iterator past the end of the range to remove + @return Iterator following the last removed element. If the iterator @a + second refers to the last element, the `end()` iterator is returned. + + @tparam IteratorType an @ref iterator or @ref const_iterator + + @post Invalidates iterators and references at or after the point of the + erase, including the `end()` iterator. + + @throw type_error.307 if called on a `null` value; example: `"cannot use + erase() with null"` + @throw invalid_iterator.203 if called on iterators which does not belong + to the current JSON value; example: `"iterators do not fit current value"` + @throw invalid_iterator.204 if called on a primitive type with invalid + iterators (i.e., if `first != begin()` and `last != end()`); example: + `"iterators out of range"` + + @complexity The complexity depends on the type: + - objects: `log(size()) + std::distance(first, last)` + - arrays: linear in the distance between @a first and @a last, plus linear + in the distance between @a last and end of the container + - strings and binary: linear in the length of the member + - other types: constant + + @liveexample{The example shows the result of `erase()` for different JSON + types.,erase__IteratorType_IteratorType} + + @sa see @ref erase(IteratorType) -- removes the element at a given position + @sa see @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa see @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + template < class IteratorType, typename std::enable_if < + std::is_same::value || + std::is_same::value, int >::type + = 0 > + IteratorType erase(IteratorType first, IteratorType last) + { + // make sure iterator fits the current value + if (JSON_HEDLEY_UNLIKELY(this != first.m_object || this != last.m_object)) + { + JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value", *this)); + } + + 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: + case value_t::binary: + { + if (JSON_HEDLEY_LIKELY(!first.m_it.primitive_iterator.is_begin() + || !last.m_it.primitive_iterator.is_end())) + { + JSON_THROW(invalid_iterator::create(204, "iterators out of range", *this)); + } + + if (is_string()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_value.string); + std::allocator_traits::deallocate(alloc, m_value.string, 1); + m_value.string = nullptr; + } + else if (is_binary()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_value.binary); + std::allocator_traits::deallocate(alloc, m_value.binary, 1); + m_value.binary = 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()), *this)); + } + + return result; + } + + /*! + @brief remove element from a JSON object given a key + + Removes elements from a JSON object with the key value @a key. + + @param[in] key value of the elements to remove + + @return Number of elements removed. If @a ObjectType is the default + `std::map` type, the return value will always be `0` (@a key was not + found) or `1` (@a key was found). + + @post References and iterators to the erased elements are invalidated. + Other references and iterators are not affected. + + @throw type_error.307 when called on a type other than JSON object; + example: `"cannot use erase() with null"` + + @complexity `log(size()) + count(key)` + + @liveexample{The example shows the effect of `erase()`.,erase__key_type} + + @sa see @ref erase(IteratorType) -- removes the element at a given position + @sa see @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa see @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + size_type erase(const typename object_t::key_type& key) + { + // this erase only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + return m_value.object->erase(key); + } + + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()), *this)); + } + + /*! + @brief remove element from a JSON array given an index + + Removes element from a JSON array at the index @a idx. + + @param[in] idx index of the element to remove + + @throw type_error.307 when called on a type other than JSON object; + example: `"cannot use erase() with null"` + @throw out_of_range.401 when `idx >= size()`; example: `"array index 17 + is out of range"` + + @complexity Linear in distance between @a idx and the end of the container. + + @liveexample{The example shows the effect of `erase()`.,erase__size_type} + + @sa see @ref erase(IteratorType) -- removes the element at a given position + @sa see @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa see @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + + @since version 1.0.0 + */ + void erase(const size_type idx) + { + // this erase only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + if (JSON_HEDLEY_UNLIKELY(idx >= size())) + { + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range", *this)); + } + + m_value.array->erase(m_value.array->begin() + static_cast(idx)); + } + else + { + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()), *this)); + } + } + + /// @} + + + //////////// + // lookup // + //////////// + + /// @name lookup + /// @{ + + /*! + @brief find an element in a JSON object + + Finds an element in a JSON object with key equivalent to @a key. If the + element is not found or the JSON value is not an object, end() is + returned. + + @note This method always returns @ref end() when executed on a JSON type + that is not an object. + + @param[in] key key value of the element to search for. + + @return Iterator to an element with key equivalent to @a key. If no such + element is found or the JSON value is not an object, past-the-end (see + @ref end()) iterator is returned. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The example shows how `find()` is used.,find__key_type} + + @sa see @ref contains(KeyT&&) const -- checks whether a key exists + + @since version 1.0.0 + */ + template + iterator find(KeyT&& key) + { + auto result = end(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(std::forward(key)); + } + + return result; + } + + /*! + @brief find an element in a JSON object + @copydoc find(KeyT&&) + */ + template + const_iterator find(KeyT&& key) const + { + auto result = cend(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(std::forward(key)); + } + + return result; + } + + /*! + @brief returns the number of occurrences of a key in a JSON object + + Returns the number of elements with key @a key. If ObjectType is the + default `std::map` type, the return value will always be `0` (@a key was + not found) or `1` (@a key was found). + + @note This method always returns `0` when executed on a JSON type that is + not an object. + + @param[in] key key value of the element to count + + @return Number of elements with key @a key. If the JSON value is not an + object, the return value will be `0`. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The example shows how `count()` is used.,count} + + @since version 1.0.0 + */ + template + size_type count(KeyT&& key) const + { + // return 0 for all nonobject types + return is_object() ? m_value.object->count(std::forward(key)) : 0; + } + + /*! + @brief check the existence of an element in a JSON object + + Check whether an element exists in a JSON object with key equivalent to + @a key. If the element is not found or the JSON value is not an object, + false is returned. + + @note This method always returns false when executed on a JSON type + that is not an object. + + @param[in] key key value to check its existence. + + @return true if an element with specified @a key exists. If no such + element with such key is found or the JSON value is not an object, + false is returned. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The following code shows an example for `contains()`.,contains} + + @sa see @ref find(KeyT&&) -- returns an iterator to an object element + @sa see @ref contains(const json_pointer&) const -- checks the existence for a JSON pointer + + @since version 3.6.0 + */ + template < typename KeyT, typename std::enable_if < + !std::is_same::type, json_pointer>::value, int >::type = 0 > + bool contains(KeyT && key) const + { + return is_object() && m_value.object->find(std::forward(key)) != m_value.object->end(); + } + + /*! + @brief check the existence of an element in a JSON object given a JSON pointer + + Check whether the given JSON pointer @a ptr can be resolved in the current + JSON value. + + @note This method can be executed on any JSON value type. + + @param[in] ptr JSON pointer to check its existence. + + @return true if the JSON pointer can be resolved to a stored value, false + otherwise. + + @post If `j.contains(ptr)` returns true, it is safe to call `j[ptr]`. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The following code shows an example for `contains()`.,contains_json_pointer} + + @sa see @ref contains(KeyT &&) const -- checks the existence of a key + + @since version 3.7.0 + */ + bool contains(const json_pointer& ptr) const + { + return ptr.contains(this); + } + + /// @} + + + /////////////// + // iterators // + /////////////// + + /// @name iterators + /// @{ + + /*! + @brief returns an iterator to the first element + + Returns an iterator to the first element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return iterator to the first element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is constant. + + @liveexample{The following code shows an example for `begin()`.,begin} + + @sa see @ref cbegin() -- returns a const iterator to the beginning + @sa see @ref end() -- returns an iterator to the end + @sa see @ref cend() -- returns a const iterator to the end + + @since version 1.0.0 + */ + iterator begin() noexcept + { + iterator result(this); + result.set_begin(); + return result; + } + + /*! + @copydoc basic_json::cbegin() + */ + const_iterator begin() const noexcept + { + return cbegin(); + } + + /*! + @brief returns a const iterator to the first element + + Returns a const iterator to the first element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return const iterator to the first element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).begin()`. + + @liveexample{The following code shows an example for `cbegin()`.,cbegin} + + @sa see @ref begin() -- returns an iterator to the beginning + @sa see @ref end() -- returns an iterator to the end + @sa see @ref cend() -- returns a const iterator to the end + + @since version 1.0.0 + */ + const_iterator cbegin() const noexcept + { + const_iterator result(this); + result.set_begin(); + return result; + } + + /*! + @brief returns an iterator to one past the last element + + Returns an iterator to one past the last element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return iterator one past the last element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is constant. + + @liveexample{The following code shows an example for `end()`.,end} + + @sa see @ref cend() -- returns a const iterator to the end + @sa see @ref begin() -- returns an iterator to the beginning + @sa see @ref cbegin() -- returns a const iterator to the beginning + + @since version 1.0.0 + */ + iterator end() noexcept + { + iterator result(this); + result.set_end(); + return result; + } + + /*! + @copydoc basic_json::cend() + */ + const_iterator end() const noexcept + { + return cend(); + } + + /*! + @brief returns a const iterator to one past the last element + + Returns a const iterator to one past the last element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return const iterator one past the last element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).end()`. + + @liveexample{The following code shows an example for `cend()`.,cend} + + @sa see @ref end() -- returns an iterator to the end + @sa see @ref begin() -- returns an iterator to the beginning + @sa see @ref cbegin() -- returns a const iterator to the beginning + + @since version 1.0.0 + */ + const_iterator cend() const noexcept + { + const_iterator result(this); + result.set_end(); + return result; + } + + /*! + @brief returns an iterator to the reverse-beginning + + Returns an iterator to the reverse-beginning; that is, the last element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(end())`. + + @liveexample{The following code shows an example for `rbegin()`.,rbegin} + + @sa see @ref crbegin() -- returns a const reverse iterator to the beginning + @sa see @ref rend() -- returns a reverse iterator to the end + @sa see @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + reverse_iterator rbegin() noexcept + { + return reverse_iterator(end()); + } + + /*! + @copydoc basic_json::crbegin() + */ + const_reverse_iterator rbegin() const noexcept + { + return crbegin(); + } + + /*! + @brief returns an iterator to the reverse-end + + Returns an iterator to the reverse-end; that is, one before the first + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(begin())`. + + @liveexample{The following code shows an example for `rend()`.,rend} + + @sa see @ref crend() -- returns a const reverse iterator to the end + @sa see @ref rbegin() -- returns a reverse iterator to the beginning + @sa see @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + reverse_iterator rend() noexcept + { + return reverse_iterator(begin()); + } + + /*! + @copydoc basic_json::crend() + */ + const_reverse_iterator rend() const noexcept + { + return crend(); + } + + /*! + @brief returns a const reverse iterator to the last element + + Returns a const iterator to the reverse-beginning; that is, the last + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).rbegin()`. + + @liveexample{The following code shows an example for `crbegin()`.,crbegin} + + @sa see @ref rbegin() -- returns a reverse iterator to the beginning + @sa see @ref rend() -- returns a reverse iterator to the end + @sa see @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + const_reverse_iterator crbegin() const noexcept + { + return const_reverse_iterator(cend()); + } + + /*! + @brief returns a const reverse iterator to one before the first + + Returns a const reverse iterator to the reverse-end; that is, one before + the first element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).rend()`. + + @liveexample{The following code shows an example for `crend()`.,crend} + + @sa see @ref rend() -- returns a reverse iterator to the end + @sa see @ref rbegin() -- returns a reverse iterator to the beginning + @sa see @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + const_reverse_iterator crend() const noexcept + { + return const_reverse_iterator(cbegin()); + } + + public: + /*! + @brief wrapper to access iterator member functions in range-based for + + This function allows to access @ref iterator::key() and @ref + iterator::value() during range-based for loops. In these loops, a + reference to the JSON values is returned, so there is no access to the + underlying iterator. + + For loop without iterator_wrapper: + + @code{cpp} + for (auto it = j_object.begin(); it != j_object.end(); ++it) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + Range-based for loop without iterator proxy: + + @code{cpp} + for (auto it : j_object) + { + // "it" is of type json::reference and has no key() member + std::cout << "value: " << it << '\n'; + } + @endcode + + Range-based for loop with iterator proxy: + + @code{cpp} + for (auto it : json::iterator_wrapper(j_object)) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + @note When iterating over an array, `key()` will return the index of the + element as string (see example). + + @param[in] ref reference to a JSON value + @return iteration proxy object wrapping @a ref with an interface to use in + range-based for loops + + @liveexample{The following code shows how the wrapper is used,iterator_wrapper} + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @note The name of this function is not yet final and may change in the + future. + + @deprecated This stream operator is deprecated and will be removed in + future 4.0.0 of the library. Please use @ref items() instead; + that is, replace `json::iterator_wrapper(j)` with `j.items()`. + */ + JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items()) + static iteration_proxy iterator_wrapper(reference ref) noexcept + { + return ref.items(); + } + + /*! + @copydoc iterator_wrapper(reference) + */ + JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items()) + static iteration_proxy iterator_wrapper(const_reference ref) noexcept + { + return ref.items(); + } + + /*! + @brief helper to access iterator member functions in range-based for + + This function allows to access @ref iterator::key() and @ref + iterator::value() during range-based for loops. In these loops, a + reference to the JSON values is returned, so there is no access to the + underlying iterator. + + For loop without `items()` function: + + @code{cpp} + for (auto it = j_object.begin(); it != j_object.end(); ++it) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + Range-based for loop without `items()` function: + + @code{cpp} + for (auto it : j_object) + { + // "it" is of type json::reference and has no key() member + std::cout << "value: " << it << '\n'; + } + @endcode + + Range-based for loop with `items()` function: + + @code{cpp} + for (auto& el : j_object.items()) + { + std::cout << "key: " << el.key() << ", value:" << el.value() << '\n'; + } + @endcode + + The `items()` function also allows to use + [structured bindings](https://en.cppreference.com/w/cpp/language/structured_binding) + (C++17): + + @code{cpp} + for (auto& [key, val] : j_object.items()) + { + std::cout << "key: " << key << ", value:" << val << '\n'; + } + @endcode + + @note When iterating over an array, `key()` will return the index of the + element as string (see example). For primitive types (e.g., numbers), + `key()` returns an empty string. + + @warning Using `items()` on temporary objects is dangerous. Make sure the + object's lifetime exeeds the iteration. See + for more + information. + + @return iteration proxy object wrapping @a ref with an interface to use in + range-based for loops + + @liveexample{The following code shows how the function is used.,items} + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 3.1.0, structured bindings support since 3.5.0. + */ + iteration_proxy items() noexcept + { + return iteration_proxy(*this); + } + + /*! + @copydoc items() + */ + iteration_proxy items() const noexcept + { + return iteration_proxy(*this); + } + + /// @} + + + ////////////// + // capacity // + ////////////// + + /// @name capacity + /// @{ + + /*! + @brief checks whether the container is empty. + + Checks if a JSON value has no elements (i.e. whether its @ref size is `0`). + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `true` + boolean | `false` + string | `false` + number | `false` + binary | `false` + object | result of function `object_t::empty()` + array | result of function `array_t::empty()` + + @liveexample{The following code uses `empty()` to check if a JSON + object contains any elements.,empty} + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `empty()` functions have constant + complexity. + + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @note This function does not return whether a string stored as JSON value + is empty - it returns whether the JSON container itself is empty which is + false in the case of a string. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is constant. + - Has the semantics of `begin() == end()`. + + @sa see @ref size() -- returns the number of elements + + @since version 1.0.0 + */ + bool empty() const noexcept + { + switch (m_type) + { + case value_t::null: + { + // null values are empty + return true; + } + + case value_t::array: + { + // delegate call to array_t::empty() + return m_value.array->empty(); + } + + case value_t::object: + { + // delegate call to object_t::empty() + return m_value.object->empty(); + } + + default: + { + // all other types are nonempty + return false; + } + } + } + + /*! + @brief returns the number of elements + + Returns the number of elements in a JSON value. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `0` + boolean | `1` + string | `1` + number | `1` + binary | `1` + object | result of function object_t::size() + array | result of function array_t::size() + + @liveexample{The following code calls `size()` on the different value + types.,size} + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their size() functions have constant + complexity. + + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @note This function does not return the length of a string stored as JSON + value - it returns the number of elements in the JSON value which is 1 in + the case of a string. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is constant. + - Has the semantics of `std::distance(begin(), end())`. + + @sa see @ref empty() -- checks whether the container is empty + @sa see @ref max_size() -- returns the maximal number of elements + + @since version 1.0.0 + */ + size_type size() const noexcept + { + switch (m_type) + { + case value_t::null: + { + // null values are empty + return 0; + } + + case value_t::array: + { + // delegate call to array_t::size() + return m_value.array->size(); + } + + case value_t::object: + { + // delegate call to object_t::size() + return m_value.object->size(); + } + + default: + { + // all other types have size 1 + return 1; + } + } + } + + /*! + @brief returns the maximum possible number of elements + + Returns the maximum number of elements a JSON value is able to hold due to + system or library implementation limitations, i.e. `std::distance(begin(), + end())` for the JSON value. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `0` (same as `size()`) + boolean | `1` (same as `size()`) + string | `1` (same as `size()`) + number | `1` (same as `size()`) + binary | `1` (same as `size()`) + object | result of function `object_t::max_size()` + array | result of function `array_t::max_size()` + + @liveexample{The following code calls `max_size()` on the different value + types. Note the output is implementation specific.,max_size} + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `max_size()` functions have constant + complexity. + + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @requirement This function helps `basic_json` satisfying the + [Container](https://en.cppreference.com/w/cpp/named_req/Container) + requirements: + - The complexity is constant. + - Has the semantics of returning `b.size()` where `b` is the largest + possible JSON value. + + @sa see @ref size() -- returns the number of elements + + @since version 1.0.0 + */ + size_type max_size() const noexcept + { + switch (m_type) + { + case value_t::array: + { + // delegate call to array_t::max_size() + return m_value.array->max_size(); + } + + case value_t::object: + { + // delegate call to object_t::max_size() + return m_value.object->max_size(); + } + + default: + { + // all other types have max_size() == size() + return size(); + } + } + } + + /// @} + + + /////////////// + // modifiers // + /////////////// + + /// @name modifiers + /// @{ + + /*! + @brief clears the contents + + Clears the content of a JSON value and resets it to the default value as + if @ref basic_json(value_t) would have been called with the current value + type from @ref type(): + + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + binary | An empty byte vector + object | `{}` + array | `[]` + + @post Has the same effect as calling + @code {.cpp} + *this = basic_json(type()); + @endcode + + @liveexample{The example below shows the effect of `clear()` to different + JSON types.,clear} + + @complexity Linear in the size of the JSON value. + + @iterators All iterators, pointers and references related to this container + are invalidated. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @sa see @ref basic_json(value_t) -- constructor that creates an object with the + same value than calling `clear()` + + @since version 1.0.0 + */ + 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::binary: + { + m_value.binary->clear(); + break; + } + + case value_t::array: + { + m_value.array->clear(); + break; + } + + case value_t::object: + { + m_value.object->clear(); + break; + } + + default: + break; + } + } + + /*! + @brief add an object to an array + + Appends the given element @a val to the end of the JSON value. If the + function is called on a JSON null value, an empty array is created before + appending @a val. + + @param[in] val the value to add to the JSON array + + @throw type_error.308 when called on a type other than JSON array or + null; example: `"cannot use push_back() with number"` + + @complexity Amortized constant. + + @liveexample{The example shows how `push_back()` and `+=` can be used to + add elements to a JSON array. Note how the `null` value was silently + converted to a JSON array.,push_back} + + @since version 1.0.0 + */ + void push_back(basic_json&& val) + { + // push_back only works for null objects or arrays + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) + { + JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()), *this)); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array (move semantics) + m_value.array->push_back(std::move(val)); + set_parent(m_value.array->back()); + // if val is moved from, basic_json move constructor marks it null so we do not call the destructor + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + reference operator+=(basic_json&& val) + { + push_back(std::move(val)); + return *this; + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + void push_back(const basic_json& val) + { + // push_back only works for null objects or arrays + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) + { + JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()), *this)); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array + m_value.array->push_back(val); + set_parent(m_value.array->back()); + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + reference operator+=(const basic_json& val) + { + push_back(val); + return *this; + } + + /*! + @brief add an object to an object + + Inserts the given element @a val to the JSON object. If the function is + called on a JSON null value, an empty object is created before inserting + @a val. + + @param[in] val the value to add to the JSON object + + @throw type_error.308 when called on a type other than JSON object or + null; example: `"cannot use push_back() with number"` + + @complexity Logarithmic in the size of the container, O(log(`size()`)). + + @liveexample{The example shows how `push_back()` and `+=` can be used to + add elements to a JSON object. Note how the `null` value was silently + converted to a JSON object.,push_back__object_t__value} + + @since version 1.0.0 + */ + void push_back(const typename object_t::value_type& val) + { + // push_back only works for null objects or objects + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) + { + JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()), *this)); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to object + auto res = m_value.object->insert(val); + set_parent(res.first->second); + } + + /*! + @brief add an object to an object + @copydoc push_back(const typename object_t::value_type&) + */ + reference operator+=(const typename object_t::value_type& val) + { + push_back(val); + return *this; + } + + /*! + @brief add an object to an object + + This function allows to use `push_back` with an initializer list. In case + + 1. the current value is an object, + 2. the initializer list @a init contains only two elements, and + 3. the first element of @a init is a string, + + @a init is converted into an object element and added using + @ref push_back(const typename object_t::value_type&). Otherwise, @a init + is converted to a JSON value and added using @ref push_back(basic_json&&). + + @param[in] init an initializer list + + @complexity Linear in the size of the initializer list @a init. + + @note This function is required to resolve an ambiguous overload error, + because pairs like `{"key", "value"}` can be both interpreted as + `object_t::value_type` or `std::initializer_list`, see + https://github.com/nlohmann/json/issues/235 for more information. + + @liveexample{The example shows how initializer lists are treated as + objects when possible.,push_back__initializer_list} + */ + void push_back(initializer_list_t init) + { + if (is_object() && init.size() == 2 && (*init.begin())->is_string()) + { + basic_json&& key = init.begin()->moved_or_copied(); + push_back(typename object_t::value_type( + std::move(key.get_ref()), (init.begin() + 1)->moved_or_copied())); + } + else + { + push_back(basic_json(init)); + } + } + + /*! + @brief add an object to an object + @copydoc push_back(initializer_list_t) + */ + reference operator+=(initializer_list_t init) + { + push_back(init); + return *this; + } + + /*! + @brief add an object to an array + + Creates a JSON value from the passed parameters @a args to the end of the + JSON value. If the function is called on a JSON null value, an empty array + is created before appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @return reference to the inserted element + + @throw type_error.311 when called on a type other than JSON array or + null; example: `"cannot use emplace_back() with number"` + + @complexity Amortized constant. + + @liveexample{The example shows how `push_back()` can be used to add + elements to a JSON array. Note how the `null` value was silently converted + to a JSON array.,emplace_back} + + @since version 2.0.8, returns reference since 3.7.0 + */ + template + reference emplace_back(Args&& ... args) + { + // emplace_back only works for null objects or arrays + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) + { + JSON_THROW(type_error::create(311, "cannot use emplace_back() with " + std::string(type_name()), *this)); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array (perfect forwarding) +#ifdef JSON_HAS_CPP_17 + return set_parent(m_value.array->emplace_back(std::forward(args)...)); +#else + m_value.array->emplace_back(std::forward(args)...); + return set_parent(m_value.array->back()); +#endif + } + + /*! + @brief add an object to an object if key does not exist + + Inserts a new element into a JSON object constructed in-place with the + given @a args if there is no element with the key in the container. If the + function is called on a JSON null value, an empty object is created before + appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @return a pair consisting of an iterator to the inserted element, or the + already-existing element if no insertion happened, and a bool + denoting whether the insertion took place. + + @throw type_error.311 when called on a type other than JSON object or + null; example: `"cannot use emplace() with number"` + + @complexity Logarithmic in the size of the container, O(log(`size()`)). + + @liveexample{The example shows how `emplace()` can be used to add elements + to a JSON object. Note how the `null` value was silently converted to a + JSON object. Further note how no value is added if there was already one + value stored with the same key.,emplace} + + @since version 2.0.8 + */ + template + std::pair emplace(Args&& ... args) + { + // emplace only works for null objects or arrays + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) + { + JSON_THROW(type_error::create(311, "cannot use emplace() with " + std::string(type_name()), *this)); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to array (perfect forwarding) + auto res = m_value.object->emplace(std::forward(args)...); + set_parent(res.first->second); + + // create result iterator and set iterator to the result of emplace + auto it = begin(); + it.m_it.object_iterator = res.first; + + // return pair of iterator and boolean + return {it, res.second}; + } + + /// Helper for insertion of an iterator + /// @note: This uses std::distance to support GCC 4.8, + /// see https://github.com/nlohmann/json/pull/1257 + template + iterator insert_iterator(const_iterator pos, Args&& ... args) + { + iterator result(this); + JSON_ASSERT(m_value.array != nullptr); + + auto insert_pos = std::distance(m_value.array->begin(), pos.m_it.array_iterator); + m_value.array->insert(pos.m_it.array_iterator, std::forward(args)...); + result.m_it.array_iterator = m_value.array->begin() + insert_pos; + + // This could have been written as: + // result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); + // but the return value of insert is missing in GCC 4.8, so it is written this way instead. + + return result; + } + + /*! + @brief inserts element + + Inserts element @a val before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] val element to insert + @return iterator pointing to the inserted @a val. + + @throw type_error.309 if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + + @complexity Constant plus linear in the distance between @a pos and end of + the container. + + @liveexample{The example shows how `insert()` is used.,insert} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, const basic_json& val) + { + // insert only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", *this)); + } + + // insert to array and return iterator + return set_parents(insert_iterator(pos, val), static_cast(1)); + } + + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()), *this)); + } + + /*! + @brief inserts element + @copydoc insert(const_iterator, const basic_json&) + */ + iterator insert(const_iterator pos, basic_json&& val) + { + return insert(pos, val); + } + + /*! + @brief inserts elements + + Inserts @a cnt copies of @a val before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] cnt number of copies of @a val to insert + @param[in] val element to insert + @return iterator pointing to the first element inserted, or @a pos if + `cnt==0` + + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + + @complexity Linear in @a cnt plus linear in the distance between @a pos + and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__count} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, size_type cnt, const basic_json& val) + { + // insert only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", *this)); + } + + // insert to array and return iterator + return set_parents(insert_iterator(pos, cnt, val), static_cast(cnt)); + } + + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()), *this)); + } + + /*! + @brief inserts elements + + Inserts elements from range `[first, last)` before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + @throw invalid_iterator.211 if @a first or @a last are iterators into + container for which insert is called; example: `"passed iterators may not + belong to container"` + + @return iterator pointing to the first element inserted, or @a pos if + `first==last` + + @complexity Linear in `std::distance(first, last)` plus linear in the + distance between @a pos and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__range} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, const_iterator first, const_iterator last) + { + // insert only works for arrays + if (JSON_HEDLEY_UNLIKELY(!is_array())) + { + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()), *this)); + } + + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", *this)); + } + + // check if range iterators belong to the same JSON object + if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit", *this)); + } + + if (JSON_HEDLEY_UNLIKELY(first.m_object == this)) + { + JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container", *this)); + } + + // insert to array and return iterator + return set_parents(insert_iterator(pos, first.m_it.array_iterator, last.m_it.array_iterator), std::distance(first, last)); + } + + /*! + @brief inserts elements + + Inserts elements from initializer list @a ilist before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] ilist initializer list to insert the values from + + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + + @return iterator pointing to the first element inserted, or @a pos if + `ilist` is empty + + @complexity Linear in `ilist.size()` plus linear in the distance between + @a pos and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__ilist} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, initializer_list_t ilist) + { + // insert only works for arrays + if (JSON_HEDLEY_UNLIKELY(!is_array())) + { + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()), *this)); + } + + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", *this)); + } + + // insert to array and return iterator + return set_parents(insert_iterator(pos, ilist.begin(), ilist.end()), static_cast(ilist.size())); + } + + /*! + @brief inserts elements + + Inserts elements from range `[first, last)`. + + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.309 if called on JSON values other than objects; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if iterator @a first or @a last does does not + point to an object; example: `"iterators first and last must point to + objects"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + + @complexity Logarithmic: `O(N*log(size() + N))`, where `N` is the number + of elements to insert. + + @liveexample{The example shows how `insert()` is used.,insert__range_object} + + @since version 3.0.0 + */ + void insert(const_iterator first, const_iterator last) + { + // insert only works for objects + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()), *this)); + } + + // check if range iterators belong to the same JSON object + if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit", *this)); + } + + // passed iterators must belong to objects + if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) + { + JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects", *this)); + } + + m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); + } + + /*! + @brief updates a JSON object from another object, overwriting existing keys + + Inserts all values from JSON object @a j and overwrites existing keys. + + @param[in] j JSON object to read values from + + @throw type_error.312 if called on JSON values other than objects; example: + `"cannot use update() with string"` + + @complexity O(N*log(size() + N)), where N is the number of elements to + insert. + + @liveexample{The example shows how `update()` is used.,update} + + @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update + + @since version 3.0.0 + */ + void update(const_reference j) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()), *this)); + } + if (JSON_HEDLEY_UNLIKELY(!j.is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(j.type_name()), *this)); + } + + for (auto it = j.cbegin(); it != j.cend(); ++it) + { + m_value.object->operator[](it.key()) = it.value(); + } + } + + /*! + @brief updates a JSON object from another object, overwriting existing keys + + Inserts all values from from range `[first, last)` and overwrites existing + keys. + + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.312 if called on JSON values other than objects; example: + `"cannot use update() with string"` + @throw invalid_iterator.202 if iterator @a first or @a last does does not + point to an object; example: `"iterators first and last must point to + objects"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + + @complexity O(N*log(size() + N)), where N is the number of elements to + insert. + + @liveexample{The example shows how `update()` is used__range.,update} + + @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update + + @since version 3.0.0 + */ + void update(const_iterator first, const_iterator last) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()), *this)); + } + + // check if range iterators belong to the same JSON object + if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit", *this)); + } + + // passed iterators must belong to objects + if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object() + || !last.m_object->is_object())) + { + JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects", *this)); + } + + for (auto it = first; it != last; ++it) + { + m_value.object->operator[](it.key()) = it.value(); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of the JSON value with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other JSON value to exchange the contents with + + @complexity Constant. + + @liveexample{The example below shows how JSON values can be swapped with + `swap()`.,swap__reference} + + @since version 1.0.0 + */ + void swap(reference other) noexcept ( + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_assignable::value&& + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_assignable::value + ) + { + std::swap(m_type, other.m_type); + std::swap(m_value, other.m_value); + + set_parents(); + other.set_parents(); + assert_invariant(); + } + + /*! + @brief exchanges the values + + Exchanges the contents of the JSON value from @a left with those of @a right. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. implemented as a friend function callable via ADL. + + @param[in,out] left JSON value to exchange the contents with + @param[in,out] right JSON value to exchange the contents with + + @complexity Constant. + + @liveexample{The example below shows how JSON values can be swapped with + `swap()`.,swap__reference} + + @since version 1.0.0 + */ + friend void swap(reference left, reference right) noexcept ( + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_assignable::value&& + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_assignable::value + ) + { + left.swap(right); + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON array with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other array to exchange the contents with + + @throw type_error.310 when JSON value is not an array; example: `"cannot + use swap() with string"` + + @complexity Constant. + + @liveexample{The example below shows how arrays can be swapped with + `swap()`.,swap__array_t} + + @since version 1.0.0 + */ + void swap(array_t& other) // NOLINT(bugprone-exception-escape) + { + // swap only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + std::swap(*(m_value.array), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()), *this)); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON object with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other object to exchange the contents with + + @throw type_error.310 when JSON value is not an object; example: + `"cannot use swap() with string"` + + @complexity Constant. + + @liveexample{The example below shows how objects can be swapped with + `swap()`.,swap__object_t} + + @since version 1.0.0 + */ + void swap(object_t& other) // NOLINT(bugprone-exception-escape) + { + // swap only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + std::swap(*(m_value.object), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()), *this)); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON string with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other string to exchange the contents with + + @throw type_error.310 when JSON value is not a string; example: `"cannot + use swap() with boolean"` + + @complexity Constant. + + @liveexample{The example below shows how strings can be swapped with + `swap()`.,swap__string_t} + + @since version 1.0.0 + */ + void swap(string_t& other) // NOLINT(bugprone-exception-escape) + { + // swap only works for strings + if (JSON_HEDLEY_LIKELY(is_string())) + { + std::swap(*(m_value.string), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()), *this)); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON string with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other binary to exchange the contents with + + @throw type_error.310 when JSON value is not a string; example: `"cannot + use swap() with boolean"` + + @complexity Constant. + + @liveexample{The example below shows how strings can be swapped with + `swap()`.,swap__binary_t} + + @since version 3.8.0 + */ + void swap(binary_t& other) // NOLINT(bugprone-exception-escape) + { + // swap only works for strings + if (JSON_HEDLEY_LIKELY(is_binary())) + { + std::swap(*(m_value.binary), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()), *this)); + } + } + + /// @copydoc swap(binary_t&) + void swap(typename binary_t::container_type& other) // NOLINT(bugprone-exception-escape) + { + // swap only works for strings + if (JSON_HEDLEY_LIKELY(is_binary())) + { + std::swap(*(m_value.binary), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()), *this)); + } + } + + /// @} + + public: + ////////////////////////////////////////// + // lexicographical comparison operators // + ////////////////////////////////////////// + + /// @name lexicographical comparison operators + /// @{ + + /*! + @brief comparison: equal + + Compares two JSON values for equality according to the following rules: + - Two JSON values are equal if (1) they are from the same type and (2) + their stored values are the same according to their respective + `operator==`. + - Integer and floating-point numbers are automatically converted before + comparison. Note that two NaN values are always treated as unequal. + - Two JSON null values are equal. + + @note Floating-point inside JSON values numbers are compared with + `json::number_float_t::operator==` which is `double::operator==` by + default. To compare floating-point while respecting an epsilon, an alternative + [comparison function](https://github.com/mariokonrad/marnav/blob/master/include/marnav/math/floatingpoint.hpp#L34-#L39) + could be used, for instance + @code {.cpp} + template::value, T>::type> + inline bool is_same(T a, T b, T epsilon = std::numeric_limits::epsilon()) noexcept + { + return std::abs(a - b) <= epsilon; + } + @endcode + Or you can self-defined operator equal function like this: + @code {.cpp} + bool my_equal(const_reference lhs, const_reference rhs) { + const auto lhs_type lhs.type(); + const auto rhs_type rhs.type(); + if (lhs_type == rhs_type) { + switch(lhs_type) + // self_defined case + case value_t::number_float: + return std::abs(lhs - rhs) <= std::numeric_limits::epsilon(); + // other cases remain the same with the original + ... + } + ... + } + @endcode + + @note NaN values never compare equal to themselves or to other NaN values. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are equal + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__equal} + + @since version 1.0.0 + */ + 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; + + case value_t::binary: + return *lhs.m_value.binary == *rhs.m_value.binary; + + default: + return false; + } + } + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_integer) == rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) + { + return lhs.m_value.number_float == static_cast(rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_float == static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) + { + return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_integer; + } + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_integer == static_cast(rhs.m_value.number_unsigned); + } + + return false; + } + + /*! + @brief comparison: equal + @copydoc operator==(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator==(const_reference lhs, ScalarType rhs) noexcept + { + return lhs == basic_json(rhs); + } + + /*! + @brief comparison: equal + @copydoc operator==(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator==(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) == rhs; + } + + /*! + @brief comparison: not equal + + Compares two JSON values for inequality by calculating `not (lhs == rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are not equal + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__notequal} + + @since version 1.0.0 + */ + friend bool operator!=(const_reference lhs, const_reference rhs) noexcept + { + return !(lhs == rhs); + } + + /*! + @brief comparison: not equal + @copydoc operator!=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept + { + return lhs != basic_json(rhs); + } + + /*! + @brief comparison: not equal + @copydoc operator!=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) != rhs; + } + + /*! + @brief comparison: less than + + Compares whether one JSON value @a lhs is less than another JSON value @a + rhs according to the following rules: + - If @a lhs and @a rhs have the same type, the values are compared using + the default `<` operator. + - Integer and floating-point numbers are automatically converted before + comparison + - In case @a lhs and @a rhs have different types, the values are ignored + and the order of the types is considered, see + @ref operator<(const value_t, const value_t). + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is less than @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__less} + + @since version 1.0.0 + */ + 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: + // note parentheses are necessary, see + // https://github.com/nlohmann/json/issues/1530 + 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); + + case value_t::binary: + return (*lhs.m_value.binary) < (*rhs.m_value.binary); + + default: + return false; + } + } + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_integer) < rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) + { + return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_float < static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_integer < static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; + } + + // We only reach this line if we cannot compare values. In that case, + // we compare types. Note we have to call the operator explicitly, + // because MSVC has problems otherwise. + return operator<(lhs_type, rhs_type); + } + + /*! + @brief comparison: less than + @copydoc operator<(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<(const_reference lhs, ScalarType rhs) noexcept + { + return lhs < basic_json(rhs); + } + + /*! + @brief comparison: less than + @copydoc operator<(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) < rhs; + } + + /*! + @brief comparison: less than or equal + + Compares whether one JSON value @a lhs is less than or equal to another + JSON value by calculating `not (rhs < lhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is less than or equal to @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__greater} + + @since version 1.0.0 + */ + friend bool operator<=(const_reference lhs, const_reference rhs) noexcept + { + return !(rhs < lhs); + } + + /*! + @brief comparison: less than or equal + @copydoc operator<=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept + { + return lhs <= basic_json(rhs); + } + + /*! + @brief comparison: less than or equal + @copydoc operator<=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) <= rhs; + } + + /*! + @brief comparison: greater than + + Compares whether one JSON value @a lhs is greater than another + JSON value by calculating `not (lhs <= rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is greater than to @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__lessequal} + + @since version 1.0.0 + */ + friend bool operator>(const_reference lhs, const_reference rhs) noexcept + { + return !(lhs <= rhs); + } + + /*! + @brief comparison: greater than + @copydoc operator>(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>(const_reference lhs, ScalarType rhs) noexcept + { + return lhs > basic_json(rhs); + } + + /*! + @brief comparison: greater than + @copydoc operator>(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) > rhs; + } + + /*! + @brief comparison: greater than or equal + + Compares whether one JSON value @a lhs is greater than or equal to another + JSON value by calculating `not (lhs < rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is greater than or equal to @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__greaterequal} + + @since version 1.0.0 + */ + friend bool operator>=(const_reference lhs, const_reference rhs) noexcept + { + return !(lhs < rhs); + } + + /*! + @brief comparison: greater than or equal + @copydoc operator>=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept + { + return lhs >= basic_json(rhs); + } + + /*! + @brief comparison: greater than or equal + @copydoc operator>=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) >= rhs; + } + + /// @} + + /////////////////// + // serialization // + /////////////////// + + /// @name serialization + /// @{ + + /*! + @brief serialize to stream + + Serialize the given JSON value @a j to the output stream @a o. The JSON + value will be serialized using the @ref dump member function. + + - The indentation of the output can be controlled with the member variable + `width` of the output stream @a o. For instance, using the manipulator + `std::setw(4)` on @a o sets the indentation level to `4` and the + serialization result is the same as calling `dump(4)`. + + - The indentation character can be controlled with the member variable + `fill` of the output stream @a o. For instance, the manipulator + `std::setfill('\\t')` sets indentation to use a tab character rather than + the default space character. + + @param[in,out] o stream to serialize to + @param[in] j JSON value to serialize + + @return the stream @a o + + @throw type_error.316 if a string stored inside the JSON value is not + UTF-8 encoded + + @complexity Linear. + + @liveexample{The example below shows the serialization with different + parameters to `width` to adjust the indentation level.,operator_serialize} + + @since version 1.0.0; indentation character added in version 3.0.0 + */ + friend std::ostream& operator<<(std::ostream& o, const basic_json& j) + { + // read width member and use it as indentation parameter if nonzero + const bool pretty_print = o.width() > 0; + const auto indentation = pretty_print ? o.width() : 0; + + // reset width to 0 for subsequent calls to this stream + o.width(0); + + // do the actual serialization + serializer s(detail::output_adapter(o), o.fill()); + s.dump(j, pretty_print, false, static_cast(indentation)); + return o; + } + + /*! + @brief serialize to stream + @deprecated This stream operator is deprecated and will be removed in + future 4.0.0 of the library. Please use + @ref operator<<(std::ostream&, const basic_json&) + instead; that is, replace calls like `j >> o;` with `o << j;`. + @since version 1.0.0; deprecated since version 3.0.0 + */ + JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator<<(std::ostream&, const basic_json&)) + friend std::ostream& operator>>(const basic_json& j, std::ostream& o) + { + return o << j; + } + + /// @} + + + ///////////////////// + // deserialization // + ///////////////////// + + /// @name deserialization + /// @{ + + /*! + @brief deserialize from a compatible input + + @tparam InputType A compatible input, for instance + - an std::istream object + - a FILE pointer + - a C-style array of characters + - a pointer to a null-terminated string of single byte characters + - an object obj for which begin(obj) and end(obj) produces a valid pair of + iterators. + + @param[in] i input to read from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + @param[in] allow_exceptions whether to throw exceptions in case of a + parse error (optional, true by default) + @param[in] ignore_comments whether comments should be ignored and treated + like whitespace (true) or yield a parse error (true); (optional, false by + default) + + @return deserialized JSON value; in case of a parse error and + @a allow_exceptions set to `false`, the return value will be + value_t::discarded. + + @throw parse_error.101 if a parse error occurs; example: `""unexpected end + of input; expected string literal""` + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb or reading from the input @a i has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function reading + from an array.,parse__array__parser_callback_t} + + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__string__parser_callback_t} + + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__istream__parser_callback_t} + + @liveexample{The example below demonstrates the `parse()` function reading + from a contiguous container.,parse__contiguouscontainer__parser_callback_t} + + @since version 2.0.3 (contiguous containers); version 3.9.0 allowed to + ignore comments. + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json parse(InputType&& i, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true, + const bool ignore_comments = false) + { + basic_json result; + parser(detail::input_adapter(std::forward(i)), cb, allow_exceptions, ignore_comments).parse(true, result); + return result; + } + + /*! + @brief deserialize from a pair of character iterators + + The value_type of the iterator must be a integral type with size of 1, 2 or + 4 bytes, which will be interpreted respectively as UTF-8, UTF-16 and UTF-32. + + @param[in] first iterator to start of character range + @param[in] last iterator to end of character range + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + @param[in] allow_exceptions whether to throw exceptions in case of a + parse error (optional, true by default) + @param[in] ignore_comments whether comments should be ignored and treated + like whitespace (true) or yield a parse error (true); (optional, false by + default) + + @return deserialized JSON value; in case of a parse error and + @a allow_exceptions set to `false`, the return value will be + value_t::discarded. + + @throw parse_error.101 if a parse error occurs; example: `""unexpected end + of input; expected string literal""` + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json parse(IteratorType first, + IteratorType last, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true, + const bool ignore_comments = false) + { + basic_json result; + parser(detail::input_adapter(std::move(first), std::move(last)), cb, allow_exceptions, ignore_comments).parse(true, result); + return result; + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len)) + static basic_json parse(detail::span_input_adapter&& i, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true, + const bool ignore_comments = false) + { + basic_json result; + parser(i.get(), cb, allow_exceptions, ignore_comments).parse(true, result); + return result; + } + + /*! + @brief check if the input is valid JSON + + Unlike the @ref parse(InputType&&, const parser_callback_t,const bool) + function, this function neither throws an exception in case of invalid JSON + input (i.e., a parse error) nor creates diagnostic information. + + @tparam InputType A compatible input, for instance + - an std::istream object + - a FILE pointer + - a C-style array of characters + - a pointer to a null-terminated string of single byte characters + - an object obj for which begin(obj) and end(obj) produces a valid pair of + iterators. + + @param[in] i input to read from + @param[in] ignore_comments whether comments should be ignored and treated + like whitespace (true) or yield a parse error (true); (optional, false by + default) + + @return Whether the input read from @a i is valid JSON. + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `accept()` function reading + from a string.,accept__string} + */ + template + static bool accept(InputType&& i, + const bool ignore_comments = false) + { + return parser(detail::input_adapter(std::forward(i)), nullptr, false, ignore_comments).accept(true); + } + + template + static bool accept(IteratorType first, IteratorType last, + const bool ignore_comments = false) + { + return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments).accept(true); + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, accept(ptr, ptr + len)) + static bool accept(detail::span_input_adapter&& i, + const bool ignore_comments = false) + { + return parser(i.get(), nullptr, false, ignore_comments).accept(true); + } + + /*! + @brief generate SAX events + + The SAX event lister must follow the interface of @ref json_sax. + + This function reads from a compatible input. Examples are: + - an std::istream object + - a FILE pointer + - a C-style array of characters + - a pointer to a null-terminated string of single byte characters + - an object obj for which begin(obj) and end(obj) produces a valid pair of + iterators. + + @param[in] i input to read from + @param[in,out] sax SAX event listener + @param[in] format the format to parse (JSON, CBOR, MessagePack, or UBJSON) + @param[in] strict whether the input has to be consumed completely + @param[in] ignore_comments whether comments should be ignored and treated + like whitespace (true) or yield a parse error (true); (optional, false by + default); only applies to the JSON file format. + + @return return value of the last processed SAX event + + @throw parse_error.101 if a parse error occurs; example: `""unexpected end + of input; expected string literal""` + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the SAX consumer @a sax has + a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `sax_parse()` function + reading from string and processing the events with a user-defined SAX + event consumer.,sax_parse} + + @since version 3.2.0 + */ + template + JSON_HEDLEY_NON_NULL(2) + static bool sax_parse(InputType&& i, SAX* sax, + input_format_t format = input_format_t::json, + const bool strict = true, + const bool ignore_comments = false) + { + auto ia = detail::input_adapter(std::forward(i)); + return format == input_format_t::json + ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) + : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); + } + + template + JSON_HEDLEY_NON_NULL(3) + static bool sax_parse(IteratorType first, IteratorType last, SAX* sax, + input_format_t format = input_format_t::json, + const bool strict = true, + const bool ignore_comments = false) + { + auto ia = detail::input_adapter(std::move(first), std::move(last)); + return format == input_format_t::json + ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) + : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); + } + + template + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, sax_parse(ptr, ptr + len, ...)) + JSON_HEDLEY_NON_NULL(2) + static bool sax_parse(detail::span_input_adapter&& i, SAX* sax, + input_format_t format = input_format_t::json, + const bool strict = true, + const bool ignore_comments = false) + { + auto ia = i.get(); + return format == input_format_t::json + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); + } + + /*! + @brief deserialize from stream + @deprecated This stream operator is deprecated and will be removed in + version 4.0.0 of the library. Please use + @ref operator>>(std::istream&, basic_json&) + instead; that is, replace calls like `j << i;` with `i >> j;`. + @since version 1.0.0; deprecated since version 3.0.0 + */ + JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator>>(std::istream&, basic_json&)) + friend std::istream& operator<<(basic_json& j, std::istream& i) + { + return operator>>(i, j); + } + + /*! + @brief deserialize from stream + + Deserializes an input stream to a JSON value. + + @param[in,out] i input stream to read a serialized JSON value from + @param[in,out] j JSON value to write the deserialized input to + + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below shows how a JSON value is constructed by + reading a serialization from a stream.,operator_deserialize} + + @sa parse(std::istream&, const parser_callback_t) for a variant with a + parser callback function to filter values while parsing + + @since version 1.0.0 + */ + friend std::istream& operator>>(std::istream& i, basic_json& j) + { + parser(detail::input_adapter(i)).parse(false, j); + return i; + } + + /// @} + + /////////////////////////// + // convenience functions // + /////////////////////////// + + /*! + @brief return the type as string + + Returns the type name as string to be used in error messages - usually to + indicate that a function was called on a wrong JSON type. + + @return a string representation of a the @a m_type member: + Value type | return value + ----------- | ------------- + null | `"null"` + boolean | `"boolean"` + string | `"string"` + number | `"number"` (for all number types) + object | `"object"` + array | `"array"` + binary | `"binary"` + discarded | `"discarded"` + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @complexity Constant. + + @liveexample{The following code exemplifies `type_name()` for all JSON + types.,type_name} + + @sa see @ref type() -- return the type of the JSON value + @sa see @ref operator value_t() -- return the type of the JSON value (implicit) + + @since version 1.0.0, public since 2.1.0, `const char*` and `noexcept` + since 3.0.0 + */ + JSON_HEDLEY_RETURNS_NON_NULL + 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::binary: + return "binary"; + case value_t::discarded: + return "discarded"; + default: + return "number"; + } + } + } + + + JSON_PRIVATE_UNLESS_TESTED: + ////////////////////// + // member variables // + ////////////////////// + + /// the type of the current element + value_t m_type = value_t::null; + + /// the value of the current element + json_value m_value = {}; + +#if JSON_DIAGNOSTICS + /// a pointer to a parent value (for debugging purposes) + basic_json* m_parent = nullptr; +#endif + + ////////////////////////////////////////// + // binary serialization/deserialization // + ////////////////////////////////////////// + + /// @name binary serialization/deserialization support + /// @{ + + public: + /*! + @brief create a CBOR serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the CBOR (Concise + Binary Object Representation) serialization format. CBOR is a binary + serialization format which aims to be more compact than JSON itself, yet + more efficient to parse. + + The library uses the following mapping from JSON values types to + CBOR types according to the CBOR specification (RFC 7049): + + JSON value type | value/range | CBOR type | first byte + --------------- | ------------------------------------------ | ---------------------------------- | --------------- + null | `null` | Null | 0xF6 + boolean | `true` | True | 0xF5 + boolean | `false` | False | 0xF4 + number_integer | -9223372036854775808..-2147483649 | Negative integer (8 bytes follow) | 0x3B + number_integer | -2147483648..-32769 | Negative integer (4 bytes follow) | 0x3A + number_integer | -32768..-129 | Negative integer (2 bytes follow) | 0x39 + number_integer | -128..-25 | Negative integer (1 byte follow) | 0x38 + number_integer | -24..-1 | Negative integer | 0x20..0x37 + number_integer | 0..23 | Integer | 0x00..0x17 + number_integer | 24..255 | Unsigned integer (1 byte follow) | 0x18 + number_integer | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 + number_integer | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A + number_integer | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B + number_unsigned | 0..23 | Integer | 0x00..0x17 + number_unsigned | 24..255 | Unsigned integer (1 byte follow) | 0x18 + number_unsigned | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 + number_unsigned | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A + number_unsigned | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B + number_float | *any value representable by a float* | Single-Precision Float | 0xFA + number_float | *any value NOT representable by a float* | Double-Precision Float | 0xFB + string | *length*: 0..23 | UTF-8 string | 0x60..0x77 + string | *length*: 23..255 | UTF-8 string (1 byte follow) | 0x78 + string | *length*: 256..65535 | UTF-8 string (2 bytes follow) | 0x79 + string | *length*: 65536..4294967295 | UTF-8 string (4 bytes follow) | 0x7A + string | *length*: 4294967296..18446744073709551615 | UTF-8 string (8 bytes follow) | 0x7B + array | *size*: 0..23 | array | 0x80..0x97 + array | *size*: 23..255 | array (1 byte follow) | 0x98 + array | *size*: 256..65535 | array (2 bytes follow) | 0x99 + array | *size*: 65536..4294967295 | array (4 bytes follow) | 0x9A + array | *size*: 4294967296..18446744073709551615 | array (8 bytes follow) | 0x9B + object | *size*: 0..23 | map | 0xA0..0xB7 + object | *size*: 23..255 | map (1 byte follow) | 0xB8 + object | *size*: 256..65535 | map (2 bytes follow) | 0xB9 + object | *size*: 65536..4294967295 | map (4 bytes follow) | 0xBA + object | *size*: 4294967296..18446744073709551615 | map (8 bytes follow) | 0xBB + binary | *size*: 0..23 | byte string | 0x40..0x57 + binary | *size*: 23..255 | byte string (1 byte follow) | 0x58 + binary | *size*: 256..65535 | byte string (2 bytes follow) | 0x59 + binary | *size*: 65536..4294967295 | byte string (4 bytes follow) | 0x5A + binary | *size*: 4294967296..18446744073709551615 | byte string (8 bytes follow) | 0x5B + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a CBOR value. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @note The following CBOR types are not used in the conversion: + - UTF-8 strings terminated by "break" (0x7F) + - arrays terminated by "break" (0x9F) + - maps terminated by "break" (0xBF) + - byte strings terminated by "break" (0x5F) + - date/time (0xC0..0xC1) + - bignum (0xC2..0xC3) + - decimal fraction (0xC4) + - bigfloat (0xC5) + - expected conversions (0xD5..0xD7) + - simple values (0xE0..0xF3, 0xF8) + - undefined (0xF7) + - half-precision floats (0xF9) + - break (0xFF) + + @param[in] j JSON value to serialize + @return CBOR serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in CBOR format.,to_cbor} + + @sa http://cbor.io + @sa see @ref from_cbor(InputType&&, const bool, const bool, const cbor_tag_handler_t) for the + analogous deserialization + @sa see @ref to_msgpack(const basic_json&) for the related MessagePack format + @sa see @ref to_ubjson(const basic_json&, const bool, const bool) for the + related UBJSON format + + @since version 2.0.9; compact representation of floating-point numbers + since version 3.8.0 + */ + static std::vector to_cbor(const basic_json& j) + { + std::vector result; + to_cbor(j, result); + return result; + } + + static void to_cbor(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_cbor(j); + } + + static void to_cbor(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_cbor(j); + } + + /*! + @brief create a MessagePack serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the MessagePack + serialization format. MessagePack is a binary serialization format which + aims to be more compact than JSON itself, yet more efficient to parse. + + The library uses the following mapping from JSON values types to + MessagePack types according to the MessagePack specification: + + JSON value type | value/range | MessagePack type | first byte + --------------- | --------------------------------- | ---------------- | ---------- + null | `null` | nil | 0xC0 + boolean | `true` | true | 0xC3 + boolean | `false` | false | 0xC2 + number_integer | -9223372036854775808..-2147483649 | int64 | 0xD3 + number_integer | -2147483648..-32769 | int32 | 0xD2 + number_integer | -32768..-129 | int16 | 0xD1 + number_integer | -128..-33 | int8 | 0xD0 + number_integer | -32..-1 | negative fixint | 0xE0..0xFF + number_integer | 0..127 | positive fixint | 0x00..0x7F + number_integer | 128..255 | uint 8 | 0xCC + number_integer | 256..65535 | uint 16 | 0xCD + number_integer | 65536..4294967295 | uint 32 | 0xCE + number_integer | 4294967296..18446744073709551615 | uint 64 | 0xCF + number_unsigned | 0..127 | positive fixint | 0x00..0x7F + number_unsigned | 128..255 | uint 8 | 0xCC + number_unsigned | 256..65535 | uint 16 | 0xCD + number_unsigned | 65536..4294967295 | uint 32 | 0xCE + number_unsigned | 4294967296..18446744073709551615 | uint 64 | 0xCF + number_float | *any value representable by a float* | float 32 | 0xCA + number_float | *any value NOT representable by a float* | float 64 | 0xCB + string | *length*: 0..31 | fixstr | 0xA0..0xBF + string | *length*: 32..255 | str 8 | 0xD9 + string | *length*: 256..65535 | str 16 | 0xDA + string | *length*: 65536..4294967295 | str 32 | 0xDB + array | *size*: 0..15 | fixarray | 0x90..0x9F + array | *size*: 16..65535 | array 16 | 0xDC + array | *size*: 65536..4294967295 | array 32 | 0xDD + object | *size*: 0..15 | fix map | 0x80..0x8F + object | *size*: 16..65535 | map 16 | 0xDE + object | *size*: 65536..4294967295 | map 32 | 0xDF + binary | *size*: 0..255 | bin 8 | 0xC4 + binary | *size*: 256..65535 | bin 16 | 0xC5 + binary | *size*: 65536..4294967295 | bin 32 | 0xC6 + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a MessagePack value. + + @note The following values can **not** be converted to a MessagePack value: + - strings with more than 4294967295 bytes + - byte strings with more than 4294967295 bytes + - arrays with more than 4294967295 elements + - objects with more than 4294967295 elements + + @note Any MessagePack output created @ref to_msgpack can be successfully + parsed by @ref from_msgpack. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @param[in] j JSON value to serialize + @return MessagePack serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in MessagePack format.,to_msgpack} + + @sa http://msgpack.org + @sa see @ref from_msgpack for the analogous deserialization + @sa see @ref to_cbor(const basic_json& for the related CBOR format + @sa see @ref to_ubjson(const basic_json&, const bool, const bool) for the + related UBJSON format + + @since version 2.0.9 + */ + static std::vector to_msgpack(const basic_json& j) + { + std::vector result; + to_msgpack(j, result); + return result; + } + + static void to_msgpack(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_msgpack(j); + } + + static void to_msgpack(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_msgpack(j); + } + + /*! + @brief create a UBJSON serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the UBJSON + (Universal Binary JSON) serialization format. UBJSON aims to be more compact + than JSON itself, yet more efficient to parse. + + The library uses the following mapping from JSON values types to + UBJSON types according to the UBJSON specification: + + JSON value type | value/range | UBJSON type | marker + --------------- | --------------------------------- | ----------- | ------ + null | `null` | null | `Z` + boolean | `true` | true | `T` + boolean | `false` | false | `F` + number_integer | -9223372036854775808..-2147483649 | int64 | `L` + number_integer | -2147483648..-32769 | int32 | `l` + number_integer | -32768..-129 | int16 | `I` + number_integer | -128..127 | int8 | `i` + number_integer | 128..255 | uint8 | `U` + number_integer | 256..32767 | int16 | `I` + number_integer | 32768..2147483647 | int32 | `l` + number_integer | 2147483648..9223372036854775807 | int64 | `L` + number_unsigned | 0..127 | int8 | `i` + number_unsigned | 128..255 | uint8 | `U` + number_unsigned | 256..32767 | int16 | `I` + number_unsigned | 32768..2147483647 | int32 | `l` + number_unsigned | 2147483648..9223372036854775807 | int64 | `L` + number_unsigned | 2147483649..18446744073709551615 | high-precision | `H` + number_float | *any value* | float64 | `D` + string | *with shortest length indicator* | string | `S` + array | *see notes on optimized format* | array | `[` + object | *see notes on optimized format* | map | `{` + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a UBJSON value. + + @note The following values can **not** be converted to a UBJSON value: + - strings with more than 9223372036854775807 bytes (theoretical) + + @note The following markers are not used in the conversion: + - `Z`: no-op values are not created. + - `C`: single-byte strings are serialized with `S` markers. + + @note Any UBJSON output created @ref to_ubjson can be successfully parsed + by @ref from_ubjson. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @note The optimized formats for containers are supported: Parameter + @a use_size adds size information to the beginning of a container and + removes the closing marker. Parameter @a use_type further checks + whether all elements of a container have the same type and adds the + type marker to the beginning of the container. The @a use_type + parameter must only be used together with @a use_size = true. Note + that @a use_size = true alone may result in larger representations - + the benefit of this parameter is that the receiving side is + immediately informed on the number of elements of the container. + + @note If the JSON data contains the binary type, the value stored is a list + of integers, as suggested by the UBJSON documentation. In particular, + this means that serialization and the deserialization of a JSON + containing binary values into UBJSON and back will result in a + different JSON object. + + @param[in] j JSON value to serialize + @param[in] use_size whether to add size annotations to container types + @param[in] use_type whether to add type annotations to container types + (must be combined with @a use_size = true) + @return UBJSON serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in UBJSON format.,to_ubjson} + + @sa http://ubjson.org + @sa see @ref from_ubjson(InputType&&, const bool, const bool) for the + analogous deserialization + @sa see @ref to_cbor(const basic_json& for the related CBOR format + @sa see @ref to_msgpack(const basic_json&) for the related MessagePack format + + @since version 3.1.0 + */ + static std::vector to_ubjson(const basic_json& j, + const bool use_size = false, + const bool use_type = false) + { + std::vector result; + to_ubjson(j, result, use_size, use_type); + return result; + } + + static void to_ubjson(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false) + { + binary_writer(o).write_ubjson(j, use_size, use_type); + } + + static void to_ubjson(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false) + { + binary_writer(o).write_ubjson(j, use_size, use_type); + } + + + /*! + @brief Serializes the given JSON object `j` to BSON and returns a vector + containing the corresponding BSON-representation. + + BSON (Binary JSON) is a binary format in which zero or more ordered key/value pairs are + stored as a single entity (a so-called document). + + The library uses the following mapping from JSON values types to BSON types: + + JSON value type | value/range | BSON type | marker + --------------- | --------------------------------- | ----------- | ------ + null | `null` | null | 0x0A + boolean | `true`, `false` | boolean | 0x08 + number_integer | -9223372036854775808..-2147483649 | int64 | 0x12 + number_integer | -2147483648..2147483647 | int32 | 0x10 + number_integer | 2147483648..9223372036854775807 | int64 | 0x12 + number_unsigned | 0..2147483647 | int32 | 0x10 + number_unsigned | 2147483648..9223372036854775807 | int64 | 0x12 + number_unsigned | 9223372036854775808..18446744073709551615| -- | -- + number_float | *any value* | double | 0x01 + string | *any value* | string | 0x02 + array | *any value* | document | 0x04 + object | *any value* | document | 0x03 + binary | *any value* | binary | 0x05 + + @warning The mapping is **incomplete**, since only JSON-objects (and things + contained therein) can be serialized to BSON. + Also, integers larger than 9223372036854775807 cannot be serialized to BSON, + and the keys may not contain U+0000, since they are serialized a + zero-terminated c-strings. + + @throw out_of_range.407 if `j.is_number_unsigned() && j.get() > 9223372036854775807` + @throw out_of_range.409 if a key in `j` contains a NULL (U+0000) + @throw type_error.317 if `!j.is_object()` + + @pre The input `j` is required to be an object: `j.is_object() == true`. + + @note Any BSON output created via @ref to_bson can be successfully parsed + by @ref from_bson. + + @param[in] j JSON value to serialize + @return BSON serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in BSON format.,to_bson} + + @sa http://bsonspec.org/spec.html + @sa see @ref from_bson(detail::input_adapter&&, const bool strict) for the + analogous deserialization + @sa see @ref to_ubjson(const basic_json&, const bool, const bool) for the + related UBJSON format + @sa see @ref to_cbor(const basic_json&) for the related CBOR format + @sa see @ref to_msgpack(const basic_json&) for the related MessagePack format + */ + static std::vector to_bson(const basic_json& j) + { + std::vector result; + to_bson(j, result); + return result; + } + + /*! + @brief Serializes the given JSON object `j` to BSON and forwards the + corresponding BSON-representation to the given output_adapter `o`. + @param j The JSON object to convert to BSON. + @param o The output adapter that receives the binary BSON representation. + @pre The input `j` shall be an object: `j.is_object() == true` + @sa see @ref to_bson(const basic_json&) + */ + static void to_bson(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_bson(j); + } + + /*! + @copydoc to_bson(const basic_json&, detail::output_adapter) + */ + static void to_bson(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_bson(j); + } + + + /*! + @brief create a JSON value from an input in CBOR format + + Deserializes a given input @a i to a JSON value using the CBOR (Concise + Binary Object Representation) serialization format. + + The library maps CBOR types to JSON value types as follows: + + CBOR type | JSON value type | first byte + ---------------------- | --------------- | ---------- + Integer | number_unsigned | 0x00..0x17 + Unsigned integer | number_unsigned | 0x18 + Unsigned integer | number_unsigned | 0x19 + Unsigned integer | number_unsigned | 0x1A + Unsigned integer | number_unsigned | 0x1B + Negative integer | number_integer | 0x20..0x37 + Negative integer | number_integer | 0x38 + Negative integer | number_integer | 0x39 + Negative integer | number_integer | 0x3A + Negative integer | number_integer | 0x3B + Byte string | binary | 0x40..0x57 + Byte string | binary | 0x58 + Byte string | binary | 0x59 + Byte string | binary | 0x5A + Byte string | binary | 0x5B + UTF-8 string | string | 0x60..0x77 + UTF-8 string | string | 0x78 + UTF-8 string | string | 0x79 + UTF-8 string | string | 0x7A + UTF-8 string | string | 0x7B + UTF-8 string | string | 0x7F + array | array | 0x80..0x97 + array | array | 0x98 + array | array | 0x99 + array | array | 0x9A + array | array | 0x9B + array | array | 0x9F + map | object | 0xA0..0xB7 + map | object | 0xB8 + map | object | 0xB9 + map | object | 0xBA + map | object | 0xBB + map | object | 0xBF + False | `false` | 0xF4 + True | `true` | 0xF5 + Null | `null` | 0xF6 + Half-Precision Float | number_float | 0xF9 + Single-Precision Float | number_float | 0xFA + Double-Precision Float | number_float | 0xFB + + @warning The mapping is **incomplete** in the sense that not all CBOR + types can be converted to a JSON value. The following CBOR types + are not supported and will yield parse errors (parse_error.112): + - date/time (0xC0..0xC1) + - bignum (0xC2..0xC3) + - decimal fraction (0xC4) + - bigfloat (0xC5) + - expected conversions (0xD5..0xD7) + - simple values (0xE0..0xF3, 0xF8) + - undefined (0xF7) + + @warning CBOR allows map keys of any type, whereas JSON only allows + strings as keys in object values. Therefore, CBOR maps with keys + other than UTF-8 strings are rejected (parse_error.113). + + @note Any CBOR output created @ref to_cbor can be successfully parsed by + @ref from_cbor. + + @param[in] i an input in CBOR format convertible to an input adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + @param[in] allow_exceptions whether to throw exceptions in case of a + parse error (optional, true by default) + @param[in] tag_handler how to treat CBOR tags (optional, error by default) + + @return deserialized JSON value; in case of a parse error and + @a allow_exceptions set to `false`, the return value will be + value_t::discarded. + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if unsupported features from CBOR were + used in the given input @a v or if the input is not valid CBOR + @throw parse_error.113 if a string was expected as map key, but not found + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in CBOR + format to a JSON value.,from_cbor} + + @sa http://cbor.io + @sa see @ref to_cbor(const basic_json&) for the analogous serialization + @sa see @ref from_msgpack(InputType&&, const bool, const bool) for the + related MessagePack format + @sa see @ref from_ubjson(InputType&&, const bool, const bool) for the + related UBJSON format + + @since version 2.0.9; parameter @a start_index since 2.1.1; changed to + consume input adapters, removed start_index parameter, and added + @a strict parameter since 3.0.0; added @a allow_exceptions parameter + since 3.2.0; added @a tag_handler parameter since 3.9.0. + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_cbor(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::forward(i)); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + return res ? result : basic_json(value_t::discarded); + } + + /*! + @copydoc from_cbor(InputType&&, const bool, const bool, const cbor_tag_handler_t) + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_cbor(IteratorType first, IteratorType last, + const bool strict = true, + const bool allow_exceptions = true, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::move(first), std::move(last)); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + return res ? result : basic_json(value_t::discarded); + } + + template + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len)) + static basic_json from_cbor(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + return from_cbor(ptr, ptr + len, strict, allow_exceptions, tag_handler); + } + + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len)) + static basic_json from_cbor(detail::span_input_adapter&& i, + const bool strict = true, + const bool allow_exceptions = true, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = i.get(); + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); + return res ? result : basic_json(value_t::discarded); + } + + /*! + @brief create a JSON value from an input in MessagePack format + + Deserializes a given input @a i to a JSON value using the MessagePack + serialization format. + + The library maps MessagePack types to JSON value types as follows: + + MessagePack type | JSON value type | first byte + ---------------- | --------------- | ---------- + positive fixint | number_unsigned | 0x00..0x7F + fixmap | object | 0x80..0x8F + fixarray | array | 0x90..0x9F + fixstr | string | 0xA0..0xBF + nil | `null` | 0xC0 + false | `false` | 0xC2 + true | `true` | 0xC3 + float 32 | number_float | 0xCA + float 64 | number_float | 0xCB + uint 8 | number_unsigned | 0xCC + uint 16 | number_unsigned | 0xCD + uint 32 | number_unsigned | 0xCE + uint 64 | number_unsigned | 0xCF + int 8 | number_integer | 0xD0 + int 16 | number_integer | 0xD1 + int 32 | number_integer | 0xD2 + int 64 | number_integer | 0xD3 + str 8 | string | 0xD9 + str 16 | string | 0xDA + str 32 | string | 0xDB + array 16 | array | 0xDC + array 32 | array | 0xDD + map 16 | object | 0xDE + map 32 | object | 0xDF + bin 8 | binary | 0xC4 + bin 16 | binary | 0xC5 + bin 32 | binary | 0xC6 + ext 8 | binary | 0xC7 + ext 16 | binary | 0xC8 + ext 32 | binary | 0xC9 + fixext 1 | binary | 0xD4 + fixext 2 | binary | 0xD5 + fixext 4 | binary | 0xD6 + fixext 8 | binary | 0xD7 + fixext 16 | binary | 0xD8 + negative fixint | number_integer | 0xE0-0xFF + + @note Any MessagePack output created @ref to_msgpack can be successfully + parsed by @ref from_msgpack. + + @param[in] i an input in MessagePack format convertible to an input + adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + @param[in] allow_exceptions whether to throw exceptions in case of a + parse error (optional, true by default) + + @return deserialized JSON value; in case of a parse error and + @a allow_exceptions set to `false`, the return value will be + value_t::discarded. + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if unsupported features from MessagePack were + used in the given input @a i or if the input is not valid MessagePack + @throw parse_error.113 if a string was expected as map key, but not found + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in + MessagePack format to a JSON value.,from_msgpack} + + @sa http://msgpack.org + @sa see @ref to_msgpack(const basic_json&) for the analogous serialization + @sa see @ref from_cbor(InputType&&, const bool, const bool, const cbor_tag_handler_t) for the + related CBOR format + @sa see @ref from_ubjson(InputType&&, const bool, const bool) for + the related UBJSON format + @sa see @ref from_bson(InputType&&, const bool, const bool) for + the related BSON format + + @since version 2.0.9; parameter @a start_index since 2.1.1; changed to + consume input adapters, removed start_index parameter, and added + @a strict parameter since 3.0.0; added @a allow_exceptions parameter + since 3.2.0 + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_msgpack(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::forward(i)); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + /*! + @copydoc from_msgpack(InputType&&, const bool, const bool) + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_msgpack(IteratorType first, IteratorType last, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::move(first), std::move(last)); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + + template + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len)) + static basic_json from_msgpack(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true) + { + return from_msgpack(ptr, ptr + len, strict, allow_exceptions); + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len)) + static basic_json from_msgpack(detail::span_input_adapter&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = i.get(); + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + + /*! + @brief create a JSON value from an input in UBJSON format + + Deserializes a given input @a i to a JSON value using the UBJSON (Universal + Binary JSON) serialization format. + + The library maps UBJSON types to JSON value types as follows: + + UBJSON type | JSON value type | marker + ----------- | --------------------------------------- | ------ + no-op | *no value, next value is read* | `N` + null | `null` | `Z` + false | `false` | `F` + true | `true` | `T` + float32 | number_float | `d` + float64 | number_float | `D` + uint8 | number_unsigned | `U` + int8 | number_integer | `i` + int16 | number_integer | `I` + int32 | number_integer | `l` + int64 | number_integer | `L` + high-precision number | number_integer, number_unsigned, or number_float - depends on number string | 'H' + string | string | `S` + char | string | `C` + array | array (optimized values are supported) | `[` + object | object (optimized values are supported) | `{` + + @note The mapping is **complete** in the sense that any UBJSON value can + be converted to a JSON value. + + @param[in] i an input in UBJSON format convertible to an input adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + @param[in] allow_exceptions whether to throw exceptions in case of a + parse error (optional, true by default) + + @return deserialized JSON value; in case of a parse error and + @a allow_exceptions set to `false`, the return value will be + value_t::discarded. + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if a parse error occurs + @throw parse_error.113 if a string could not be parsed successfully + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in + UBJSON format to a JSON value.,from_ubjson} + + @sa http://ubjson.org + @sa see @ref to_ubjson(const basic_json&, const bool, const bool) for the + analogous serialization + @sa see @ref from_cbor(InputType&&, const bool, const bool, const cbor_tag_handler_t) for the + related CBOR format + @sa see @ref from_msgpack(InputType&&, const bool, const bool) for + the related MessagePack format + @sa see @ref from_bson(InputType&&, const bool, const bool) for + the related BSON format + + @since version 3.1.0; added @a allow_exceptions parameter since 3.2.0 + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_ubjson(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::forward(i)); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + /*! + @copydoc from_ubjson(InputType&&, const bool, const bool) + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_ubjson(IteratorType first, IteratorType last, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::move(first), std::move(last)); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + template + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len)) + static basic_json from_ubjson(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true) + { + return from_ubjson(ptr, ptr + len, strict, allow_exceptions); + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len)) + static basic_json from_ubjson(detail::span_input_adapter&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = i.get(); + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + + /*! + @brief Create a JSON value from an input in BSON format + + Deserializes a given input @a i to a JSON value using the BSON (Binary JSON) + serialization format. + + The library maps BSON record types to JSON value types as follows: + + BSON type | BSON marker byte | JSON value type + --------------- | ---------------- | --------------------------- + double | 0x01 | number_float + string | 0x02 | string + document | 0x03 | object + array | 0x04 | array + binary | 0x05 | binary + undefined | 0x06 | still unsupported + ObjectId | 0x07 | still unsupported + boolean | 0x08 | boolean + UTC Date-Time | 0x09 | still unsupported + null | 0x0A | null + Regular Expr. | 0x0B | still unsupported + DB Pointer | 0x0C | still unsupported + JavaScript Code | 0x0D | still unsupported + Symbol | 0x0E | still unsupported + JavaScript Code | 0x0F | still unsupported + int32 | 0x10 | number_integer + Timestamp | 0x11 | still unsupported + 128-bit decimal float | 0x13 | still unsupported + Max Key | 0x7F | still unsupported + Min Key | 0xFF | still unsupported + + @warning The mapping is **incomplete**. The unsupported mappings + are indicated in the table above. + + @param[in] i an input in BSON format convertible to an input adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + @param[in] allow_exceptions whether to throw exceptions in case of a + parse error (optional, true by default) + + @return deserialized JSON value; in case of a parse error and + @a allow_exceptions set to `false`, the return value will be + value_t::discarded. + + @throw parse_error.114 if an unsupported BSON record type is encountered + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in + BSON format to a JSON value.,from_bson} + + @sa http://bsonspec.org/spec.html + @sa see @ref to_bson(const basic_json&) for the analogous serialization + @sa see @ref from_cbor(InputType&&, const bool, const bool, const cbor_tag_handler_t) for the + related CBOR format + @sa see @ref from_msgpack(InputType&&, const bool, const bool) for + the related MessagePack format + @sa see @ref from_ubjson(InputType&&, const bool, const bool) for the + related UBJSON format + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bson(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::forward(i)); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + /*! + @copydoc from_bson(InputType&&, const bool, const bool) + */ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bson(IteratorType first, IteratorType last, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = detail::input_adapter(std::move(first), std::move(last)); + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + + template + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len)) + static basic_json from_bson(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true) + { + return from_bson(ptr, ptr + len, strict, allow_exceptions); + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len)) + static basic_json from_bson(detail::span_input_adapter&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + detail::json_sax_dom_parser sdp(result, allow_exceptions); + auto ia = i.get(); + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); + return res ? result : basic_json(value_t::discarded); + } + /// @} + + ////////////////////////// + // JSON Pointer support // + ////////////////////////// + + /// @name JSON Pointer functions + /// @{ + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. Similar to @ref operator[](const typename + object_t::key_type&), `null` values are created in arrays and objects if + necessary. + + In particular: + - If the JSON pointer points to an object key that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. + - If the JSON pointer points to an array index that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. All indices between the current maximum and the given + index are also filled with `null`. + - The special value `-` is treated as a synonym for the index past the + end. + + @param[in] ptr a JSON pointer + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.404 if the JSON pointer can not be resolved + + @liveexample{The behavior is shown in the example.,operatorjson_pointer} + + @since version 2.0.0 + */ + reference operator[](const json_pointer& ptr) + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. The function does not change the JSON + value; no `null` values are created. In particular, the special value + `-` yields an exception. + + @param[in] ptr JSON pointer to the desired element + + @return const reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + + @liveexample{The behavior is shown in the example.,operatorjson_pointer_const} + + @since version 2.0.0 + */ + const_reference operator[](const json_pointer& ptr) const + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a reference to the element at with specified JSON pointer @a ptr, + with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @throw parse_error.106 if an array index in the passed JSON pointer @a ptr + begins with '0'. See example below. + + @throw parse_error.109 if an array index in the passed JSON pointer @a ptr + is not a number. See example below. + + @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr + is out of range. See example below. + + @throw out_of_range.402 if the array index '-' is used in the passed JSON + pointer @a ptr. As `at` provides checked access (and no elements are + implicitly inserted), the index '-' is always invalid. See example below. + + @throw out_of_range.403 if the JSON pointer describes a key of an object + which cannot be found. See example below. + + @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. + See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 2.0.0 + + @liveexample{The behavior is shown in the example.,at_json_pointer} + */ + reference at(const json_pointer& ptr) + { + return ptr.get_checked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a const reference to the element at with specified JSON pointer @a + ptr, with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @throw parse_error.106 if an array index in the passed JSON pointer @a ptr + begins with '0'. See example below. + + @throw parse_error.109 if an array index in the passed JSON pointer @a ptr + is not a number. See example below. + + @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr + is out of range. See example below. + + @throw out_of_range.402 if the array index '-' is used in the passed JSON + pointer @a ptr. As `at` provides checked access (and no elements are + implicitly inserted), the index '-' is always invalid. See example below. + + @throw out_of_range.403 if the JSON pointer describes a key of an object + which cannot be found. See example below. + + @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. + See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 2.0.0 + + @liveexample{The behavior is shown in the example.,at_json_pointer_const} + */ + const_reference at(const json_pointer& ptr) const + { + return ptr.get_checked(this); + } + + /*! + @brief return flattened JSON value + + The function creates a JSON object whose keys are JSON pointers (see [RFC + 6901](https://tools.ietf.org/html/rfc6901)) and whose values are all + primitive. The original JSON value can be restored using the @ref + unflatten() function. + + @return an object that maps JSON pointers to primitive values + + @note Empty objects and arrays are flattened to `null` and will not be + reconstructed correctly by the @ref unflatten() function. + + @complexity Linear in the size the JSON value. + + @liveexample{The following code shows how a JSON object is flattened to an + object whose keys consist of JSON pointers.,flatten} + + @sa see @ref unflatten() for the reverse function + + @since version 2.0.0 + */ + basic_json flatten() const + { + basic_json result(value_t::object); + json_pointer::flatten("", *this, result); + return result; + } + + /*! + @brief unflatten a previously flattened JSON value + + The function restores the arbitrary nesting of a JSON value that has been + flattened before using the @ref flatten() function. The JSON value must + meet certain constraints: + 1. The value must be an object. + 2. The keys must be JSON pointers (see + [RFC 6901](https://tools.ietf.org/html/rfc6901)) + 3. The mapped values must be primitive JSON types. + + @return the original JSON from a flattened version + + @note Empty objects and arrays are flattened by @ref flatten() to `null` + values and can not unflattened to their original type. Apart from + this example, for a JSON value `j`, the following is always true: + `j == j.flatten().unflatten()`. + + @complexity Linear in the size the JSON value. + + @throw type_error.314 if value is not an object + @throw type_error.315 if object values are not primitive + + @liveexample{The following code shows how a flattened JSON object is + unflattened into the original nested JSON object.,unflatten} + + @sa see @ref flatten() for the reverse function + + @since version 2.0.0 + */ + basic_json unflatten() const + { + return json_pointer::unflatten(*this); + } + + /// @} + + ////////////////////////// + // JSON Patch functions // + ////////////////////////// + + /// @name JSON Patch functions + /// @{ + + /*! + @brief applies a JSON patch + + [JSON Patch](http://jsonpatch.com) defines a JSON document structure for + expressing a sequence of operations to apply to a JSON) document. With + this function, a JSON Patch is applied to the current JSON value by + executing all operations from the patch. + + @param[in] json_patch JSON patch document + @return patched document + + @note The application of a patch is atomic: Either all operations succeed + and the patched document is returned or an exception is thrown. In + any case, the original value is not changed: the patch is applied + to a copy of the value. + + @throw parse_error.104 if the JSON patch does not consist of an array of + objects + + @throw parse_error.105 if the JSON patch is malformed (e.g., mandatory + attributes are missing); example: `"operation add must have member path"` + + @throw out_of_range.401 if an array index is out of range. + + @throw out_of_range.403 if a JSON pointer inside the patch could not be + resolved successfully in the current JSON value; example: `"key baz not + found"` + + @throw out_of_range.405 if JSON pointer has no parent ("add", "remove", + "move") + + @throw other_error.501 if "test" operation was unsuccessful + + @complexity Linear in the size of the JSON value and the length of the + JSON patch. As usually only a fraction of the JSON value is affected by + the patch, the complexity can usually be neglected. + + @liveexample{The following code shows how a JSON patch is applied to a + value.,patch} + + @sa see @ref diff -- create a JSON patch by comparing two JSON values + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901) + + @since version 2.0.0 + */ + basic_json patch(const basic_json& json_patch) const + { + // make a working copy to apply the patch to + basic_json result = *this; + + // the valid JSON Patch operations + 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; + }; + + // wrapper for "add" operation; add value at ptr + const auto operation_add = [&result](json_pointer & ptr, basic_json val) + { + // adding to the root of the target document means replacing it + if (ptr.empty()) + { + result = val; + return; + } + + // make sure the top element of the pointer exists + json_pointer top_pointer = ptr.top(); + if (top_pointer != ptr) + { + result.at(top_pointer); + } + + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.back(); + ptr.pop_back(); + basic_json& parent = result[ptr]; + + switch (parent.m_type) + { + case value_t::null: + case value_t::object: + { + // use operator[] to add value + parent[last_path] = val; + break; + } + + case value_t::array: + { + if (last_path == "-") + { + // special case: append to back + parent.push_back(val); + } + else + { + const auto idx = json_pointer::array_index(last_path); + if (JSON_HEDLEY_UNLIKELY(idx > parent.size())) + { + // avoid undefined behavior + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range", parent)); + } + + // default case: insert add offset + parent.insert(parent.begin() + static_cast(idx), val); + } + break; + } + + // if there exists a parent it cannot be primitive + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } + }; + + // wrapper for "remove" operation; remove value at ptr + const auto operation_remove = [this, &result](json_pointer & ptr) + { + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.back(); + ptr.pop_back(); + basic_json& parent = result.at(ptr); + + // remove child + if (parent.is_object()) + { + // perform range check + auto it = parent.find(last_path); + if (JSON_HEDLEY_LIKELY(it != parent.end())) + { + parent.erase(it); + } + else + { + JSON_THROW(out_of_range::create(403, "key '" + last_path + "' not found", *this)); + } + } + else if (parent.is_array()) + { + // note erase performs range check + parent.erase(json_pointer::array_index(last_path)); + } + }; + + // type check: top level value must be an array + if (JSON_HEDLEY_UNLIKELY(!json_patch.is_array())) + { + JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects", json_patch)); + } + + // iterate and apply the operations + for (const auto& val : json_patch) + { + // wrapper to get a value for an operation + const auto get_value = [&val](const std::string & op, + const std::string & member, + bool string_type) -> basic_json & + { + // find value + auto it = val.m_value.object->find(member); + + // context-sensitive error message + const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'"; + + // check if desired value is present + if (JSON_HEDLEY_UNLIKELY(it == val.m_value.object->end())) + { + // NOLINTNEXTLINE(performance-inefficient-string-concatenation) + JSON_THROW(parse_error::create(105, 0, error_msg + " must have member '" + member + "'", val)); + } + + // check if result is of type string + if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string())) + { + // NOLINTNEXTLINE(performance-inefficient-string-concatenation) + JSON_THROW(parse_error::create(105, 0, error_msg + " must have string member '" + member + "'", val)); + } + + // no error: return value + return it->second; + }; + + // type check: every element of the array must be an object + if (JSON_HEDLEY_UNLIKELY(!val.is_object())) + { + JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects", val)); + } + + // collect mandatory members + const auto op = get_value("op", "op", true).template get(); + const auto path = get_value(op, "path", true).template get(); + 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: + { + // the "path" location must exist - use at() + result.at(ptr) = get_value("replace", "value", false); + break; + } + + case patch_operations::move: + { + const auto from_path = get_value("move", "from", true).template get(); + json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + basic_json v = result.at(from_ptr); + + // The move operation is functionally identical to a + // "remove" operation on the "from" location, followed + // immediately by an "add" operation at the target + // location with the value that was just removed. + operation_remove(from_ptr); + operation_add(ptr, v); + break; + } + + case patch_operations::copy: + { + const auto from_path = get_value("copy", "from", true).template get(); + const json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + basic_json v = result.at(from_ptr); + + // The copy is functionally identical to an "add" + // operation at the target location using the value + // specified in the "from" member. + operation_add(ptr, v); + break; + } + + case patch_operations::test: + { + bool success = false; + JSON_TRY + { + // check if "value" matches the one at "path" + // the "path" location must exist - use at() + success = (result.at(ptr) == get_value("test", "value", false)); + } + JSON_INTERNAL_CATCH (out_of_range&) + { + // ignore out of range errors: success remains false + } + + // throw an exception if test fails + if (JSON_HEDLEY_UNLIKELY(!success)) + { + JSON_THROW(other_error::create(501, "unsuccessful: " + val.dump(), val)); + } + + break; + } + + default: + { + // op must be "add", "remove", "replace", "move", "copy", or + // "test" + JSON_THROW(parse_error::create(105, 0, "operation value '" + op + "' is invalid", val)); + } + } + } + + return result; + } + + /*! + @brief creates a diff as a JSON patch + + Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can + be changed into the value @a target by calling @ref patch function. + + @invariant For two JSON values @a source and @a target, the following code + yields always `true`: + @code {.cpp} + source.patch(diff(source, target)) == target; + @endcode + + @note Currently, only `remove`, `add`, and `replace` operations are + generated. + + @param[in] source JSON value to compare from + @param[in] target JSON value to compare against + @param[in] path helper value to create JSON pointers + + @return a JSON patch to convert the @a source to @a target + + @complexity Linear in the lengths of @a source and @a target. + + @liveexample{The following code shows how a JSON patch is created as a + diff for two JSON values.,diff} + + @sa see @ref patch -- apply a JSON patch + @sa see @ref merge_patch -- apply a JSON Merge Patch + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + + @since version 2.0.0 + */ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json diff(const basic_json& source, const basic_json& target, + const std::string& path = "") + { + // the patch + basic_json result(value_t::array); + + // if the values are the same, return empty patch + if (source == target) + { + return result; + } + + if (source.type() != target.type()) + { + // different types: replace value + result.push_back( + { + {"op", "replace"}, {"path", path}, {"value", target} + }); + return result; + } + + switch (source.type()) + { + case value_t::array: + { + // first pass: traverse common elements + std::size_t i = 0; + while (i < source.size() && i < target.size()) + { + // recursive call to compare array values at index i + auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i)); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + ++i; + } + + // i now reached the end of at least one array + // in a second pass, traverse the remaining elements + + // remove my remaining elements + const auto end_index = static_cast(result.size()); + while (i < source.size()) + { + // add operations in reverse order to avoid invalid + // indices + result.insert(result.begin() + end_index, object( + { + {"op", "remove"}, + {"path", path + "/" + std::to_string(i)} + })); + ++i; + } + + // add other remaining elements + while (i < target.size()) + { + result.push_back( + { + {"op", "add"}, + {"path", path + "/-"}, + {"value", target[i]} + }); + ++i; + } + + break; + } + + case value_t::object: + { + // first pass: traverse this object's elements + for (auto it = source.cbegin(); it != source.cend(); ++it) + { + // escape the key name to be used in a JSON patch + const auto path_key = path + "/" + detail::escape(it.key()); + + if (target.find(it.key()) != target.end()) + { + // recursive call to compare object values at key it + auto temp_diff = diff(it.value(), target[it.key()], path_key); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + } + else + { + // found a key that is not in o -> remove it + result.push_back(object( + { + {"op", "remove"}, {"path", path_key} + })); + } + } + + // second pass: traverse other object's elements + for (auto it = target.cbegin(); it != target.cend(); ++it) + { + if (source.find(it.key()) == source.end()) + { + // found a key that is not in this -> add it + const auto path_key = path + "/" + detail::escape(it.key()); + result.push_back( + { + {"op", "add"}, {"path", path_key}, + {"value", it.value()} + }); + } + } + + break; + } + + default: + { + // both primitive type: replace value + result.push_back( + { + {"op", "replace"}, {"path", path}, {"value", target} + }); + break; + } + } + + return result; + } + + /// @} + + //////////////////////////////// + // JSON Merge Patch functions // + //////////////////////////////// + + /// @name JSON Merge Patch functions + /// @{ + + /*! + @brief applies a JSON Merge Patch + + The merge patch format is primarily intended for use with the HTTP PATCH + method as a means of describing a set of modifications to a target + resource's content. This function applies a merge patch to the current + JSON value. + + The function implements the following algorithm from Section 2 of + [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396): + + ``` + define MergePatch(Target, Patch): + if Patch is an Object: + if Target is not an Object: + Target = {} // Ignore the contents and set it to an empty Object + for each Name/Value pair in Patch: + if Value is null: + if Name exists in Target: + remove the Name/Value pair from Target + else: + Target[Name] = MergePatch(Target[Name], Value) + return Target + else: + return Patch + ``` + + Thereby, `Target` is the current object; that is, the patch is applied to + the current value. + + @param[in] apply_patch the patch to apply + + @complexity Linear in the lengths of @a patch. + + @liveexample{The following code shows how a JSON Merge Patch is applied to + a JSON document.,merge_patch} + + @sa see @ref patch -- apply a JSON patch + @sa [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396) + + @since version 3.0.0 + */ + void merge_patch(const basic_json& apply_patch) + { + if (apply_patch.is_object()) + { + if (!is_object()) + { + *this = object(); + } + for (auto it = apply_patch.begin(); it != apply_patch.end(); ++it) + { + if (it.value().is_null()) + { + erase(it.key()); + } + else + { + operator[](it.key()).merge_patch(it.value()); + } + } + } + else + { + *this = apply_patch; + } + } + + /// @} +}; + +/*! +@brief user-defined to_string function for JSON values + +This function implements a user-defined to_string for JSON objects. + +@param[in] j a JSON object +@return a std::string object +*/ + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +std::string to_string(const NLOHMANN_BASIC_JSON_TPL& j) +{ + return j.dump(); +} +} // namespace nlohmann + +/////////////////////// +// nonmember support // +/////////////////////// + +// specialization of std::swap, and std::hash +namespace std +{ + +/// hash value for JSON objects +template<> +struct hash +{ + /*! + @brief return a hash value for a JSON object + + @since version 1.0.0 + */ + std::size_t operator()(const nlohmann::json& j) const + { + return nlohmann::detail::hash(j); + } +}; + +/// specialization for std::less +/// @note: do not remove the space after '<', +/// see https://github.com/nlohmann/json/pull/679 +template<> +struct less<::nlohmann::detail::value_t> +{ + /*! + @brief compare two value_t enum values + @since version 3.0.0 + */ + bool operator()(nlohmann::detail::value_t lhs, + nlohmann::detail::value_t rhs) const noexcept + { + return nlohmann::detail::operator<(lhs, rhs); + } +}; + +// C++20 prohibit function specialization in the std namespace. +#ifndef JSON_HAS_CPP_20 + +/*! +@brief exchanges the values of two JSON objects + +@since version 1.0.0 +*/ +template<> +inline void swap(nlohmann::json& j1, nlohmann::json& j2) noexcept( // NOLINT(readability-inconsistent-declaration-parameter-name) + is_nothrow_move_constructible::value&& // NOLINT(misc-redundant-expression) + is_nothrow_move_assignable::value +) +{ + j1.swap(j2); +} + +#endif + +} // namespace std + +/*! +@brief user-defined string literal for JSON values + +This operator implements a user-defined string literal for JSON objects. It +can be used by adding `"_json"` to a string literal and returns a JSON object +if no parse error occurred. + +@param[in] s a string representation of a JSON object +@param[in] n the length of string @a s +@return a JSON object + +@since version 1.0.0 +*/ +JSON_HEDLEY_NON_NULL(1) +inline nlohmann::json operator "" _json(const char* s, std::size_t n) +{ + return nlohmann::json::parse(s, s + n); +} + +/*! +@brief user-defined string literal for JSON pointer + +This operator implements a user-defined string literal for JSON Pointers. It +can be used by adding `"_json_pointer"` to a string literal and returns a JSON pointer +object if no parse error occurred. + +@param[in] s a string representation of a JSON Pointer +@param[in] n the length of string @a s +@return a JSON pointer object + +@since version 2.0.0 +*/ +JSON_HEDLEY_NON_NULL(1) +inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n) +{ + return nlohmann::json::json_pointer(std::string(s, n)); +} + +#include + +#endif // INCLUDE_NLOHMANN_JSON_HPP_ diff --git a/lib/json/include/nlohmann/json_fwd.hpp b/lib/json/include/nlohmann/json_fwd.hpp new file mode 100644 index 0000000..332227c --- /dev/null +++ b/lib/json/include/nlohmann/json_fwd.hpp @@ -0,0 +1,78 @@ +#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ +#define INCLUDE_NLOHMANN_JSON_FWD_HPP_ + +#include // int64_t, uint64_t +#include // map +#include // allocator +#include // string +#include // vector + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ +/*! +@brief default JSONSerializer template argument + +This serializer ignores the template arguments and uses ADL +([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) +for serialization. +*/ +template +struct adl_serializer; + +template class ObjectType = + std::map, + template 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 class AllocatorType = std::allocator, + template class JSONSerializer = + adl_serializer, + class BinaryType = std::vector> +class basic_json; + +/*! +@brief JSON Pointer + +A JSON pointer defines a string syntax for identifying a specific value +within a JSON document. It can be used with functions `at` and +`operator[]`. Furthermore, JSON pointers are the base for JSON patches. + +@sa [RFC 6901](https://tools.ietf.org/html/rfc6901) + +@since version 2.0.0 +*/ +template +class json_pointer; + +/*! +@brief default JSON class + +This type is the default specialization of the @ref basic_json class which +uses the standard template types. + +@since version 1.0.0 +*/ +using json = basic_json<>; + +template +struct ordered_map; + +/*! +@brief ordered JSON class + +This type preserves the insertion order of object keys. + +@since version 3.9.0 +*/ +using ordered_json = basic_json; + +} // namespace nlohmann + +#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ diff --git a/lib/json/include/nlohmann/ordered_map.hpp b/lib/json/include/nlohmann/ordered_map.hpp new file mode 100644 index 0000000..cf5f133 --- /dev/null +++ b/lib/json/include/nlohmann/ordered_map.hpp @@ -0,0 +1,190 @@ +#pragma once + +#include // less +#include // initializer_list +#include // input_iterator_tag, iterator_traits +#include // allocator +#include // for out_of_range +#include // enable_if, is_convertible +#include // pair +#include // vector + +#include + +namespace nlohmann +{ + +/// ordered_map: a minimal map-like container that preserves insertion order +/// for use within nlohmann::basic_json +template , + class Allocator = std::allocator>> + struct ordered_map : std::vector, Allocator> +{ + using key_type = Key; + using mapped_type = T; + using Container = std::vector, Allocator>; + using typename Container::iterator; + using typename Container::const_iterator; + using typename Container::size_type; + using typename Container::value_type; + + // Explicit constructors instead of `using Container::Container` + // otherwise older compilers choke on it (GCC <= 5.5, xcode <= 9.4) + ordered_map(const Allocator& alloc = Allocator()) : Container{alloc} {} + template + ordered_map(It first, It last, const Allocator& alloc = Allocator()) + : Container{first, last, alloc} {} + ordered_map(std::initializer_list init, const Allocator& alloc = Allocator() ) + : Container{init, alloc} {} + + std::pair emplace(const key_type& key, T&& t) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return {it, false}; + } + } + Container::emplace_back(key, t); + return {--this->end(), true}; + } + + T& operator[](const Key& key) + { + return emplace(key, T{}).first->second; + } + + const T& operator[](const Key& key) const + { + return at(key); + } + + T& at(const Key& key) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return it->second; + } + } + + JSON_THROW(std::out_of_range("key not found")); + } + + const T& at(const Key& key) const + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return it->second; + } + } + + JSON_THROW(std::out_of_range("key not found")); + } + + size_type erase(const Key& key) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + // Since we cannot move const Keys, re-construct them in place + for (auto next = it; ++next != this->end(); ++it) + { + it->~value_type(); // Destroy but keep allocation + new (&*it) value_type{std::move(*next)}; + } + Container::pop_back(); + return 1; + } + } + return 0; + } + + iterator erase(iterator pos) + { + auto it = pos; + + // Since we cannot move const Keys, re-construct them in place + for (auto next = it; ++next != this->end(); ++it) + { + it->~value_type(); // Destroy but keep allocation + new (&*it) value_type{std::move(*next)}; + } + Container::pop_back(); + return pos; + } + + size_type count(const Key& key) const + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return 1; + } + } + return 0; + } + + iterator find(const Key& key) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return it; + } + } + return Container::end(); + } + + const_iterator find(const Key& key) const + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == key) + { + return it; + } + } + return Container::end(); + } + + std::pair insert( value_type&& value ) + { + return emplace(value.first, std::move(value.second)); + } + + std::pair insert( const value_type& value ) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (it->first == value.first) + { + return {it, false}; + } + } + Container::push_back(value); + return {--this->end(), true}; + } + + template + using require_input_iter = typename std::enable_if::iterator_category, + std::input_iterator_tag>::value>::type; + + template> + void insert(InputIt first, InputIt last) + { + for (auto it = first; it != last; ++it) + { + insert(*it); + } + } +}; + +} // namespace nlohmann diff --git a/lib/json/include/nlohmann/thirdparty/hedley/hedley.hpp b/lib/json/include/nlohmann/thirdparty/hedley/hedley.hpp new file mode 100644 index 0000000..b309e98 --- /dev/null +++ b/lib/json/include/nlohmann/thirdparty/hedley/hedley.hpp @@ -0,0 +1,2044 @@ +#pragma once + +/* Hedley - https://nemequ.github.io/hedley + * Created by Evan Nemerson + * + * To the extent possible under law, the author(s) have dedicated all + * copyright and related and neighboring rights to this software to + * the public domain worldwide. This software is distributed without + * any warranty. + * + * For details, see . + * SPDX-License-Identifier: CC0-1.0 + */ + +#if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 15) +#if defined(JSON_HEDLEY_VERSION) + #undef JSON_HEDLEY_VERSION +#endif +#define JSON_HEDLEY_VERSION 15 + +#if defined(JSON_HEDLEY_STRINGIFY_EX) + #undef JSON_HEDLEY_STRINGIFY_EX +#endif +#define JSON_HEDLEY_STRINGIFY_EX(x) #x + +#if defined(JSON_HEDLEY_STRINGIFY) + #undef JSON_HEDLEY_STRINGIFY +#endif +#define JSON_HEDLEY_STRINGIFY(x) JSON_HEDLEY_STRINGIFY_EX(x) + +#if defined(JSON_HEDLEY_CONCAT_EX) + #undef JSON_HEDLEY_CONCAT_EX +#endif +#define JSON_HEDLEY_CONCAT_EX(a,b) a##b + +#if defined(JSON_HEDLEY_CONCAT) + #undef JSON_HEDLEY_CONCAT +#endif +#define JSON_HEDLEY_CONCAT(a,b) JSON_HEDLEY_CONCAT_EX(a,b) + +#if defined(JSON_HEDLEY_CONCAT3_EX) + #undef JSON_HEDLEY_CONCAT3_EX +#endif +#define JSON_HEDLEY_CONCAT3_EX(a,b,c) a##b##c + +#if defined(JSON_HEDLEY_CONCAT3) + #undef JSON_HEDLEY_CONCAT3 +#endif +#define JSON_HEDLEY_CONCAT3(a,b,c) JSON_HEDLEY_CONCAT3_EX(a,b,c) + +#if defined(JSON_HEDLEY_VERSION_ENCODE) + #undef JSON_HEDLEY_VERSION_ENCODE +#endif +#define JSON_HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision)) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MAJOR) + #undef JSON_HEDLEY_VERSION_DECODE_MAJOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MINOR) + #undef JSON_HEDLEY_VERSION_DECODE_MINOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_REVISION) + #undef JSON_HEDLEY_VERSION_DECODE_REVISION +#endif +#define JSON_HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000) + +#if defined(JSON_HEDLEY_GNUC_VERSION) + #undef JSON_HEDLEY_GNUC_VERSION +#endif +#if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#elif defined(__GNUC__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0) +#endif + +#if defined(JSON_HEDLEY_GNUC_VERSION_CHECK) + #undef JSON_HEDLEY_GNUC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GNUC_VERSION) + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GNUC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION) + #undef JSON_HEDLEY_MSVC_VERSION +#endif +#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100) +#elif defined(_MSC_FULL_VER) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10) +#elif defined(_MSC_VER) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION_CHECK) + #undef JSON_HEDLEY_MSVC_VERSION_CHECK +#endif +#if !defined(JSON_HEDLEY_MSVC_VERSION) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0) +#elif defined(_MSC_VER) && (_MSC_VER >= 1400) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch))) +#elif defined(_MSC_VER) && (_MSC_VER >= 1200) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch))) +#else + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor))) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION) + #undef JSON_HEDLEY_INTEL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && !defined(__ICL) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE) +#elif defined(__INTEL_COMPILER) && !defined(__ICL) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_VERSION) + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_INTEL_CL_VERSION) + #undef JSON_HEDLEY_INTEL_CL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && defined(__ICL) + #define JSON_HEDLEY_INTEL_CL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER, __INTEL_COMPILER_UPDATE, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_CL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_CL_VERSION) + #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_CL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION) + #undef JSON_HEDLEY_PGI_VERSION +#endif +#if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__) + #define JSON_HEDLEY_PGI_VERSION JSON_HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION_CHECK) + #undef JSON_HEDLEY_PGI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PGI_VERSION) + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PGI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #undef JSON_HEDLEY_SUNPRO_VERSION +#endif +#if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10) +#elif defined(__SUNPRO_C) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf) +#elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10) +#elif defined(__SUNPRO_CC) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION_CHECK) + #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_SUNPRO_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION +#endif +#if defined(__EMSCRIPTEN__) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION JSON_HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_EMSCRIPTEN_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION) + #undef JSON_HEDLEY_ARM_VERSION +#endif +#if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100) +#elif defined(__CC_ARM) && defined(__ARMCC_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION_CHECK) + #undef JSON_HEDLEY_ARM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_ARM_VERSION) + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_ARM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION) + #undef JSON_HEDLEY_IBM_VERSION +#endif +#if defined(__ibmxl__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__) +#elif defined(__xlC__) && defined(__xlC_ver__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff) +#elif defined(__xlC__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION_CHECK) + #undef JSON_HEDLEY_IBM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IBM_VERSION) + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IBM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_VERSION) + #undef JSON_HEDLEY_TI_VERSION +#endif +#if \ + defined(__TI_COMPILER_VERSION__) && \ + ( \ + defined(__TMS470__) || defined(__TI_ARM__) || \ + defined(__MSP430__) || \ + defined(__TMS320C2000__) \ + ) +#if (__TI_COMPILER_VERSION__ >= 16000000) + #define JSON_HEDLEY_TI_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif +#endif + +#if defined(JSON_HEDLEY_TI_VERSION_CHECK) + #undef JSON_HEDLEY_TI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_VERSION) + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #undef JSON_HEDLEY_TI_CL2000_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C2000__) + #define JSON_HEDLEY_TI_CL2000_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL2000_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #undef JSON_HEDLEY_TI_CL430_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__MSP430__) + #define JSON_HEDLEY_TI_CL430_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL430_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #undef JSON_HEDLEY_TI_ARMCL_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && (defined(__TMS470__) || defined(__TI_ARM__)) + #define JSON_HEDLEY_TI_ARMCL_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION_CHECK) + #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_ARMCL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #undef JSON_HEDLEY_TI_CL6X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C6X__) + #define JSON_HEDLEY_TI_CL6X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL6X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #undef JSON_HEDLEY_TI_CL7X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__C7000__) + #define JSON_HEDLEY_TI_CL7X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL7X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #undef JSON_HEDLEY_TI_CLPRU_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__PRU__) + #define JSON_HEDLEY_TI_CLPRU_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CLPRU_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION) + #undef JSON_HEDLEY_CRAY_VERSION +#endif +#if defined(_CRAYC) + #if defined(_RELEASE_PATCHLEVEL) + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL) + #else + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION_CHECK) + #undef JSON_HEDLEY_CRAY_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_CRAY_VERSION) + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_CRAY_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION) + #undef JSON_HEDLEY_IAR_VERSION +#endif +#if defined(__IAR_SYSTEMS_ICC__) + #if __VER__ > 1000 + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000)) + #else + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(__VER__ / 100, __VER__ % 100, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION_CHECK) + #undef JSON_HEDLEY_IAR_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IAR_VERSION) + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IAR_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION) + #undef JSON_HEDLEY_TINYC_VERSION +#endif +#if defined(__TINYC__) + #define JSON_HEDLEY_TINYC_VERSION JSON_HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION_CHECK) + #undef JSON_HEDLEY_TINYC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TINYC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION) + #undef JSON_HEDLEY_DMC_VERSION +#endif +#if defined(__DMC__) + #define JSON_HEDLEY_DMC_VERSION JSON_HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION_CHECK) + #undef JSON_HEDLEY_DMC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_DMC_VERSION) + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_DMC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #undef JSON_HEDLEY_COMPCERT_VERSION +#endif +#if defined(__COMPCERT_VERSION__) + #define JSON_HEDLEY_COMPCERT_VERSION JSON_HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION_CHECK) + #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_COMPCERT_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION) + #undef JSON_HEDLEY_PELLES_VERSION +#endif +#if defined(__POCC__) + #define JSON_HEDLEY_PELLES_VERSION JSON_HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION_CHECK) + #undef JSON_HEDLEY_PELLES_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PELLES_VERSION) + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PELLES_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_MCST_LCC_VERSION) + #undef JSON_HEDLEY_MCST_LCC_VERSION +#endif +#if defined(__LCC__) && defined(__LCC_MINOR__) + #define JSON_HEDLEY_MCST_LCC_VERSION JSON_HEDLEY_VERSION_ENCODE(__LCC__ / 100, __LCC__ % 100, __LCC_MINOR__) +#endif + +#if defined(JSON_HEDLEY_MCST_LCC_VERSION_CHECK) + #undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_MCST_LCC_VERSION) + #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_MCST_LCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION) + #undef JSON_HEDLEY_GCC_VERSION +#endif +#if \ + defined(JSON_HEDLEY_GNUC_VERSION) && \ + !defined(__clang__) && \ + !defined(JSON_HEDLEY_INTEL_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_ARM_VERSION) && \ + !defined(JSON_HEDLEY_CRAY_VERSION) && \ + !defined(JSON_HEDLEY_TI_VERSION) && \ + !defined(JSON_HEDLEY_TI_ARMCL_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL430_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL2000_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL6X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL7X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CLPRU_VERSION) && \ + !defined(__COMPCERT__) && \ + !defined(JSON_HEDLEY_MCST_LCC_VERSION) + #define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GCC_VERSION) + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_ATTRIBUTE +#endif +#if \ + defined(__has_attribute) && \ + ( \ + (!defined(JSON_HEDLEY_IAR_VERSION) || JSON_HEDLEY_IAR_VERSION_CHECK(8,5,9)) \ + ) +# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute) +#else +# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE +#endif +#if \ + defined(__has_cpp_attribute) && \ + defined(__cplusplus) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS +#endif +#if !defined(__cplusplus) || !defined(__has_cpp_attribute) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#elif \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \ + (!defined(JSON_HEDLEY_MSVC_VERSION) || JSON_HEDLEY_MSVC_VERSION_CHECK(19,20,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(ns::attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_BUILTIN) + #undef JSON_HEDLEY_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin) +#else + #define JSON_HEDLEY_HAS_BUILTIN(builtin) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_BUILTIN) + #undef JSON_HEDLEY_GNUC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_BUILTIN) + #undef JSON_HEDLEY_GCC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_FEATURE) + #undef JSON_HEDLEY_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_HAS_FEATURE(feature) __has_feature(feature) +#else + #define JSON_HEDLEY_HAS_FEATURE(feature) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_FEATURE) + #undef JSON_HEDLEY_GNUC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_FEATURE) + #undef JSON_HEDLEY_GCC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_EXTENSION) + #undef JSON_HEDLEY_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_HAS_EXTENSION(extension) __has_extension(extension) +#else + #define JSON_HEDLEY_HAS_EXTENSION(extension) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_EXTENSION) + #undef JSON_HEDLEY_GNUC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_EXTENSION) + #undef JSON_HEDLEY_GCC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_WARNING) + #undef JSON_HEDLEY_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_HAS_WARNING(warning) __has_warning(warning) +#else + #define JSON_HEDLEY_HAS_WARNING(warning) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_WARNING) + #undef JSON_HEDLEY_GNUC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_WARNING) + #undef JSON_HEDLEY_GCC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + defined(__clang__) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \ + (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR)) + #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_PRAGMA(value) __pragma(value) +#else + #define JSON_HEDLEY_PRAGMA(value) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH) + #undef JSON_HEDLEY_DIAGNOSTIC_PUSH +#endif +#if defined(JSON_HEDLEY_DIAGNOSTIC_POP) + #undef JSON_HEDLEY_DIAGNOSTIC_POP +#endif +#if defined(__clang__) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push)) + #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop)) +#elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("pop") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_PUSH + #define JSON_HEDLEY_DIAGNOSTIC_POP +#endif + +/* JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat") +# if JSON_HEDLEY_HAS_WARNING("-Wc++17-extensions") +# if JSON_HEDLEY_HAS_WARNING("-Wc++1z-extensions") +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + _Pragma("clang diagnostic ignored \"-Wc++1z-extensions\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# endif +# else +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# endif +# endif +#endif +#if !defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(x) x +#endif + +#if defined(JSON_HEDLEY_CONST_CAST) + #undef JSON_HEDLEY_CONST_CAST +#endif +#if defined(__cplusplus) +# define JSON_HEDLEY_CONST_CAST(T, expr) (const_cast(expr)) +#elif \ + JSON_HEDLEY_HAS_WARNING("-Wcast-qual") || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_CONST_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_REINTERPRET_CAST) + #undef JSON_HEDLEY_REINTERPRET_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast(expr)) +#else + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_STATIC_CAST) + #undef JSON_HEDLEY_STATIC_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_STATIC_CAST(T, expr) (static_cast(expr)) +#else + #define JSON_HEDLEY_STATIC_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_CPP_CAST) + #undef JSON_HEDLEY_CPP_CAST +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wold-style-cast") +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wold-style-cast\"") \ + ((T) (expr)) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# elif JSON_HEDLEY_IAR_VERSION_CHECK(8,3,0) +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("diag_suppress=Pe137") \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_CPP_CAST(T, expr) ((T) (expr)) +# endif +#else +# define JSON_HEDLEY_CPP_CAST(T, expr) (expr) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wdeprecated-declarations") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:1478 1786)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1216,1444,1445") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996)) +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:161)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068)) +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(16,9,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161") +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 161") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-attributes") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("clang diagnostic ignored \"-Wunknown-attributes\"") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("warning(disable:1292)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:1292)) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097,1098") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("error_messages(off,attrskipunsup)") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1173") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress=Pe1097") +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wcast-qual") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunused-function") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("clang diagnostic ignored \"-Wunused-function\"") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("GCC diagnostic ignored \"-Wunused-function\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(1,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION __pragma(warning(disable:4505)) +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("diag_suppress 3142") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#endif + +#if defined(JSON_HEDLEY_DEPRECATED) + #undef JSON_HEDLEY_DEPRECATED +#endif +#if defined(JSON_HEDLEY_DEPRECATED_FOR) + #undef JSON_HEDLEY_DEPRECATED_FOR +#endif +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement)) +#elif \ + (JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since))) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement))) +#elif defined(__cplusplus) && (__cplusplus >= 201402L) + #define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]]) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DEPRECATED(since) _Pragma("deprecated") + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma("deprecated") +#else + #define JSON_HEDLEY_DEPRECATED(since) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) +#endif + +#if defined(JSON_HEDLEY_UNAVAILABLE) + #undef JSON_HEDLEY_UNAVAILABLE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since))) +#else + #define JSON_HEDLEY_UNAVAILABLE(available_since) +#endif + +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT +#endif +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT_MSG) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__)) +#elif (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) +#elif defined(_Check_return_) /* SAL */ + #define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_ + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_ +#else + #define JSON_HEDLEY_WARN_UNUSED_RESULT + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) +#endif + +#if defined(JSON_HEDLEY_SENTINEL) + #undef JSON_HEDLEY_SENTINEL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position))) +#else + #define JSON_HEDLEY_SENTINEL(position) +#endif + +#if defined(JSON_HEDLEY_NO_RETURN) + #undef JSON_HEDLEY_NO_RETURN +#endif +#if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NO_RETURN __noreturn +#elif \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + #define JSON_HEDLEY_NO_RETURN _Noreturn +#elif defined(__cplusplus) && (__cplusplus >= 201103L) + #define JSON_HEDLEY_NO_RETURN JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(noreturn) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,2,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_NO_RETURN _Pragma("does_not_return") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NO_RETURN __attribute((noreturn)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#else + #define JSON_HEDLEY_NO_RETURN +#endif + +#if defined(JSON_HEDLEY_NO_ESCAPE) + #undef JSON_HEDLEY_NO_ESCAPE +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(noescape) + #define JSON_HEDLEY_NO_ESCAPE __attribute__((__noescape__)) +#else + #define JSON_HEDLEY_NO_ESCAPE +#endif + +#if defined(JSON_HEDLEY_UNREACHABLE) + #undef JSON_HEDLEY_UNREACHABLE +#endif +#if defined(JSON_HEDLEY_UNREACHABLE_RETURN) + #undef JSON_HEDLEY_UNREACHABLE_RETURN +#endif +#if defined(JSON_HEDLEY_ASSUME) + #undef JSON_HEDLEY_ASSUME +#endif +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_ASSUME(expr) __assume(expr) +#elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume) + #define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr) +#elif \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #if defined(__cplusplus) + #define JSON_HEDLEY_ASSUME(expr) std::_nassert(expr) + #else + #define JSON_HEDLEY_ASSUME(expr) _nassert(expr) + #endif +#endif +#if \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(JSON_HEDLEY_ARM_VERSION))) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(10,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable() +#elif defined(JSON_HEDLEY_ASSUME) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif +#if !defined(JSON_HEDLEY_ASSUME) + #if defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, ((expr) ? 1 : (JSON_HEDLEY_UNREACHABLE(), 1))) + #else + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, expr) + #endif +#endif +#if defined(JSON_HEDLEY_UNREACHABLE) + #if \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (JSON_HEDLEY_STATIC_CAST(void, JSON_HEDLEY_ASSUME(0)), (value)) + #else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) JSON_HEDLEY_UNREACHABLE() + #endif +#else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (value) +#endif +#if !defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif + +JSON_HEDLEY_DIAGNOSTIC_PUSH +#if JSON_HEDLEY_HAS_WARNING("-Wpedantic") + #pragma clang diagnostic ignored "-Wpedantic" +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat-pedantic") && defined(__cplusplus) + #pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#endif +#if JSON_HEDLEY_GCC_HAS_WARNING("-Wvariadic-macros",4,0,0) + #if defined(__clang__) + #pragma clang diagnostic ignored "-Wvariadic-macros" + #elif defined(JSON_HEDLEY_GCC_VERSION) + #pragma GCC diagnostic ignored "-Wvariadic-macros" + #endif +#endif +#if defined(JSON_HEDLEY_NON_NULL) + #undef JSON_HEDLEY_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__))) +#else + #define JSON_HEDLEY_NON_NULL(...) +#endif +JSON_HEDLEY_DIAGNOSTIC_POP + +#if defined(JSON_HEDLEY_PRINTF_FORMAT) + #undef JSON_HEDLEY_PRINTF_FORMAT +#endif +#if defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check))) +#elif defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check))) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(format) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check))) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check)) +#else + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) +#endif + +#if defined(JSON_HEDLEY_CONSTEXPR) + #undef JSON_HEDLEY_CONSTEXPR +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_CONSTEXPR JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(constexpr) + #endif +#endif +#if !defined(JSON_HEDLEY_CONSTEXPR) + #define JSON_HEDLEY_CONSTEXPR +#endif + +#if defined(JSON_HEDLEY_PREDICT) + #undef JSON_HEDLEY_PREDICT +#endif +#if defined(JSON_HEDLEY_LIKELY) + #undef JSON_HEDLEY_LIKELY +#endif +#if defined(JSON_HEDLEY_UNLIKELY) + #undef JSON_HEDLEY_UNLIKELY +#endif +#if defined(JSON_HEDLEY_UNPREDICTABLE) + #undef JSON_HEDLEY_UNPREDICTABLE +#endif +#if JSON_HEDLEY_HAS_BUILTIN(__builtin_unpredictable) + #define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr)) +#endif +#if \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) && !defined(JSON_HEDLEY_PGI_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability( (expr), (value), (probability)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1 , (probability)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0 , (probability)) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect (!!(expr), 1 ) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect (!!(expr), 0 ) +#elif \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PREDICT(expr, expected, probability) \ + (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (JSON_HEDLEY_STATIC_CAST(void, expected), (expr))) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \ + })) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \ + })) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0) +#else +# define JSON_HEDLEY_PREDICT(expr, expected, probability) (JSON_HEDLEY_STATIC_CAST(void, expected), (expr)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_LIKELY(expr) (!!(expr)) +# define JSON_HEDLEY_UNLIKELY(expr) (!!(expr)) +#endif +#if !defined(JSON_HEDLEY_UNPREDICTABLE) + #define JSON_HEDLEY_UNPREDICTABLE(expr) JSON_HEDLEY_PREDICT(expr, 1, 0.5) +#endif + +#if defined(JSON_HEDLEY_MALLOC) + #undef JSON_HEDLEY_MALLOC +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(malloc) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_MALLOC __attribute__((__malloc__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_MALLOC _Pragma("returns_new_memory") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_MALLOC __declspec(restrict) +#else + #define JSON_HEDLEY_MALLOC +#endif + +#if defined(JSON_HEDLEY_PURE) + #undef JSON_HEDLEY_PURE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(pure) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,96,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PURE __attribute__((__pure__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) +# define JSON_HEDLEY_PURE _Pragma("does_not_write_global_data") +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) \ + ) +# define JSON_HEDLEY_PURE _Pragma("FUNC_IS_PURE;") +#else +# define JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_CONST) + #undef JSON_HEDLEY_CONST +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(const) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_CONST __attribute__((__const__)) +#elif \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_CONST _Pragma("no_side_effect") +#else + #define JSON_HEDLEY_CONST JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_RESTRICT) + #undef JSON_HEDLEY_RESTRICT +#endif +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT restrict +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,4) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + defined(__clang__) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_RESTRICT __restrict +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT _Restrict +#else + #define JSON_HEDLEY_RESTRICT +#endif + +#if defined(JSON_HEDLEY_INLINE) + #undef JSON_HEDLEY_INLINE +#endif +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + (defined(__cplusplus) && (__cplusplus >= 199711L)) + #define JSON_HEDLEY_INLINE inline +#elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(6,2,0) + #define JSON_HEDLEY_INLINE __inline__ +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_INLINE __inline +#else + #define JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_ALWAYS_INLINE) + #undef JSON_HEDLEY_ALWAYS_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(always_inline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) +# define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_ALWAYS_INLINE __forceinline +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) \ + ) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("inline=forced") +#else +# define JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_NEVER_INLINE) + #undef JSON_HEDLEY_NEVER_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(noinline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("noinline") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("inline=never") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute((noinline)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#else + #define JSON_HEDLEY_NEVER_INLINE +#endif + +#if defined(JSON_HEDLEY_PRIVATE) + #undef JSON_HEDLEY_PRIVATE +#endif +#if defined(JSON_HEDLEY_PUBLIC) + #undef JSON_HEDLEY_PUBLIC +#endif +#if defined(JSON_HEDLEY_IMPORT) + #undef JSON_HEDLEY_IMPORT +#endif +#if defined(_WIN32) || defined(__CYGWIN__) +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC __declspec(dllexport) +# define JSON_HEDLEY_IMPORT __declspec(dllimport) +#else +# if \ + JSON_HEDLEY_HAS_ATTRIBUTE(visibility) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + ( \ + defined(__TI_EABI__) && \ + ( \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \ + ) \ + ) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PRIVATE __attribute__((__visibility__("hidden"))) +# define JSON_HEDLEY_PUBLIC __attribute__((__visibility__("default"))) +# else +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC +# endif +# define JSON_HEDLEY_IMPORT extern +#endif + +#if defined(JSON_HEDLEY_NO_THROW) + #undef JSON_HEDLEY_NO_THROW +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NO_THROW __declspec(nothrow) +#else + #define JSON_HEDLEY_NO_THROW +#endif + +#if defined(JSON_HEDLEY_FALL_THROUGH) + #undef JSON_HEDLEY_FALL_THROUGH +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(fallthrough) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__)) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]]) +#elif defined(__fallthrough) /* SAL */ + #define JSON_HEDLEY_FALL_THROUGH __fallthrough +#else + #define JSON_HEDLEY_FALL_THROUGH +#endif + +#if defined(JSON_HEDLEY_RETURNS_NON_NULL) + #undef JSON_HEDLEY_RETURNS_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__)) +#elif defined(_Ret_notnull_) /* SAL */ + #define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_ +#else + #define JSON_HEDLEY_RETURNS_NON_NULL +#endif + +#if defined(JSON_HEDLEY_ARRAY_PARAM) + #undef JSON_HEDLEY_ARRAY_PARAM +#endif +#if \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ + !defined(__STDC_NO_VLA__) && \ + !defined(__cplusplus) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_ARRAY_PARAM(name) (name) +#else + #define JSON_HEDLEY_ARRAY_PARAM(name) +#endif + +#if defined(JSON_HEDLEY_IS_CONSTANT) + #undef JSON_HEDLEY_IS_CONSTANT +#endif +#if defined(JSON_HEDLEY_REQUIRE_CONSTEXPR) + #undef JSON_HEDLEY_REQUIRE_CONSTEXPR +#endif +/* JSON_HEDLEY_IS_CONSTEXPR_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #undef JSON_HEDLEY_IS_CONSTEXPR_ +#endif +#if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr) +#endif +#if !defined(__cplusplus) +# if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,24) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*) +#else + #include + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*) +#endif +# elif \ + ( \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ + !defined(JSON_HEDLEY_SUNPRO_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION)) || \ + (JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,3,0) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0) +#else + #include + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0) +#endif +# elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + defined(JSON_HEDLEY_INTEL_VERSION) || \ + defined(JSON_HEDLEY_TINYC_VERSION) || \ + defined(JSON_HEDLEY_TI_ARMCL_VERSION) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(18,12,0) || \ + defined(JSON_HEDLEY_TI_CL2000_VERSION) || \ + defined(JSON_HEDLEY_TI_CL6X_VERSION) || \ + defined(JSON_HEDLEY_TI_CL7X_VERSION) || \ + defined(JSON_HEDLEY_TI_CLPRU_VERSION) || \ + defined(__clang__) +# define JSON_HEDLEY_IS_CONSTEXPR_(expr) ( \ + sizeof(void) != \ + sizeof(*( \ + 1 ? \ + ((void*) ((expr) * 0L) ) : \ +((struct { char v[sizeof(void) * 2]; } *) 1) \ + ) \ + ) \ + ) +# endif +#endif +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) JSON_HEDLEY_IS_CONSTEXPR_(expr) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (JSON_HEDLEY_IS_CONSTEXPR_(expr) ? (expr) : (-1)) +#else + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) (0) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (expr) +#endif + +#if defined(JSON_HEDLEY_BEGIN_C_DECLS) + #undef JSON_HEDLEY_BEGIN_C_DECLS +#endif +#if defined(JSON_HEDLEY_END_C_DECLS) + #undef JSON_HEDLEY_END_C_DECLS +#endif +#if defined(JSON_HEDLEY_C_DECL) + #undef JSON_HEDLEY_C_DECL +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_BEGIN_C_DECLS extern "C" { + #define JSON_HEDLEY_END_C_DECLS } + #define JSON_HEDLEY_C_DECL extern "C" +#else + #define JSON_HEDLEY_BEGIN_C_DECLS + #define JSON_HEDLEY_END_C_DECLS + #define JSON_HEDLEY_C_DECL +#endif + +#if defined(JSON_HEDLEY_STATIC_ASSERT) + #undef JSON_HEDLEY_STATIC_ASSERT +#endif +#if \ + !defined(__cplusplus) && ( \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \ + (JSON_HEDLEY_HAS_FEATURE(c_static_assert) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + defined(_Static_assert) \ + ) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message) +#elif \ + (defined(__cplusplus) && (__cplusplus >= 201103L)) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message)) +#else +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) +#endif + +#if defined(JSON_HEDLEY_NULL) + #undef JSON_HEDLEY_NULL +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_NULL JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(nullptr) + #elif defined(NULL) + #define JSON_HEDLEY_NULL NULL + #else + #define JSON_HEDLEY_NULL JSON_HEDLEY_STATIC_CAST(void*, 0) + #endif +#elif defined(NULL) + #define JSON_HEDLEY_NULL NULL +#else + #define JSON_HEDLEY_NULL ((void*) 0) +#endif + +#if defined(JSON_HEDLEY_MESSAGE) + #undef JSON_HEDLEY_MESSAGE +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_MESSAGE(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(message msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message msg) +#elif JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(_CRI message msg) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_WARNING) + #undef JSON_HEDLEY_WARNING +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_WARNING(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(clang warning msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,8,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_REQUIRE) + #undef JSON_HEDLEY_REQUIRE +#endif +#if defined(JSON_HEDLEY_REQUIRE_MSG) + #undef JSON_HEDLEY_REQUIRE_MSG +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(diagnose_if) +# if JSON_HEDLEY_HAS_WARNING("-Wgcc-compat") +# define JSON_HEDLEY_REQUIRE(expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), #expr, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), msg, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_REQUIRE(expr) __attribute__((diagnose_if(!(expr), #expr, "error"))) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) __attribute__((diagnose_if(!(expr), msg, "error"))) +# endif +#else +# define JSON_HEDLEY_REQUIRE(expr) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) +#endif + +#if defined(JSON_HEDLEY_FLAGS) + #undef JSON_HEDLEY_FLAGS +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) && (!defined(__cplusplus) || JSON_HEDLEY_HAS_WARNING("-Wbitfield-enum-conversion")) + #define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__)) +#else + #define JSON_HEDLEY_FLAGS +#endif + +#if defined(JSON_HEDLEY_FLAGS_CAST) + #undef JSON_HEDLEY_FLAGS_CAST +#endif +#if JSON_HEDLEY_INTEL_VERSION_CHECK(19,0,0) +# define JSON_HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("warning(disable:188)") \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_FLAGS_CAST(T, expr) JSON_HEDLEY_STATIC_CAST(T, expr) +#endif + +#if defined(JSON_HEDLEY_EMPTY_BASES) + #undef JSON_HEDLEY_EMPTY_BASES +#endif +#if \ + (JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0)) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_EMPTY_BASES __declspec(empty_bases) +#else + #define JSON_HEDLEY_EMPTY_BASES +#endif + +/* Remaining macros are deprecated. */ + +#if defined(JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK +#endif +#if defined(__clang__) + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0) +#else + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_CLANG_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_BUILTIN) + #undef JSON_HEDLEY_CLANG_HAS_BUILTIN +#endif +#define JSON_HEDLEY_CLANG_HAS_BUILTIN(builtin) JSON_HEDLEY_HAS_BUILTIN(builtin) + +#if defined(JSON_HEDLEY_CLANG_HAS_FEATURE) + #undef JSON_HEDLEY_CLANG_HAS_FEATURE +#endif +#define JSON_HEDLEY_CLANG_HAS_FEATURE(feature) JSON_HEDLEY_HAS_FEATURE(feature) + +#if defined(JSON_HEDLEY_CLANG_HAS_EXTENSION) + #undef JSON_HEDLEY_CLANG_HAS_EXTENSION +#endif +#define JSON_HEDLEY_CLANG_HAS_EXTENSION(extension) JSON_HEDLEY_HAS_EXTENSION(extension) + +#if defined(JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_WARNING) + #undef JSON_HEDLEY_CLANG_HAS_WARNING +#endif +#define JSON_HEDLEY_CLANG_HAS_WARNING(warning) JSON_HEDLEY_HAS_WARNING(warning) + +#endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */ diff --git a/lib/json/include/nlohmann/thirdparty/hedley/hedley_undef.hpp b/lib/json/include/nlohmann/thirdparty/hedley/hedley_undef.hpp new file mode 100644 index 0000000..d2b37a1 --- /dev/null +++ b/lib/json/include/nlohmann/thirdparty/hedley/hedley_undef.hpp @@ -0,0 +1,150 @@ +#pragma once + +#undef JSON_HEDLEY_ALWAYS_INLINE +#undef JSON_HEDLEY_ARM_VERSION +#undef JSON_HEDLEY_ARM_VERSION_CHECK +#undef JSON_HEDLEY_ARRAY_PARAM +#undef JSON_HEDLEY_ASSUME +#undef JSON_HEDLEY_BEGIN_C_DECLS +#undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE +#undef JSON_HEDLEY_CLANG_HAS_BUILTIN +#undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE +#undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE +#undef JSON_HEDLEY_CLANG_HAS_EXTENSION +#undef JSON_HEDLEY_CLANG_HAS_FEATURE +#undef JSON_HEDLEY_CLANG_HAS_WARNING +#undef JSON_HEDLEY_COMPCERT_VERSION +#undef JSON_HEDLEY_COMPCERT_VERSION_CHECK +#undef JSON_HEDLEY_CONCAT +#undef JSON_HEDLEY_CONCAT3 +#undef JSON_HEDLEY_CONCAT3_EX +#undef JSON_HEDLEY_CONCAT_EX +#undef JSON_HEDLEY_CONST +#undef JSON_HEDLEY_CONSTEXPR +#undef JSON_HEDLEY_CONST_CAST +#undef JSON_HEDLEY_CPP_CAST +#undef JSON_HEDLEY_CRAY_VERSION +#undef JSON_HEDLEY_CRAY_VERSION_CHECK +#undef JSON_HEDLEY_C_DECL +#undef JSON_HEDLEY_DEPRECATED +#undef JSON_HEDLEY_DEPRECATED_FOR +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#undef JSON_HEDLEY_DIAGNOSTIC_POP +#undef JSON_HEDLEY_DIAGNOSTIC_PUSH +#undef JSON_HEDLEY_DMC_VERSION +#undef JSON_HEDLEY_DMC_VERSION_CHECK +#undef JSON_HEDLEY_EMPTY_BASES +#undef JSON_HEDLEY_EMSCRIPTEN_VERSION +#undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK +#undef JSON_HEDLEY_END_C_DECLS +#undef JSON_HEDLEY_FLAGS +#undef JSON_HEDLEY_FLAGS_CAST +#undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE +#undef JSON_HEDLEY_GCC_HAS_BUILTIN +#undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE +#undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE +#undef JSON_HEDLEY_GCC_HAS_EXTENSION +#undef JSON_HEDLEY_GCC_HAS_FEATURE +#undef JSON_HEDLEY_GCC_HAS_WARNING +#undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK +#undef JSON_HEDLEY_GCC_VERSION +#undef JSON_HEDLEY_GCC_VERSION_CHECK +#undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE +#undef JSON_HEDLEY_GNUC_HAS_BUILTIN +#undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE +#undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE +#undef JSON_HEDLEY_GNUC_HAS_EXTENSION +#undef JSON_HEDLEY_GNUC_HAS_FEATURE +#undef JSON_HEDLEY_GNUC_HAS_WARNING +#undef JSON_HEDLEY_GNUC_VERSION +#undef JSON_HEDLEY_GNUC_VERSION_CHECK +#undef JSON_HEDLEY_HAS_ATTRIBUTE +#undef JSON_HEDLEY_HAS_BUILTIN +#undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE +#undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS +#undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE +#undef JSON_HEDLEY_HAS_EXTENSION +#undef JSON_HEDLEY_HAS_FEATURE +#undef JSON_HEDLEY_HAS_WARNING +#undef JSON_HEDLEY_IAR_VERSION +#undef JSON_HEDLEY_IAR_VERSION_CHECK +#undef JSON_HEDLEY_IBM_VERSION +#undef JSON_HEDLEY_IBM_VERSION_CHECK +#undef JSON_HEDLEY_IMPORT +#undef JSON_HEDLEY_INLINE +#undef JSON_HEDLEY_INTEL_CL_VERSION +#undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK +#undef JSON_HEDLEY_INTEL_VERSION +#undef JSON_HEDLEY_INTEL_VERSION_CHECK +#undef JSON_HEDLEY_IS_CONSTANT +#undef JSON_HEDLEY_IS_CONSTEXPR_ +#undef JSON_HEDLEY_LIKELY +#undef JSON_HEDLEY_MALLOC +#undef JSON_HEDLEY_MCST_LCC_VERSION +#undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK +#undef JSON_HEDLEY_MESSAGE +#undef JSON_HEDLEY_MSVC_VERSION +#undef JSON_HEDLEY_MSVC_VERSION_CHECK +#undef JSON_HEDLEY_NEVER_INLINE +#undef JSON_HEDLEY_NON_NULL +#undef JSON_HEDLEY_NO_ESCAPE +#undef JSON_HEDLEY_NO_RETURN +#undef JSON_HEDLEY_NO_THROW +#undef JSON_HEDLEY_NULL +#undef JSON_HEDLEY_PELLES_VERSION +#undef JSON_HEDLEY_PELLES_VERSION_CHECK +#undef JSON_HEDLEY_PGI_VERSION +#undef JSON_HEDLEY_PGI_VERSION_CHECK +#undef JSON_HEDLEY_PREDICT +#undef JSON_HEDLEY_PRINTF_FORMAT +#undef JSON_HEDLEY_PRIVATE +#undef JSON_HEDLEY_PUBLIC +#undef JSON_HEDLEY_PURE +#undef JSON_HEDLEY_REINTERPRET_CAST +#undef JSON_HEDLEY_REQUIRE +#undef JSON_HEDLEY_REQUIRE_CONSTEXPR +#undef JSON_HEDLEY_REQUIRE_MSG +#undef JSON_HEDLEY_RESTRICT +#undef JSON_HEDLEY_RETURNS_NON_NULL +#undef JSON_HEDLEY_SENTINEL +#undef JSON_HEDLEY_STATIC_ASSERT +#undef JSON_HEDLEY_STATIC_CAST +#undef JSON_HEDLEY_STRINGIFY +#undef JSON_HEDLEY_STRINGIFY_EX +#undef JSON_HEDLEY_SUNPRO_VERSION +#undef JSON_HEDLEY_SUNPRO_VERSION_CHECK +#undef JSON_HEDLEY_TINYC_VERSION +#undef JSON_HEDLEY_TINYC_VERSION_CHECK +#undef JSON_HEDLEY_TI_ARMCL_VERSION +#undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK +#undef JSON_HEDLEY_TI_CL2000_VERSION +#undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK +#undef JSON_HEDLEY_TI_CL430_VERSION +#undef JSON_HEDLEY_TI_CL430_VERSION_CHECK +#undef JSON_HEDLEY_TI_CL6X_VERSION +#undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK +#undef JSON_HEDLEY_TI_CL7X_VERSION +#undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK +#undef JSON_HEDLEY_TI_CLPRU_VERSION +#undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK +#undef JSON_HEDLEY_TI_VERSION +#undef JSON_HEDLEY_TI_VERSION_CHECK +#undef JSON_HEDLEY_UNAVAILABLE +#undef JSON_HEDLEY_UNLIKELY +#undef JSON_HEDLEY_UNPREDICTABLE +#undef JSON_HEDLEY_UNREACHABLE +#undef JSON_HEDLEY_UNREACHABLE_RETURN +#undef JSON_HEDLEY_VERSION +#undef JSON_HEDLEY_VERSION_DECODE_MAJOR +#undef JSON_HEDLEY_VERSION_DECODE_MINOR +#undef JSON_HEDLEY_VERSION_DECODE_REVISION +#undef JSON_HEDLEY_VERSION_ENCODE +#undef JSON_HEDLEY_WARNING +#undef JSON_HEDLEY_WARN_UNUSED_RESULT +#undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG +#undef JSON_HEDLEY_FALL_THROUGH diff --git a/src/commands.cpp b/src/commands.cpp index c58932f..aa41a2a 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -1,8 +1,8 @@ #include "commands.hpp" #include "config.hpp" #include "filesystem.hpp" +#include "json.hpp" #include "terminal.hpp" -#include void Commands::load() { auto commands_file = Config::get().home_juci_path / "commands.json"; @@ -27,10 +27,9 @@ void Commands::load() { commands.clear(); try { - boost::property_tree::ptree pt; - boost::property_tree::json_parser::read_json(commands_file.string(), pt); - for(auto command_it = pt.begin(); command_it != pt.end(); ++command_it) { - auto key_string = command_it->second.get("key"); + JSON commands_json(commands_file); + for(auto &command : commands_json.array()) { + auto key_string = command.string("key"); guint key = 0; GdkModifierType modifier = static_cast(0); if(!key_string.empty()) { @@ -38,13 +37,13 @@ void Commands::load() { if(key == 0 && modifier == 0) Terminal::get().async_print("\e[31mError\e[m: could not parse key string: " + key_string + "\n", true); } - auto path = command_it->second.get("path", ""); + auto path = command.string_or("path", ""); boost::optional regex; if(!path.empty()) regex = std::regex(path, std::regex::optimize); commands.emplace_back(Command{key, modifier, std::move(regex), - command_it->second.get("compile", ""), command_it->second.get("run"), - command_it->second.get("debug", false), command_it->second.get("debug_remote_host", "")}); + command.string_or("compile", ""), command.string("run"), + command.boolean_or("debug", false), command.string_or("debug_remote_host", "")}); } } catch(const std::exception &e) { diff --git a/src/compile_commands.cpp b/src/compile_commands.cpp index 88e598b..731f95b 100644 --- a/src/compile_commands.cpp +++ b/src/compile_commands.cpp @@ -2,10 +2,10 @@ #include "clangmm.hpp" #include "config.hpp" #include "filesystem.hpp" +#include "json.hpp" #include "terminal.hpp" #include "utility.hpp" #include -#include #include CompileCommands::FindSystemIncludePaths::FindSystemIncludePaths() { @@ -55,14 +55,11 @@ std::vector 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("directory"); - auto parameters_str = command.second.get("command"); - boost::filesystem::path file = command.second.get("file"); + JSON compile_commands(build_path / "compile_commands.json"); + for(auto &command : compile_commands.array()) { + boost::filesystem::path directory = command.string("directory"); + auto parameters_str = command.string("command"); + boost::filesystem::path file = command.string("file"); std::vector parameters; bool backslash = false; @@ -77,7 +74,7 @@ CompileCommands::CompileCommands(const boost::filesystem::path &build_path) { if(parameter[c] == '\\') parameter.replace(c, 2, std::string() + parameter[c + 1]); } - parameters.emplace_back(parameter); + parameters.emplace_back(std::move(parameter)); }; for(size_t c = 0; c < parameters_str.size(); ++c) { if(backslash) @@ -108,7 +105,7 @@ CompileCommands::CompileCommands(const boost::filesystem::path &build_path) { if(parameter_start_pos != std::string::npos) add_parameter(); - commands.emplace_back(Command{directory, parameters, filesystem::get_absolute_path(file, build_path)}); + commands.emplace_back(Command{std::move(directory), std::move(parameters), filesystem::get_absolute_path(file, build_path)}); } } catch(...) { diff --git a/src/config.cpp b/src/config.cpp index fde9cec..283955f 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -1,7 +1,6 @@ #include "config.hpp" #include "files.hpp" #include "filesystem.hpp" -#include "json.hpp" #include "terminal.hpp" #include #include @@ -38,8 +37,7 @@ void Config::load() { if(!boost::filesystem::exists(juci_style_path)) filesystem::write(juci_style_path, juci_dark_blue_style); - boost::property_tree::ptree cfg; - boost::property_tree::json_parser::read_json(config_json.string(), cfg); + JSON cfg(config_json); update(cfg); read(cfg); } @@ -47,59 +45,40 @@ void Config::load() { dispatcher.post([config_json = std::move(config_json), e_what = std::string(e.what())] { ::Terminal::get().print("\e[31mError\e[m: could not parse " + filesystem::get_short_path(config_json).string() + ": " + e_what + "\n", true); }); - std::stringstream ss; - ss << default_config_file; - boost::property_tree::ptree cfg; - boost::property_tree::read_json(ss, cfg); - read(cfg); + JSON default_cfg(default_config_file); + read(default_cfg); } } -void Config::update(boost::property_tree::ptree &cfg) { - boost::property_tree::ptree default_cfg; - bool cfg_ok = true; - if(cfg.get("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 +void Config::update(JSON &cfg) { + auto version = cfg.string("version"); + if(version == JUCI_VERSION) return; - cfg_ok &= add_missing_nodes(cfg, default_cfg); - cfg_ok &= remove_deprecated_nodes(cfg, default_cfg); - if(!cfg_ok) { - auto path = home_juci_path / "config" / "config.json"; - std::ofstream output(path.string(), std::ios::binary); - if(output) { - JSON::write(output, cfg); - output << '\n'; - } - else - std::cerr << "Error writing config file: " << filesystem::get_short_path(path).string() << std::endl; - } + JSON default_cfg(default_config_file); + make_version_dependent_corrections(cfg, default_cfg, version); + cfg.set("version", JUCI_VERSION); + + add_missing_nodes(cfg, default_cfg); + remove_deprecated_nodes(cfg, default_cfg); + + cfg.to_file(home_juci_path / "config" / "config.json", 2); + + 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); } -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"); +void Config::make_version_dependent_corrections(JSON &cfg, const JSON &default_cfg, const std::string &version) { 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() == "p") { + auto keybindings = cfg.object("keybindings"); + auto print = keybindings.string_optional("print"); + if(print && *print == "p") { + keybindings.set("print", ""); dispatcher.post([] { ::Terminal::get().print("Preference change: keybindings.print set to \"\"\n"); }); - it_file_print->second.data() = ""; } } } @@ -108,118 +87,113 @@ void Config::make_version_dependent_corrections(boost::property_tree::ptree &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::add_missing_nodes(JSON &cfg, const JSON &default_cfg) { + for(auto &default_cfg_child : default_cfg.children_or_empty()) { try { - cfg.get(path); + auto cfg_child = cfg.child(default_cfg_child.first); + add_missing_nodes(cfg_child, default_cfg_child.second); } - catch(const std::exception &e) { - cfg.add(path, node.second.data()); - unchanged = false; + catch(...) { + cfg.set(default_cfg_child.first, default_cfg_child.second); } - 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; +void Config::remove_deprecated_nodes(JSON &cfg, const JSON &default_cfg) { + auto children = cfg.children_or_empty(); + for(size_t i = 0; i < children.size();) { try { - default_cfg.get(path); - unchanged &= remove_deprecated_nodes(it->second, default_cfg, path); - ++it; + auto default_cfg_child = default_cfg.child(children[i].first); + remove_deprecated_nodes(children[i].second, default_cfg_child); + ++i; } - catch(const std::exception &e) { - it = cfg.erase(it); - unchanged = false; + catch(...) { + cfg.remove(children[i].first); + children = cfg.children_or_empty(); } } - 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(); - } - - auto source_json = cfg.get_child("source"); - source.style = source_json.get("style"); - source.font = source_json.get("font"); - source.cleanup_whitespace_characters = source_json.get("cleanup_whitespace_characters"); - source.show_whitespace_characters = source_json.get("show_whitespace_characters"); - source.format_style_on_save = source_json.get("format_style_on_save"); - source.format_style_on_save_if_style_file_found = source_json.get("format_style_on_save_if_style_file_found"); - source.smart_brackets = source_json.get("smart_brackets"); - source.smart_inserts = source_json.get("smart_inserts"); +void Config::read(const JSON &cfg) { + for(auto &keybinding : cfg.children("keybindings")) + menu.keys[keybinding.first] = keybinding.second.string(); + + auto source_json = cfg.object("source"); + source.style = source_json.string("style"); + source.font = source_json.string("font"); + source.cleanup_whitespace_characters = source_json.boolean("cleanup_whitespace_characters", JSON::ParseOptions::accept_string); + source.show_whitespace_characters = source_json.string("show_whitespace_characters"); + source.format_style_on_save = source_json.boolean("format_style_on_save", JSON::ParseOptions::accept_string); + source.format_style_on_save_if_style_file_found = source_json.boolean("format_style_on_save_if_style_file_found", JSON::ParseOptions::accept_string); + source.smart_brackets = source_json.boolean("smart_brackets", JSON::ParseOptions::accept_string); + source.smart_inserts = source_json.boolean("smart_inserts", JSON::ParseOptions::accept_string); if(source.smart_inserts) source.smart_brackets = true; - source.show_map = source_json.get("show_map"); - source.map_font_size = source_json.get("map_font_size"); - source.show_git_diff = source_json.get("show_git_diff"); - source.show_background_pattern = source_json.get("show_background_pattern"); - source.show_right_margin = source_json.get("show_right_margin"); - source.right_margin_position = source_json.get("right_margin_position"); - source.spellcheck_language = source_json.get("spellcheck_language"); - source.default_tab_char = source_json.get("default_tab_char"); - source.default_tab_size = source_json.get("default_tab_size"); - source.auto_tab_char_and_size = source_json.get("auto_tab_char_and_size"); - source.tab_indents_line = source_json.get("tab_indents_line"); - source.word_wrap = source_json.get("word_wrap"); - source.highlight_current_line = source_json.get("highlight_current_line"); - source.show_line_numbers = source_json.get("show_line_numbers"); - source.enable_multiple_cursors = source_json.get("enable_multiple_cursors"); - source.auto_reload_changed_files = source_json.get("auto_reload_changed_files"); - source.search_for_selection = source_json.get("search_for_selection"); - source.clang_format_style = source_json.get("clang_format_style"); - source.clang_usages_threads = static_cast(source_json.get("clang_usages_threads")); - source.clang_tidy_enable = source_json.get("clang_tidy_enable"); - source.clang_tidy_checks = source_json.get("clang_tidy_checks"); - source.clang_detailed_preprocessing_record = source_json.get("clang_detailed_preprocessing_record"); - source.debug_place_cursor_at_stop = source_json.get("debug_place_cursor_at_stop"); - 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("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(); - } + source.show_map = source_json.boolean("show_map", JSON::ParseOptions::accept_string); + source.map_font_size = source_json.integer("map_font_size", JSON::ParseOptions::accept_string); + source.show_git_diff = source_json.boolean("show_git_diff", JSON::ParseOptions::accept_string); + source.show_background_pattern = source_json.boolean("show_background_pattern", JSON::ParseOptions::accept_string); + source.show_right_margin = source_json.boolean("show_right_margin", JSON::ParseOptions::accept_string); + source.right_margin_position = source_json.integer("right_margin_position", JSON::ParseOptions::accept_string); + source.spellcheck_language = source_json.string("spellcheck_language"); + auto default_tab_char_str = source_json.string("default_tab_char"); + if(default_tab_char_str.size() == 1) + source.default_tab_char = default_tab_char_str[0]; + else + source.default_tab_char = ' '; + source.default_tab_size = source_json.integer("default_tab_size", JSON::ParseOptions::accept_string); + source.auto_tab_char_and_size = source_json.boolean("auto_tab_char_and_size", JSON::ParseOptions::accept_string); + source.tab_indents_line = source_json.boolean("tab_indents_line", JSON::ParseOptions::accept_string); + source.word_wrap = source_json.string("word_wrap"); + source.highlight_current_line = source_json.boolean("highlight_current_line", JSON::ParseOptions::accept_string); + source.show_line_numbers = source_json.boolean("show_line_numbers", JSON::ParseOptions::accept_string); + source.enable_multiple_cursors = source_json.boolean("enable_multiple_cursors", JSON::ParseOptions::accept_string); + source.auto_reload_changed_files = source_json.boolean("auto_reload_changed_files", JSON::ParseOptions::accept_string); + source.search_for_selection = source_json.boolean("search_for_selection", JSON::ParseOptions::accept_string); + source.clang_format_style = source_json.string("clang_format_style"); + source.clang_usages_threads = static_cast(source_json.integer("clang_usages_threads", JSON::ParseOptions::accept_string)); + source.clang_tidy_enable = source_json.boolean("clang_tidy_enable", JSON::ParseOptions::accept_string); + source.clang_tidy_checks = source_json.string("clang_tidy_checks"); + source.clang_detailed_preprocessing_record = source_json.boolean("clang_detailed_preprocessing_record", JSON::ParseOptions::accept_string); + source.debug_place_cursor_at_stop = source_json.boolean("debug_place_cursor_at_stop", JSON::ParseOptions::accept_string); + + for(auto &documentation_searches : cfg.children("documentation_searches")) { + auto &documentation_search = source.documentation_searches[documentation_searches.first]; + documentation_search.separator = documentation_searches.second.string("separator"); + for(auto &i : documentation_searches.second.children("queries")) + documentation_search.queries[i.first] = i.second.string(); } - version = cfg.get("version"); - - theme.name = cfg.get("gtk_theme.name"); - theme.variant = cfg.get("gtk_theme.variant"); - theme.font = cfg.get("gtk_theme.font"); - - project.default_build_path = cfg.get("project.default_build_path"); - project.debug_build_path = cfg.get("project.debug_build_path"); - project.cmake.command = cfg.get("project.cmake.command"); - project.cmake.compile_command = cfg.get("project.cmake.compile_command"); - project.meson.command = cfg.get("project.meson.command"); - project.meson.compile_command = cfg.get("project.meson.compile_command"); - project.default_build_management_system = cfg.get("project.default_build_management_system"); - project.save_on_compile_or_run = cfg.get("project.save_on_compile_or_run"); - project.ctags_command = cfg.get("project.ctags_command"); - project.grep_command = cfg.get("project.grep_command"); - project.cargo_command = cfg.get("project.cargo_command"); - project.python_command = cfg.get("project.python_command"); - project.markdown_command = cfg.get("project.markdown_command"); - - terminal.history_size = cfg.get("terminal.history_size"); - terminal.font = cfg.get("terminal.font"); - terminal.clear_on_compile = cfg.get("terminal.clear_on_compile"); - terminal.clear_on_run_command = cfg.get("terminal.clear_on_run_command"); - terminal.hide_entry_on_run_command = cfg.get("terminal.hide_entry_on_run_command"); - - log.libclang = cfg.get("log.libclang"); - log.language_server = cfg.get("log.language_server"); + auto theme_json = cfg.object("gtk_theme"); + theme.name = theme_json.string("name"); + theme.variant = theme_json.string("variant"); + theme.font = theme_json.string("font"); + + auto project_json = cfg.object("project"); + project.default_build_path = project_json.string("default_build_path"); + project.debug_build_path = project_json.string("debug_build_path"); + auto cmake_json = project_json.object("cmake"); + project.cmake.command = cmake_json.string("command"); + project.cmake.compile_command = cmake_json.string("compile_command"); + auto meson_json = project_json.object("meson"); + project.meson.command = meson_json.string("command"); + project.meson.compile_command = meson_json.string("compile_command"); + project.default_build_management_system = project_json.string("default_build_management_system"); + project.save_on_compile_or_run = project_json.boolean("save_on_compile_or_run", JSON::ParseOptions::accept_string); + project.ctags_command = project_json.string("ctags_command"); + project.grep_command = project_json.string("grep_command"); + project.cargo_command = project_json.string("cargo_command"); + project.python_command = project_json.string("python_command"); + project.markdown_command = project_json.string("markdown_command"); + + auto terminal_json = cfg.object("terminal"); + terminal.history_size = terminal_json.integer("history_size", JSON::ParseOptions::accept_string); + terminal.font = terminal_json.string("font"); + terminal.clear_on_compile = terminal_json.boolean("clear_on_compile", JSON::ParseOptions::accept_string); + terminal.clear_on_run_command = terminal_json.boolean("clear_on_run_command", JSON::ParseOptions::accept_string); + terminal.hide_entry_on_run_command = terminal_json.boolean("hide_entry_on_run_command", JSON::ParseOptions::accept_string); + + auto log_json = cfg.object("log"); + log.libclang = log_json.boolean("libclang", JSON::ParseOptions::accept_string); + log.language_server = log_json.boolean("language_server", JSON::ParseOptions::accept_string); } diff --git a/src/config.hpp b/src/config.hpp index 36999d7..fd7f4c4 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -1,7 +1,7 @@ #pragma once #include "dispatcher.hpp" +#include "json.hpp" #include -#include #include #include #include @@ -125,7 +125,6 @@ public: void load(); - std::string version; Menu menu; Theme theme; Terminal terminal; @@ -140,9 +139,9 @@ private: /// Used to dispatch Terminal outputs after juCi++ GUI setup and configuration Dispatcher dispatcher; - 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); + void update(JSON &cfg); + void make_version_dependent_corrections(JSON &cfg, const JSON &default_cfg, const std::string &version); + void add_missing_nodes(JSON &cfg, const JSON &default_cfg); + void remove_deprecated_nodes(JSON &cfg, const JSON &default_cfg); + void read(const JSON &cfg); }; diff --git a/src/json.cpp b/src/json.cpp index 42a9bbc..3d9e222 100644 --- a/src/json.cpp +++ b/src/json.cpp @@ -1,71 +1,11 @@ +#include "nlohmann/json.hpp" #include "json.hpp" #include #include +#include #include #include -void JSON::write_json_internal(std::ostream &stream, const boost::property_tree::ptree &pt, bool pretty, const std::vector ¬_string_keys, size_t column, const std::string &key) { - // Based on boost::property_tree::json_parser::write_json_helper() - - if(pt.empty()) { // Value - auto value = pt.get_value(); - if(std::any_of(not_string_keys.begin(), not_string_keys.end(), [&key](const std::string ¬_string_key) { - return key == not_string_key; - })) - stream << value; - else - stream << '"' << escape_string(value) << '"'; - } - else if(pt.count(std::string{}) == pt.size()) { // Array - stream << '['; - if(pretty) - stream << '\n'; - - for(auto it = pt.begin(); it != pt.end(); ++it) { - if(pretty) - stream << std::string((column + 1) * 2, ' '); - - write_json_internal(stream, it->second, pretty, not_string_keys, column + 1, key); - - if(std::next(it) != pt.end()) - stream << ','; - if(pretty) - stream << '\n'; - } - - if(pretty) - stream << std::string(column * 2, ' '); - stream << ']'; - } - else { // Object - stream << '{'; - if(pretty) - stream << '\n'; - - for(auto it = pt.begin(); it != pt.end(); ++it) { - if(pretty) - stream << std::string((column + 1) * 2, ' '); - - stream << '"' << escape_string(it->first) << "\":"; - if(pretty) - stream << ' '; - write_json_internal(stream, it->second, pretty, not_string_keys, column + 1, it->first); - - if(std::next(it) != pt.end()) - stream << ','; - if(pretty) - stream << '\n'; - } - - if(pretty) - stream << std::string(column * 2, ' '); - stream << '}'; - } -} -void JSON::write(std::ostream &stream, const boost::property_tree::ptree &pt, bool pretty, const std::vector ¬_string_keys) { - write_json_internal(stream, pt, pretty, not_string_keys, 0, ""); -} - std::string JSON::escape_string(std::string string) { for(size_t c = 0; c < string.size(); ++c) { if(string[c] == '\b') { @@ -99,3 +39,459 @@ std::string JSON::escape_string(std::string string) { } return string; } + +JSON::JSON(StructureType type) noexcept : ptr(type == StructureType::object ? new nlohmann::ordered_json() : new nlohmann::ordered_json(nlohmann::ordered_json::array())), owner(true) {} + +JSON::JSON(const std::string &string) : ptr(new nlohmann::ordered_json(nlohmann::ordered_json::parse(string))), owner(true) {} + +JSON::JSON(const char *c_str) : ptr(new nlohmann::ordered_json(nlohmann::ordered_json::parse(c_str))), owner(true) {} + +JSON::JSON(std::istream &istream) : ptr(new nlohmann::ordered_json(nlohmann::ordered_json::parse(istream))), owner(true) {} + +JSON::JSON(const boost::filesystem::path &path) { + std::ifstream input(path.string(), std::ios::binary); + if(!input) + throw std::runtime_error("could not open file " + path.string()); + ptr = new nlohmann::ordered_json(nlohmann::ordered_json::parse(input)); + owner = true; +} + +JSON::JSON(JSON &&other) noexcept : ptr(other.ptr), owner(other.owner) { + other.owner = false; +} + +JSON &JSON::operator=(JSON &&other) noexcept { + if(owner) + delete ptr; + ptr = other.ptr; + owner = other.owner; + other.owner = false; + return *this; +} + +JSON::~JSON() { + if(owner) + delete ptr; +} + +JSON JSON::make_owner(JSON &&other) noexcept { + auto owner = JSON(new nlohmann::ordered_json(std::move(*other.ptr))); + owner.owner = true; + other.owner = false; + return owner; +} + +std::ostream &operator<<(std::ostream &os, const JSON &json) { + return os << *json.ptr; +} + +std::string JSON::to_string(int indent) const { + return ptr->dump(indent); +} + +void JSON::to_file(const boost::filesystem::path &path, int indent) const { + std::ofstream file(path.string(), std::ios::binary); + if(!file) + throw std::runtime_error("could not open file " + path.string()); + if(indent != -1) + file << std::setw(indent); + file << *ptr << '\n'; +} + +void JSON::set(const std::string &key, std::string value) noexcept { + (*ptr)[key] = std::move(value); +} + +void JSON::set(const std::string &key, const char *value) noexcept { + (*ptr)[key] = value; +} + +void JSON::set(const std::string &key, long long value) noexcept { + (*ptr)[key] = value; +} + +void JSON::set(const std::string &key, double value) noexcept { + (*ptr)[key] = value; +} + +void JSON::set(const std::string &key, bool value) noexcept { + (*ptr)[key] = value; +} + +void JSON::set(const std::string &key, JSON &&value) noexcept { + (*ptr)[key] = std::move(*value.ptr); + value.owner = false; +} + +void JSON::set(const std::string &key, const JSON &value) noexcept { + (*ptr)[key] = *value.ptr; +} + +void JSON::remove(const std::string &key) noexcept { + ptr->erase(key); +} + +void JSON::emplace_back(JSON &&value) { + ptr->emplace_back(std::move(*value.ptr)); + value.owner = false; +} + +boost::optional JSON::child_optional(const std::string &key) const noexcept { + try { + return child(key); + } + catch(...) { + return {}; + } +} + +JSON JSON::child(const std::string &key) const { + return JSON(&ptr->at(key)); +} + +boost::optional>> JSON::children_optional(const std::string &key) const noexcept { + try { + return children(key); + } + catch(...) { + return {}; + } +} + +std::vector> JSON::children_or_empty(const std::string &key) const noexcept { + try { + return children(key); + } + catch(...) { + return {}; + } +} + +std::vector> JSON::children(const std::string &key) const { + return JSON(&ptr->at(key)).children(); +} + +boost::optional>> JSON::children_optional() const noexcept { + try { + return children(); + } + catch(...) { + return {}; + } +} + +std::vector> JSON::children_or_empty() const noexcept { + try { + return children(); + } + catch(...) { + return {}; + } +} + +std::vector> JSON::children() const { + if(!ptr->is_object()) + throw std::runtime_error("value '" + to_string() + "' is not an object"); + std::vector> result; + for(auto it = ptr->begin(); it != ptr->end(); ++it) + result.emplace_back(it.key(), JSON(&*it)); + return result; +} + +boost::optional JSON::object_optional(const std::string &key) const noexcept { + try { + return object(key); + } + catch(...) { + return {}; + } +} + +JSON JSON::object(const std::string &key) const { + return JSON(&ptr->at(key)).object(); +} + + +boost::optional JSON::object_optional() const noexcept { + try { + return object(); + } + catch(...) { + return {}; + } +} + +JSON JSON::object() const { + if(!ptr->is_object()) + throw std::runtime_error("value '" + to_string() + "' is not an object"); + return JSON(ptr); +} + +boost::optional> JSON::array_optional(const std::string &key) const noexcept { + try { + return array(key); + } + catch(...) { + return {}; + } +} + +std::vector JSON::array_or_empty(const std::string &key) const noexcept { + try { + return array(key); + } + catch(...) { + return {}; + } +} + +std::vector JSON::array(const std::string &key) const { + return JSON(&ptr->at(key)).array(); +} + +boost::optional> JSON::array_optional() const noexcept { + try { + return array(); + } + catch(...) { + return {}; + } +} + +std::vector JSON::array_or_empty() const noexcept { + try { + return array(); + } + catch(...) { + return {}; + } +} + +std::vector JSON::array() const { + if(!ptr->is_array()) + throw std::runtime_error("value '" + to_string() + "' is not an array"); + std::vector result; + result.reserve(ptr->size()); + for(auto &e : *ptr) + result.emplace_back(&e); + return result; +} + +boost::optional JSON::string_optional(const std::string &key) const noexcept { + try { + return string(key); + } + catch(...) { + return {}; + } +} + +std::string JSON::string_or(const std::string &key, const std::string &default_value) const noexcept { + try { + return string(key); + } + catch(...) { + return default_value; + } +} + +std::string JSON::string(const std::string &key) const { + return ptr->at(key).get(); +} + +boost::optional JSON::string_optional() const noexcept { + try { + return string(); + } + catch(...) { + return {}; + } +} + +std::string JSON::string_or(const std::string &default_value) const noexcept { + try { + return string(); + } + catch(...) { + return default_value; + } +} + +std::string JSON::string() const { + return ptr->get(); +} + +boost::optional JSON::integer_optional(const std::string &key, ParseOptions parse_options) const noexcept { + try { + return integer(key, parse_options); + } + catch(...) { + return {}; + } +} + +long long JSON::integer_or(const std::string &key, long long default_value, ParseOptions parse_options) const noexcept { + try { + return integer(key, parse_options); + } + catch(...) { + return default_value; + } +} + +long long JSON::integer(const std::string &key, ParseOptions parse_options) const { + return JSON(&ptr->at(key)).integer(parse_options); +} + +boost::optional JSON::integer_optional(ParseOptions parse_options) const noexcept { + try { + return integer(parse_options); + } + catch(...) { + return {}; + } +} + +long long JSON::integer_or(long long default_value, ParseOptions parse_options) const noexcept { + try { + return integer(parse_options); + } + catch(...) { + return default_value; + } +} + +long long JSON::integer(ParseOptions parse_options) const { + if(auto integer = ptr->get()) + return *integer; + if(auto floating_point = ptr->get()) + return *floating_point; + if(parse_options == ParseOptions::accept_string) { + if(auto string = ptr->get()) { + try { + return std::stoll(*string); + } + catch(...) { + } + } + } + throw std::runtime_error("value '" + to_string() + "' could not be converted to integer"); +} + +boost::optional JSON::floating_point_optional(const std::string &key, ParseOptions parse_options) const noexcept { + try { + return floating_point(key, parse_options); + } + catch(...) { + return {}; + } +} + +double JSON::floating_point_or(const std::string &key, double default_value, ParseOptions parse_options) const noexcept { + try { + return floating_point(key, parse_options); + } + catch(...) { + return default_value; + } +} + +double JSON::floating_point(const std::string &key, ParseOptions parse_options) const { + return JSON(&ptr->at(key)).floating_point(parse_options); +} + +boost::optional JSON::floating_point_optional(ParseOptions parse_options) const noexcept { + try { + return floating_point(parse_options); + } + catch(...) { + return {}; + } +} + +double JSON::floating_point_or(double default_value, ParseOptions parse_options) const noexcept { + try { + return floating_point(parse_options); + } + catch(...) { + return default_value; + } +} + +double JSON::floating_point(ParseOptions parse_options) const { + if(auto floating_point = ptr->get()) + return *floating_point; + if(auto integer = ptr->get()) + return *integer; + if(parse_options == ParseOptions::accept_string) { + if(auto string = ptr->get()) { + try { + return std::stof(*string); + } + catch(...) { + } + } + } + throw std::runtime_error("value '" + to_string() + "' could not be converted to floating point"); +} + +boost::optional JSON::boolean_optional(const std::string &key, ParseOptions parse_options) const noexcept { + try { + return boolean(key, parse_options); + } + catch(...) { + return {}; + } +} + +bool JSON::boolean_or(const std::string &key, bool default_value, ParseOptions parse_options) const noexcept { + try { + return boolean(key, parse_options); + } + catch(...) { + return default_value; + } +} + +bool JSON::boolean(const std::string &key, ParseOptions parse_options) const { + return JSON(&ptr->at(key)).boolean(parse_options); +} + +boost::optional JSON::boolean_optional(ParseOptions parse_options) const noexcept { + try { + return boolean(parse_options); + } + catch(...) { + return {}; + } +} + +bool JSON::boolean_or(bool default_value, ParseOptions parse_options) const noexcept { + + try { + return boolean(parse_options); + } + catch(...) { + return default_value; + } +} + +bool JSON::boolean(ParseOptions parse_options) const { + if(auto boolean = ptr->get()) + return *boolean; + if(auto integer = ptr->get()) { + if(*integer == 1) + return true; + if(*integer == 0) + return false; + } + if(parse_options == ParseOptions::accept_string) { + if(auto string = ptr->get()) { + if(*string == "true" || *string == "1") + return true; + if(*string == "false" || *string == "0") + return false; + } + } + throw std::runtime_error("value '" + to_string() + "' could not be converted to bool"); +} diff --git a/src/json.hpp b/src/json.hpp index 41b6d6b..1712b75 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -1,19 +1,128 @@ #pragma once +#include "nlohmann/json_fwd.hpp" #include -#include +#include #include class JSON { - static void write_json_internal(std::ostream &stream, const boost::property_tree::ptree &pt, bool pretty, const std::vector ¬_string_keys, size_t column, const std::string &key); + nlohmann::ordered_json *ptr; + bool owner; public: - /// A replacement of boost::property_tree::write_json() as it does not conform to the JSON standard (https://svn.boost.org/trac10/ticket/9721). - /// Some JSON parses expects, for instance, number types instead of numbers in strings. - /// Note that boost::property_tree::write_json() will always produce string values. - /// Use not_string_keys to specify which keys that should not have string values. - /// TODO: replace boost::property_tree with another JSON library. - static void write(std::ostream &stream, const boost::property_tree::ptree &pt, bool pretty = true, const std::vector ¬_string_keys = {}); - /// A replacement of boost::property_tree::escape_text() as it does not conform to the JSON standard. static std::string escape_string(std::string string); + + enum class ParseOptions { none = 0, + accept_string }; + + enum class StructureType { object = 0, + array }; + + /// Create an empty structure (null) or array + JSON(StructureType type = StructureType::object) + noexcept; + + explicit JSON(nlohmann::ordered_json *json_ptr) noexcept : ptr(json_ptr), owner(false) {} + explicit JSON(const std::string &string); + explicit JSON(const char *c_str); + explicit JSON(std::istream &istream); + explicit JSON(const boost::filesystem::path &path); + + JSON(JSON &&other) + noexcept; + JSON &operator=(JSON &&other) noexcept; + + ~JSON(); + + static JSON make_owner(JSON &&other) noexcept; + + /// Use for instance std::setw(2) prior to this call to enable pretty printing. + friend std::ostream &operator<<(std::ostream &os, const JSON &json); + + /// Set indent to for instance 2 to enable pretty printing. + std::string to_string(int indent = -1) const; + + /// Set indent to for instance 2 to enable pretty printing. + void to_file(const boost::filesystem::path &path, int indent = -1) const; + + void set(const std::string &key, std::string value) noexcept; + void set(const std::string &key, const char *value) noexcept; + void set(const std::string &key, int value) noexcept { + set(key, static_cast(value)); + } + void set(const std::string &key, long value) noexcept { + set(key, static_cast(value)); + } + void set(const std::string &key, long long value) noexcept; + void set(const std::string &key, unsigned value) noexcept { + set(key, static_cast(value)); + } + void set(const std::string &key, unsigned long value) noexcept { + set(key, static_cast(value)); + } + void set(const std::string &key, unsigned long long value) noexcept { + set(key, static_cast(value)); + } + void set(const std::string &key, float value) noexcept { + set(key, static_cast(value)); + } + void set(const std::string &key, double value) noexcept; + void set(const std::string &key, bool value) noexcept; + void set(const std::string &key, JSON &&value) noexcept; + void set(const std::string &key, const JSON &value) noexcept; + + /// Might invalidate JSON references returned by children(), if one of the these elements are removed. + void remove(const std::string &key) noexcept; + + void emplace_back(JSON &&value); + + boost::optional child_optional(const std::string &key) const noexcept; + JSON child(const std::string &key) const; + + boost::optional>> children_optional(const std::string &key) const noexcept; + std::vector> children_or_empty(const std::string &key) const noexcept; + std::vector> children(const std::string &key) const; + boost::optional>> children_optional() const noexcept; + std::vector> children_or_empty() const noexcept; + std::vector> children() const; + + boost::optional object_optional(const std::string &key) const noexcept; + JSON object(const std::string &key) const; + boost::optional object_optional() const noexcept; + JSON object() const; + + boost::optional> array_optional(const std::string &key) const noexcept; + std::vector array_or_empty(const std::string &key) const noexcept; + std::vector array(const std::string &key) const; + boost::optional> array_optional() const noexcept; + std::vector array_or_empty() const noexcept; + std::vector array() const; + + boost::optional string_optional(const std::string &key) const noexcept; + std::string string_or(const std::string &key, const std::string &default_value) const noexcept; + std::string string(const std::string &key) const; + boost::optional string_optional() const noexcept; + std::string string_or(const std::string &default_value) const noexcept; + std::string string() const; + + boost::optional integer_optional(const std::string &key, ParseOptions parse_options = ParseOptions::none) const noexcept; + long long integer_or(const std::string &key, long long default_value, ParseOptions parse_options = ParseOptions::none) const noexcept; + long long integer(const std::string &key, ParseOptions parse_options = ParseOptions::none) const; + boost::optional integer_optional(ParseOptions parse_options = ParseOptions::none) const noexcept; + long long integer_or(long long default_value, ParseOptions parse_options = ParseOptions::none) const noexcept; + long long integer(ParseOptions parse_options = ParseOptions::none) const; + + boost::optional floating_point_optional(const std::string &key, ParseOptions parse_options = ParseOptions::none) const noexcept; + double floating_point_or(const std::string &key, double default_value, ParseOptions parse_options = ParseOptions::none) const noexcept; + double floating_point(const std::string &key, ParseOptions parse_options = ParseOptions::none) const; + boost::optional floating_point_optional(ParseOptions parse_options = ParseOptions::none) const noexcept; + double floating_point_or(double default_value, ParseOptions parse_options = ParseOptions::none) const noexcept; + double floating_point(ParseOptions parse_options = ParseOptions::none) const; + + boost::optional boolean_optional(const std::string &key, ParseOptions parse_options = ParseOptions::none) const noexcept; + bool boolean_or(const std::string &key, bool default_value, ParseOptions parse_options = ParseOptions::none) const noexcept; + bool boolean(const std::string &key, ParseOptions parse_options = ParseOptions::none) const; + boost::optional boolean_optional(ParseOptions parse_options = ParseOptions::none) const noexcept; + bool boolean_or(bool default_value, ParseOptions parse_options = ParseOptions::none) const noexcept; + bool boolean(ParseOptions parse_options = ParseOptions::none) const; }; diff --git a/src/juci.cpp b/src/juci.cpp index 733a43b..f0ec0a7 100644 --- a/src/juci.cpp +++ b/src/juci.cpp @@ -53,7 +53,7 @@ int Application::on_command_line(const Glib::RefPtr void Application::on_activate() { std::vector> file_offsets; - std::string current_file; + boost::filesystem::path current_file; Window::get().load_session(directories, files, file_offsets, current_file, directories.empty() && files.empty()); Window::get().add_widgets(); diff --git a/src/meson.cpp b/src/meson.cpp index 926db34..33ac0da 100644 --- a/src/meson.cpp +++ b/src/meson.cpp @@ -3,10 +3,10 @@ #include "config.hpp" #include "dialog.hpp" #include "filesystem.hpp" +#include "json.hpp" #include "terminal.hpp" #include "utility.hpp" #include -#include #include #include @@ -156,18 +156,17 @@ boost::filesystem::path Meson::get_executable(const boost::filesystem::path &bui } if(best_match_executable.empty()) { // Newer Meson outputs intro-targets.json that can be used to find executable - boost::property_tree::ptree pt; try { - boost::property_tree::json_parser::read_json((build_path / "meson-info" / "intro-targets.json").string(), pt); - for(auto &target : pt) { - if(target.second.get("type") == "executable") { - auto filenames = target.second.get_child("filename"); + JSON targets(build_path / "meson-info" / "intro-targets.json"); + for(auto &target : targets.array()) { + if(target.string("type") == "executable") { + auto filenames = target.array("filename"); if(filenames.empty()) // No executable file found break; - auto executable = filesystem::get_normal_path(filenames.begin()->second.get("")); - for(auto &target_source : target.second.get_child("target_sources")) { - for(auto &source : target_source.second.get_child("sources")) { - auto source_file = filesystem::get_normal_path(source.second.get("")); + auto executable = filesystem::get_normal_path(filenames.begin()->string()); + for(auto &target_source : target.array("target_sources")) { + for(auto &source : target_source.array("sources")) { + auto source_file = filesystem::get_normal_path(source.string()); if(source_file == file_path) return executable; auto source_file_directory = source_file.parent_path(); diff --git a/src/project_build.cpp b/src/project_build.cpp index 2c74e63..f8b096d 100644 --- a/src/project_build.cpp +++ b/src/project_build.cpp @@ -1,9 +1,9 @@ #include "project_build.hpp" #include "config.hpp" #include "filesystem.hpp" +#include "json.hpp" #include "terminal.hpp" #include -#include #include std::unique_ptr Project::Build::create(const boost::filesystem::path &path) { @@ -199,10 +199,9 @@ bool Project::MesonBuild::is_valid() { auto default_path = get_default_path(); if(default_path.empty()) return true; - boost::property_tree::ptree pt; try { - boost::property_tree::json_parser::read_json((default_path / "meson-info" / "meson-info.json").string(), pt); - return boost::filesystem::path(pt.get("directories.source")) == project_path; + JSON info(default_path / "meson-info" / "meson-info.json"); + return boost::filesystem::path(info.object("directories").string("source")) == project_path; } catch(...) { } diff --git a/src/snippets.cpp b/src/snippets.cpp index 413f520..aaffd74 100644 --- a/src/snippets.cpp +++ b/src/snippets.cpp @@ -1,8 +1,8 @@ #include "snippets.hpp" #include "config.hpp" #include "filesystem.hpp" +#include "json.hpp" #include "terminal.hpp" -#include void Snippets::load() { auto snippets_file = Config::get().home_juci_path / "snippets.json"; @@ -23,12 +23,11 @@ void Snippets::load() { snippets.clear(); try { - boost::property_tree::ptree pt; - boost::property_tree::json_parser::read_json(snippets_file.string(), pt); - for(auto language_it = pt.begin(); language_it != pt.end(); ++language_it) { - snippets.emplace_back(std::regex(language_it->first), std::vector()); - for(auto snippet_it = language_it->second.begin(); snippet_it != language_it->second.end(); ++snippet_it) { - auto key_string = snippet_it->second.get("key", ""); + JSON languages(snippets_file); + for(auto &language : languages.children()) { + snippets.emplace_back(std::regex(language.first), std::vector()); + for(auto &snippet : language.second.array()) { + auto key_string = snippet.string_or("key", ""); guint key = 0; GdkModifierType modifier = static_cast(0); if(!key_string.empty()) { @@ -36,7 +35,7 @@ void Snippets::load() { if(key == 0 && modifier == 0) Terminal::get().async_print("\e[31mError\e[m: could not parse key string: " + key_string + "\n", true); } - snippets.back().second.emplace_back(Snippet{snippet_it->second.get("prefix", ""), key, modifier, snippet_it->second.get("body"), snippet_it->second.get("description", "")}); + snippets.back().second.emplace_back(Snippet{snippet.string_or("prefix", ""), key, modifier, snippet.string("body"), snippet.string_or("description", "")}); } } } diff --git a/src/source.cpp b/src/source.cpp index ecf6ec0..299f1f6 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -5,12 +5,12 @@ #include "filesystem.hpp" #include "git.hpp" #include "info.hpp" +#include "json.hpp" #include "menu.hpp" #include "selection_dialog.hpp" #include "terminal.hpp" #include "utility.hpp" #include -#include #include #include #include @@ -764,11 +764,9 @@ void Source::View::setup_format_style(bool is_generic_view) { break; auto package_json = search_path / "package.json"; if(boost::filesystem::exists(package_json, ec)) { - boost::property_tree::ptree pt; try { - boost::property_tree::json_parser::read_json(package_json.string(), pt); - auto child = pt.get_child("prettier"); - break; + if(JSON(package_json).child_optional("prettier")) + break; } catch(...) { } diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 36b9c40..799218b 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -17,61 +17,53 @@ #include #include -const std::vector not_string_keys = {"line", "character", "severity", "tags", "isPreferred", "deprecated", "preselect", "insertTextFormat", "insertTextMode", "version"}; const std::string type_coverage_message = "Un-type checked code. Consider adding type annotations."; -LanguageProtocol::Offset::Offset(const boost::property_tree::ptree &pt) { +LanguageProtocol::Position::Position(const JSON &position) { try { - line = pt.get("line"); - character = pt.get("character"); + line = position.integer("line"); + character = position.integer("character"); } catch(...) { // Workaround for buggy rls - line = std::min(pt.get("line"), static_cast(std::numeric_limits::max())); - character = std::min(pt.get("character"), static_cast(std::numeric_limits::max())); + line = std::min(position.integer("line"), std::numeric_limits::max()); + character = std::min(position.integer("character"), std::numeric_limits::max()); } } -LanguageProtocol::Range::Range(const boost::property_tree::ptree &pt) : start(pt.get_child("start")), end(pt.get_child("end")) {} +LanguageProtocol::Range::Range(const JSON &range) : start(range.object("start")), end(range.object("end")) {} -LanguageProtocol::Location::Location(const boost::property_tree::ptree &pt, std::string file_) : range(pt.get_child("range")) { - if(file_.empty()) { - file = filesystem::get_path_from_uri(pt.get("uri")).string(); - } - else - file = std::move(file_); +LanguageProtocol::Location::Location(const JSON &location, std::string file_) : file(file_.empty() ? filesystem::get_path_from_uri(location.string("uri")).string() : std::move(file_)), range(location.object("range")) { } -LanguageProtocol::Documentation::Documentation(const boost::property_tree::ptree &pt) { - value = pt.get("documentation", ""); - if(value.empty()) { - if(auto documentation_pt = pt.get_child_optional("documentation")) { - value = documentation_pt->get("value", ""); - kind = documentation_pt->get("kind", ""); - } +LanguageProtocol::Documentation::Documentation(const boost::optional &documentation) { + if(!documentation) + return; + if(auto child = documentation->object_optional()) { + value = child->string_or("value", ""); + kind = child->string_or("kind", ""); } - if(value == "null") // Python erroneously returns "null" when a parameter is not documented - value.clear(); + else + value = documentation->string_or(""); } -LanguageProtocol::Diagnostic::RelatedInformation::RelatedInformation(const boost::property_tree::ptree &pt) : message(pt.get("message")), location(pt.get_child("location")) {} +LanguageProtocol::Diagnostic::RelatedInformation::RelatedInformation(const JSON &related_information) : message(related_information.string("message")), location(related_information.object("location")) {} -LanguageProtocol::Diagnostic::Diagnostic(const boost::property_tree::ptree &pt) : message(pt.get("message")), range(pt.get_child("range")), severity(pt.get("severity", 0)), code(pt.get("code", "")), ptree(pt) { - auto related_information_it = pt.get_child("relatedInformation", boost::property_tree::ptree()); - for(auto it = related_information_it.begin(); it != related_information_it.end(); ++it) - related_informations.emplace_back(it->second); +LanguageProtocol::Diagnostic::Diagnostic(JSON &&diagnostic) : message(diagnostic.string("message")), range(diagnostic.object("range")), severity(diagnostic.integer_or("severity", 0)), code(diagnostic.string_or("code", "")) { + for(auto &related_information : diagnostic.array_or_empty("relatedInformation")) + related_informations.emplace_back(related_information); + object = std::make_shared(JSON::make_owner(std::move(diagnostic))); } -LanguageProtocol::TextEdit::TextEdit(const boost::property_tree::ptree &pt, std::string new_text_) : range(pt.get_child("range")), new_text(new_text_.empty() ? pt.get("newText") : std::move(new_text_)) {} +LanguageProtocol::TextEdit::TextEdit(const JSON &text_edit, std::string new_text_) : range(text_edit.object("range")), new_text(new_text_.empty() ? text_edit.string("newText") : std::move(new_text_)) {} -LanguageProtocol::TextDocumentEdit::TextDocumentEdit(const boost::property_tree::ptree &pt) : file(filesystem::get_path_from_uri(pt.get("textDocument.uri", "")).string()) { - auto edits_it = pt.get_child("edits", boost::property_tree::ptree()); - for(auto it = edits_it.begin(); it != edits_it.end(); ++it) - text_edits.emplace_back(it->second); +LanguageProtocol::TextDocumentEdit::TextDocumentEdit(const JSON &text_document_edit) : file(filesystem::get_path_from_uri(text_document_edit.object("textDocument").string("uri")).string()) { + for(auto &text_edit : text_document_edit.array_or_empty("edits")) + text_edits.emplace_back(text_edit); } LanguageProtocol::TextDocumentEdit::TextDocumentEdit(std::string file, std::vector text_edits) : file(std::move(file)), text_edits(std::move(text_edits)) {} -LanguageProtocol::WorkspaceEdit::WorkspaceEdit(const boost::property_tree::ptree &pt, boost::filesystem::path file_path) { +LanguageProtocol::WorkspaceEdit::WorkspaceEdit(const JSON &workspace_edit, boost::filesystem::path file_path) { boost::filesystem::path project_path; auto build = Project::Build::create(file_path); if(!build->project_path.empty()) @@ -79,24 +71,29 @@ LanguageProtocol::WorkspaceEdit::WorkspaceEdit(const boost::property_tree::ptree else project_path = file_path.parent_path(); try { - if(auto changes_pt = pt.get_child_optional("changes")) { - for(auto file_it = changes_pt->begin(); file_it != changes_pt->end(); ++file_it) { - auto file = filesystem::get_path_from_uri(file_it->first); + if(auto children = workspace_edit.children_optional("changes")) { + for(auto &child : *children) { + auto file = filesystem::get_path_from_uri(child.first); if(filesystem::file_in_path(file, project_path)) { std::vector text_edits; - for(auto edit_it = file_it->second.begin(); edit_it != file_it->second.end(); ++edit_it) - text_edits.emplace_back(edit_it->second); + for(auto &text_edit : child.second.array()) + text_edits.emplace_back(text_edit); document_edits.emplace_back(file.string(), std::move(text_edits)); } } } - else if(auto changes_pt = pt.get_child_optional("documentChanges")) { - for(auto change_it = changes_pt->begin(); change_it != changes_pt->end(); ++change_it) { - LanguageProtocol::TextDocumentEdit document_edit(change_it->second); + else if(auto children = workspace_edit.array_optional("documentChanges")) { + for(auto &child : *children) { + LanguageProtocol::TextDocumentEdit document_edit(child); if(filesystem::file_in_path(document_edit.file, project_path)) document_edits.emplace_back(std::move(document_edit.file), std::move(document_edit.text_edits)); } } + else if(auto child = workspace_edit.object_optional("documentChanges")) { + LanguageProtocol::TextDocumentEdit document_edit(*child); + if(filesystem::file_in_path(document_edit.file, project_path)) + document_edits.emplace_back(std::move(document_edit.file), std::move(document_edit.text_edits)); + } } catch(...) { document_edits.clear(); @@ -148,7 +145,7 @@ std::shared_ptr LanguageProtocol::Client::get(const bo LanguageProtocol::Client::~Client() { std::promise result_processed; - write_request(nullptr, "shutdown", "", [this, &result_processed](const boost::property_tree::ptree &result, bool error) { + write_request(nullptr, "shutdown", "", [this, &result_processed](JSON &&result, bool error) { if(!error) this->write_notification("exit"); result_processed.set_value(); @@ -263,37 +260,37 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang "checkOnSave": { "enable": true } }, "trace": "off")", - [this, &result_processed](const boost::property_tree::ptree &result, bool error) { + [this, &result_processed](JSON &&result, bool error) { if(!error) { - if(auto capabilities_pt = result.get_child_optional("capabilities")) { - try { - capabilities.text_document_sync = static_cast(capabilities_pt->get("textDocumentSync")); + if(auto object = result.object_optional("capabilities")) { + if(auto child = object->child_optional("textDocumentSync")) { + if(auto integer = child->integer_optional()) + capabilities.text_document_sync = static_cast(*integer); + else + capabilities.text_document_sync = static_cast(child->integer_or("change", 0)); } - catch(...) { - capabilities.text_document_sync = static_cast(capabilities_pt->get("textDocumentSync.change", 0)); + capabilities.hover = static_cast(object->child_optional("hoverProvider")); + if(auto child = object->child_optional("completionProvider")) { + capabilities.completion = true; + capabilities.completion_resolve = child->boolean_or("resolveProvider", false); + } + capabilities.signature_help = static_cast(object->child_optional("signatureHelpProvider")); + capabilities.definition = static_cast(object->child_optional("definitionProvider")); + capabilities.type_definition = static_cast(object->child_optional("typeDefinitionProvider")); + capabilities.implementation = static_cast(object->child_optional("implementationProvider")); + capabilities.references = static_cast(object->child_optional("referencesProvider")); + capabilities.document_highlight = static_cast(object->child_optional("documentHighlightProvider")); + capabilities.workspace_symbol = static_cast(object->child_optional("workspaceSymbolProvider")); + capabilities.document_symbol = static_cast(object->child_optional("documentSymbolProvider")); + capabilities.document_formatting = static_cast(object->child_optional("documentFormattingProvider")); + capabilities.document_range_formatting = static_cast(object->child_optional("documentRangeFormattingProvider")); + capabilities.rename = static_cast(object->child_optional("renameProvider")); + if(auto child = object->child_optional("codeActionProvider")) { + capabilities.code_action = true; + capabilities.code_action_resolve = child->boolean_or("resolveProvider", false); } - capabilities.hover = capabilities_pt->get("hoverProvider", false); - capabilities.completion = static_cast(capabilities_pt->get_child_optional("completionProvider")); - capabilities.completion_resolve = capabilities_pt->get("completionProvider.resolveProvider", false); - capabilities.signature_help = static_cast(capabilities_pt->get_child_optional("signatureHelpProvider")); - capabilities.definition = capabilities_pt->get("definitionProvider", false); - capabilities.type_definition = capabilities_pt->get("typeDefinitionProvider", false); - capabilities.implementation = capabilities_pt->get("implementationProvider", false); - capabilities.references = capabilities_pt->get("referencesProvider", false); - capabilities.document_highlight = capabilities_pt->get("documentHighlightProvider", false); - capabilities.workspace_symbol = capabilities_pt->get("workspaceSymbolProvider", false); - capabilities.document_symbol = capabilities_pt->get("documentSymbolProvider", false); - capabilities.document_formatting = capabilities_pt->get("documentFormattingProvider", false); - capabilities.document_range_formatting = capabilities_pt->get("documentRangeFormattingProvider", false); - capabilities.rename = capabilities_pt->get("renameProvider", false); - if(!capabilities.rename) - capabilities.rename = capabilities_pt->get("renameProvider.prepareProvider", false); - capabilities.code_action = capabilities_pt->get("codeActionProvider", false); - if(!capabilities.code_action) - capabilities.code_action = static_cast(capabilities_pt->get_child_optional("codeActionProvider.codeActionKinds")); - capabilities.code_action_resolve = capabilities_pt->get("codeActionProvider.resolveProvider", false); - capabilities.execute_command = static_cast(capabilities_pt->get_child_optional("executeCommandProvider")); - capabilities.type_coverage = capabilities_pt->get("typeCoverageProvider", false); + capabilities.execute_command = static_cast(object->child_optional("executeCommandProvider")); + capabilities.type_coverage = static_cast(object->child_optional("typeCoverageProvider")); } // See https://clangd.llvm.org/extensions.html#utf-8-offsets for documentation on offsetEncoding @@ -323,7 +320,7 @@ void LanguageProtocol::Client::close(Source::LanguageProtocolView *view) { auto function = std::move(it->second.second); it = handlers.erase(it); lock.unlock(); - function(boost::property_tree::ptree(), true); + function({}, true); lock.lock(); } else @@ -368,53 +365,49 @@ void LanguageProtocol::Client::parse_server_message() { try { 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); + JSON object(server_message_stream); if(Config::get().log.language_server) { std::cout << "language server: "; - JSON::write(std::cout, pt); - std::cout << '\n'; + std::cout << std::setw(2) << object << '\n'; } - auto message_id = pt.get_optional("id"); + auto message_id = object.integer_optional("id"); { LockGuard lock(read_write_mutex); - if(auto result = pt.get_child_optional("result")) { + if(auto result = object.child_optional("result")) { 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); lock.unlock(); - function(*result, false); + function(JSON::make_owner(std::move(*result)), false); lock.lock(); } } } - else if(auto error = pt.get_child_optional("error")) { - if(!Config::get().log.language_server) { - JSON::write(std::cerr, pt); - std::cerr << '\n'; - } + else if(auto error = object.child_optional("error")) { + if(!Config::get().log.language_server) + std::cerr << std::setw(2) << object << '\n'; 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); lock.unlock(); - function(*error, true); + function(JSON::make_owner(std::move(*error)), true); lock.lock(); } } } - else if(auto method = pt.get_optional("method")) { - if(auto params = pt.get_child_optional("params")) { + else if(auto method = object.string_optional("method")) { + if(auto params = object.object_optional("params")) { lock.unlock(); if(message_id) - handle_server_request(*message_id, *method, *params); + handle_server_request(*message_id, *method, JSON::make_owner(std::move(*params))); else - handle_server_notification(*method, *params); + handle_server_notification(*method, JSON::make_owner(std::move(*params))); lock.lock(); } } @@ -438,7 +431,7 @@ void LanguageProtocol::Client::parse_server_message() { } } -void LanguageProtocol::Client::write_request(Source::LanguageProtocolView *view, const std::string &method, const std::string ¶ms, std::function &&function) { +void LanguageProtocol::Client::write_request(Source::LanguageProtocolView *view, const std::string &method, const std::string ¶ms, std::function &&function) { LockGuard lock(read_write_mutex); if(function) { handlers.emplace(message_id, std::make_pair(view, std::move(function))); @@ -460,7 +453,7 @@ void LanguageProtocol::Client::write_request(Source::LanguageProtocolView *view, auto function = std::move(id_it->second.second); handlers.erase(id_it); lock.unlock(); - function(boost::property_tree::ptree(), true); + function({}, true); lock.lock(); } }); @@ -475,7 +468,7 @@ void LanguageProtocol::Client::write_request(Source::LanguageProtocolView *view, auto function = std::move(id_it->second.second); handlers.erase(id_it); lock.unlock(); - function(boost::property_tree::ptree(), true); + function({}, true); lock.lock(); } } @@ -497,15 +490,14 @@ void LanguageProtocol::Client::write_notification(const std::string &method, con process->write("Content-Length: " + std::to_string(content.size()) + "\r\n\r\n" + content); } -void LanguageProtocol::Client::handle_server_notification(const std::string &method, const boost::property_tree::ptree ¶ms) { +void LanguageProtocol::Client::handle_server_notification(const std::string &method, JSON &¶ms) { if(method == "textDocument/publishDiagnostics") { std::vector diagnostics; - auto file = filesystem::get_path_from_uri(params.get("uri", "")); + auto file = filesystem::get_path_from_uri(params.string_or("uri", "")); if(!file.empty()) { - auto diagnostics_pt = params.get_child("diagnostics", boost::property_tree::ptree()); - for(auto it = diagnostics_pt.begin(); it != diagnostics_pt.end(); ++it) { + for(auto &child : params.array_or_empty("diagnostics")) { try { - diagnostics.emplace_back(it->second); + diagnostics.emplace_back(std::move(child)); } catch(...) { } @@ -521,18 +513,18 @@ void LanguageProtocol::Client::handle_server_notification(const std::string &met } } -void LanguageProtocol::Client::handle_server_request(size_t id, const std::string &method, const boost::property_tree::ptree ¶ms) { +void LanguageProtocol::Client::handle_server_request(size_t id, const std::string &method, JSON &¶ms) { if(method == "workspace/applyEdit") { std::promise result_processed; bool applied = true; - dispatcher->post([&result_processed, &applied, params] { + dispatcher->post([&result_processed, &applied, params = std::make_shared(std::move(params))] { ScopeGuard guard({[&result_processed] { result_processed.set_value(); }}); if(auto current_view = dynamic_cast(Notebook::get().get_current_view())) { LanguageProtocol::WorkspaceEdit workspace_edit; try { - workspace_edit = LanguageProtocol::WorkspaceEdit(params.get_child("edit"), current_view->file_path); + workspace_edit = LanguageProtocol::WorkspaceEdit(params->object("edit"), current_view->file_path); } catch(...) { applied = false; @@ -738,7 +730,7 @@ std::string Source::LanguageProtocolView::to_string(const std::vector &&function) { +void Source::LanguageProtocolView::write_request(const std::string &method, const std::string ¶ms, std::function &&function) { client->write_request(this, method, "\"textDocument\":{\"uri\":\"" + uri_escaped + "\"}" + (params.empty() ? "" : "," + params), std::move(function)); } @@ -827,11 +819,11 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { else method = "textDocument/formatting"; - write_request(method, to_string(params), [&text_edits, &result_processed](const boost::property_tree::ptree &result, bool error) { + write_request(method, to_string(params), [&text_edits, &result_processed](JSON &&result, bool error) { if(!error) { - for(auto it = result.begin(); it != result.end(); ++it) { + for(auto &edit : result.array_or_empty()) { try { - text_edits.emplace_back(it->second); + text_edits.emplace_back(edit); } catch(...) { } @@ -930,11 +922,11 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { else method = "textDocument/documentHighlight"; - write_request(method, to_string({make_position(iter.get_line(), get_line_pos(iter)), {"context", "{\"includeDeclaration\":true}"}}), [this, &locations, &result_processed](const boost::property_tree::ptree &result, bool error) { + write_request(method, to_string({make_position(iter.get_line(), get_line_pos(iter)), {"context", "{\"includeDeclaration\":true}"}}), [this, &locations, &result_processed](JSON &&result, bool error) { if(!error) { try { - for(auto it = result.begin(); it != result.end(); ++it) - locations.emplace(it->second, !capabilities.references ? file_path.string() : std::string()); + for(auto &location : result.array_or_empty()) + locations.emplace(location, !capabilities.references ? file_path.string() : std::string()); } catch(...) { locations.clear(); @@ -1057,7 +1049,7 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { LanguageProtocol::WorkspaceEdit workspace_edit; std::promise result_processed; if(capabilities.rename) { - write_request("textDocument/rename", to_string({make_position(iter.get_line(), get_line_pos(iter)), {"newName", '"' + text + '"'}}), [this, &workspace_edit, &result_processed](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/rename", to_string({make_position(iter.get_line(), get_line_pos(iter)), {"newName", '"' + text + '"'}}), [this, &workspace_edit, &result_processed](JSON &&result, bool error) { if(!error) { workspace_edit = LanguageProtocol::WorkspaceEdit(result, file_path); } @@ -1065,12 +1057,12 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { }); } else { - write_request("textDocument/documentHighlight", to_string({make_position(iter.get_line(), get_line_pos(iter)), {"context", "{\"includeDeclaration\":true}"}}), [this, &workspace_edit, &text, &result_processed](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/documentHighlight", to_string({make_position(iter.get_line(), get_line_pos(iter)), {"context", "{\"includeDeclaration\":true}"}}), [this, &workspace_edit, &text, &result_processed](JSON &&result, bool error) { if(!error) { try { std::vector edits; - for(auto it = result.begin(); it != result.end(); ++it) - edits.emplace_back(it->second, text); + for(auto &edit : result.array_or_empty()) + edits.emplace_back(edit, text); workspace_edit.document_edits.emplace_back(file_path.string(), std::move(edits)); } catch(...) { @@ -1190,33 +1182,32 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { std::vector> methods; std::promise result_processed; - write_request("textDocument/documentSymbol", {}, [&result_processed, &methods](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/documentSymbol", {}, [&result_processed, &methods](JSON &&result, bool error) { if(!error) { - std::function parse_result = [&methods, &parse_result](const boost::property_tree::ptree &pt, const std::string &container) { - for(auto it = pt.begin(); it != pt.end(); ++it) { + std::function parse_result = [&methods, &parse_result](const JSON &symbols, const std::string &container) { + for(auto &symbol : symbols.array_or_empty()) { try { - auto kind = it->second.get("kind"); + auto name = symbol.string("name"); + auto kind = symbol.integer("kind"); if(kind == 6 || kind == 9 || kind == 12) { std::unique_ptr range; std::string prefix; - if(auto location_pt = it->second.get_child_optional("location")) { - LanguageProtocol::Location location(*location_pt); + if(auto location_object = symbol.object_optional("location")) { + LanguageProtocol::Location location(*location_object); range = std::make_unique(location.range); - std::string container = it->second.get("containerName", ""); - if(container == "null") - container.clear(); + std::string container = symbol.string_or("containerName", ""); if(!container.empty()) prefix = container; } else { - range = std::make_unique(it->second.get_child("range")); + range = std::make_unique(symbol.object("range")); if(!container.empty()) prefix = container; } - methods.emplace_back(Offset(range->start.line, range->start.character), (!prefix.empty() ? Glib::Markup::escape_text(prefix) + ':' : "") + std::to_string(range->start.line + 1) + ": " + "" + Glib::Markup::escape_text(it->second.get("name")) + ""); + methods.emplace_back(Offset(range->start.line, range->start.character), (!prefix.empty() ? Glib::Markup::escape_text(prefix) + ':' : "") + std::to_string(range->start.line + 1) + ": " + "" + Glib::Markup::escape_text(name) + ""); } - if(auto children = it->second.get_child_optional("children")) - parse_result(*children, (!container.empty() ? container + "::" : "") + it->second.get("name")); + if(auto children = symbol.child_optional("children")) + parse_result(*children, (!container.empty() ? container + "::" : "") + name); } catch(...) { } @@ -1252,13 +1243,13 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { std::promise result_processed; Gtk::TextIter start, end; get_buffer()->get_selection_bounds(start, end); - std::vector> results; - write_request("textDocument/codeAction", to_string({make_range({start.get_line(), get_line_pos(start)}, {end.get_line(), get_line_pos(end)}), {"context", "{\"diagnostics\":[]}"}}), [&result_processed, &results](const boost::property_tree::ptree &result, bool error) { + std::vector>> results; + write_request("textDocument/codeAction", to_string({make_range({start.get_line(), get_line_pos(start)}, {end.get_line(), get_line_pos(end)}), {"context", "{\"diagnostics\":[]}"}}), [&result_processed, &results](JSON &&result, bool error) { if(!error) { - for(auto it = result.begin(); it != result.end(); ++it) { - auto title = it->second.get("title", ""); + for(auto &code_action : result.array_or_empty()) { + auto title = code_action.string_or("title", ""); if(!title.empty()) - results.emplace_back(title, it->second); + results.emplace_back(title, std::make_shared(JSON::make_owner(std::move(code_action)))); } } result_processed.set_value(); @@ -1277,29 +1268,30 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { if(index >= results.size()) return; - auto edit_pt = results[index].second.get_child_optional("edit"); - if(capabilities.code_action_resolve && !edit_pt) { + auto edit = results[index].second->object_optional("edit"); + if(capabilities.code_action_resolve && !edit) { std::promise result_processed; std::stringstream ss; - for(auto it = results[index].second.begin(); it != results[index].second.end(); ++it) { - ss << (it != results[index].second.begin() ? ",\"" : "\"") << JSON::escape_string(it->first) << "\":"; - JSON::write(ss, it->second, false, not_string_keys); + bool first = true; + for(auto &child : results[index].second->children_or_empty()) { + ss << (!first ? ",\"" : "\"") << JSON::escape_string(child.first) << "\":" << child.second; + first = false; } - write_request("codeAction/resolve", ss.str(), [&result_processed, &edit_pt](const boost::property_tree::ptree &result, bool error) { + write_request("codeAction/resolve", ss.str(), [&result_processed, &edit](JSON &&result, bool error) { if(!error) { - auto child = result.get_child_optional("edit"); - if(child) - edit_pt = *child; // Make copy, since result will be destroyed at end of callback + auto object = result.object_optional("edit"); + if(object) + edit = JSON::make_owner(std::move(*object)); } result_processed.set_value(); }); result_processed.get_future().get(); } - if(edit_pt) { + if(edit) { LanguageProtocol::WorkspaceEdit workspace_edit; try { - workspace_edit = LanguageProtocol::WorkspaceEdit(*edit_pt, file_path); + workspace_edit = LanguageProtocol::WorkspaceEdit(*edit, file_path); } catch(...) { return; @@ -1367,14 +1359,15 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() { } if(capabilities.execute_command) { - auto command_pt = results[index].second.get_child_optional("command"); - if(command_pt) { + auto command = results[index].second->object_optional("command"); + if(command) { std::stringstream ss; - for(auto it = command_pt->begin(); it != command_pt->end(); ++it) { - ss << (it != command_pt->begin() ? ",\"" : "\"") << JSON::escape_string(it->first) << "\":"; - JSON::write(ss, it->second, false, not_string_keys); + bool first = true; + for(auto &child : command->children_or_empty()) { + ss << (!first ? ",\"" : "\"") << JSON::escape_string(child.first) << "\":" << child.second; + first = false; } - write_request("workspace/executeCommand", ss.str(), [](const boost::property_tree::ptree &result, bool error) { + write_request("workspace/executeCommand", ss.str(), [](JSON &&result, bool error) { }); } } @@ -1637,19 +1630,17 @@ void Source::LanguageProtocolView::setup_autocomplete() { } bool using_named_parameters = named_parameter_symbol && !(current_parameter_position > 0 && used_named_parameters.empty()); - write_request("textDocument/signatureHelp", to_string({make_position(line, get_line_pos(line, line_index))}), [this, &result_processed, current_parameter_position, using_named_parameters, used_named_parameters = std::move(used_named_parameters)](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/signatureHelp", to_string({make_position(line, get_line_pos(line, line_index))}), [this, &result_processed, current_parameter_position, using_named_parameters, used_named_parameters = std::move(used_named_parameters)](JSON &&result, bool error) { if(!error) { - auto signatures = result.get_child("signatures", boost::property_tree::ptree()); - for(auto signature_it = signatures.begin(); signature_it != signatures.end(); ++signature_it) { - auto parameters = signature_it->second.get_child("parameters", boost::property_tree::ptree()); + for(auto &signature : result.array_or_empty("signatures")) { unsigned parameter_position = 0; - for(auto parameter_it = parameters.begin(); parameter_it != parameters.end(); ++parameter_it) { + for(auto ¶meter : signature.array_or_empty("parameters")) { if(parameter_position == current_parameter_position || using_named_parameters) { - auto label = parameter_it->second.get("label", ""); + auto label = parameter.string_or("label", ""); auto insert = label; if(!using_named_parameters || used_named_parameters.find(insert) == used_named_parameters.end()) { autocomplete->rows.emplace_back(std::move(label)); - autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), {}, LanguageProtocol::Documentation(parameter_it->second), {}, {}}); + autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), {}, LanguageProtocol::Documentation(parameter.child_optional("documentation")), {}, {}}); } } parameter_position++; @@ -1673,56 +1664,50 @@ void Source::LanguageProtocolView::setup_autocomplete() { } else { dispatcher.post([this, line, line_index, &result_processed] { - write_request("textDocument/completion", to_string({make_position(line, get_line_pos(line, line_index))}), [this, &result_processed](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/completion", to_string({make_position(line, get_line_pos(line, line_index))}), [this, &result_processed](JSON &&result, bool error) { if(!error) { - bool is_incomplete = result.get("isIncomplete", false); - boost::property_tree::ptree::const_iterator begin, end; - if(auto items = result.get_child_optional("items")) { - begin = items->begin(); - end = items->end(); - } - else { - begin = result.begin(); - end = result.end(); - } + bool is_incomplete = result.boolean_or("isIncomplete", false); + auto items = result.array_or_empty(); + if(items.empty()) + items = result.array_or_empty("items"); std::string prefix; { LockGuard lock(autocomplete->prefix_mutex); prefix = autocomplete->prefix; } - for(auto it = begin; it != end; ++it) { - auto label = it->second.get("label", ""); + for(auto &item : items) { + auto label = item.string_or("label", ""); if(starts_with(label, prefix)) { - auto detail = it->second.get("detail", ""); - LanguageProtocol::Documentation documentation(it->second); - - boost::property_tree::ptree ptree; - if(detail.empty() && documentation.value.empty() && (is_incomplete || is_js)) // Workaround for typescript-language-server (is_js) - ptree = it->second; + auto detail = item.string_or("detail", ""); + LanguageProtocol::Documentation documentation(item.child_optional("documentation")); std::vector additional_text_edits; - auto additional_text_edits_pt = it->second.get_child_optional("additionalTextEdits"); - if(additional_text_edits_pt) { - try { - for(auto text_edit_it = additional_text_edits_pt->begin(); text_edit_it != additional_text_edits_pt->end(); ++text_edit_it) - additional_text_edits.emplace_back(text_edit_it->second); - } - catch(...) { - additional_text_edits.clear(); - } + try { + for(auto &text_edit : item.array_or_empty("additionalTextEdits")) + additional_text_edits.emplace_back(text_edit); + } + catch(...) { + additional_text_edits.clear(); } - auto insert = it->second.get("insertText", ""); - if(insert.empty()) - insert = it->second.get("textEdit.newText", ""); + auto insert = item.string_or("insertText", ""); + if(insert.empty()) { + if(auto text_edit = item.object_optional("textEdit")) + insert = text_edit->string_or("newText", ""); + } if(insert.empty()) insert = label; if(!insert.empty()) { - auto kind = it->second.get("kind", 0); + auto kind = item.integer_or("kind", 0); if(kind >= 2 && kind <= 4 && insert.find('(') == std::string::npos) // If kind is method, function or constructor, but parentheses are missing insert += "(${1:})"; + + std::shared_ptr item_object; + if(detail.empty() && documentation.value.empty() && (is_incomplete || is_js)) // Workaround for typescript-language-server (is_js) + item_object = std::make_shared(JSON::make_owner(std::move(item))); + autocomplete->rows.emplace_back(std::move(label)); - autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), std::move(detail), std::move(documentation), std::move(ptree), std::move(additional_text_edits)}); + autocomplete_rows.emplace_back(AutocompleteRow{std::move(insert), std::move(detail), std::move(documentation), std::move(item_object), std::move(additional_text_edits)}); } } } @@ -1754,6 +1739,7 @@ void Source::LanguageProtocolView::setup_autocomplete() { autocomplete->on_hide = [this] { autocomplete_rows.clear(); + ++set_tooltip_count; }; autocomplete->on_select = [this](unsigned int index, const std::string &text, bool hide_window) { @@ -1819,61 +1805,81 @@ void Source::LanguageProtocolView::setup_autocomplete() { }; autocomplete->set_tooltip_buffer = [this](unsigned int index) -> std::function { - auto autocomplete = autocomplete_rows[index]; - if(capabilities.completion_resolve && autocomplete.detail.empty() && autocomplete.documentation.value.empty() && !autocomplete.ptree.empty()) { - std::stringstream ss; - for(auto it = autocomplete.ptree.begin(); it != autocomplete.ptree.end(); ++it) { - ss << (it != autocomplete.ptree.begin() ? ",\"" : "\"") << JSON::escape_string(it->first) << "\":"; - JSON::write(ss, it->second, false, not_string_keys); - } - std::promise result_processed; - write_request("completionItem/resolve", ss.str(), [&result_processed, &autocomplete](const boost::property_tree::ptree &result, bool error) { - if(!error) { - autocomplete.detail = result.get("detail", ""); - autocomplete.documentation = LanguageProtocol::Documentation(result); - } - result_processed.set_value(); - }); - result_processed.get_future().get(); - } - if(autocomplete.detail.empty() && autocomplete.documentation.value.empty()) - return nullptr; + size_t last_count = ++set_tooltip_count; + auto &autocomplete_row = autocomplete_rows[index]; - return [this, autocomplete = std::move(autocomplete)](Tooltip &tooltip) mutable { - if(language_id == "python") // Python might support markdown in the future - tooltip.insert_docstring(autocomplete.documentation.value); + const static auto insert_documentation = [](Source::LanguageProtocolView *view, Tooltip &tooltip, const std::string &detail, const LanguageProtocol::Documentation &documentation) { + if(view->language_id == "python") // Python might support markdown in the future + tooltip.insert_docstring(documentation.value); else { - if(!autocomplete.detail.empty()) { - tooltip.insert_code(autocomplete.detail, language); + if(!detail.empty()) { + tooltip.insert_code(detail, view->language); tooltip.remove_trailing_newlines(); } - if(!autocomplete.documentation.value.empty()) { + if(!documentation.value.empty()) { if(tooltip.buffer->size() > 0) tooltip.buffer->insert_at_cursor("\n\n"); - if(autocomplete.documentation.kind == "plaintext" || autocomplete.documentation.kind.empty()) - tooltip.insert_with_links_tagged(autocomplete.documentation.value); - else if(autocomplete.documentation.kind == "markdown") - tooltip.insert_markdown(autocomplete.documentation.value); + if(documentation.kind == "plaintext" || documentation.kind.empty()) + tooltip.insert_with_links_tagged(documentation.value); + else if(documentation.kind == "markdown") + tooltip.insert_markdown(documentation.value); else - tooltip.insert_code(autocomplete.documentation.value, autocomplete.documentation.kind); + tooltip.insert_code(documentation.value, documentation.kind); } } }; + + if(capabilities.completion_resolve && autocomplete_row.detail.empty() && autocomplete_row.documentation.value.empty() && autocomplete_row.item_object) { + std::stringstream ss; + bool first = true; + for(auto &child : autocomplete_row.item_object->children_or_empty()) { + ss << (!first ? ",\"" : "\"") << JSON::escape_string(child.first) << "\":" << child.second; + first = false; + } + write_request("completionItem/resolve", ss.str(), [this, last_count](JSON &&result, bool error) { + if(!error) { + if(last_count != set_tooltip_count) + return; + auto detail = result.string_or("detail", ""); + auto documentation = LanguageProtocol::Documentation(result.child_optional("documentation")); + if(detail.empty() && documentation.value.empty()) + return; + dispatcher.post([this, last_count, detail = std::move(detail), documentation = std::move(documentation)] { + if(last_count != set_tooltip_count) + return; + autocomplete->tooltips.clear(); + auto iter = CompletionDialog::get()->start_mark->get_iter(); + autocomplete->tooltips.emplace_back(this, iter, iter, [this, detail = std::move(detail), documentation = std::move(documentation)](Tooltip &tooltip) { + insert_documentation(this, tooltip, detail, documentation); + }); + autocomplete->tooltips.show(true); + }); + } + }); + return nullptr; + } + if(autocomplete_row.detail.empty() && autocomplete_row.documentation.value.empty()) + return nullptr; + + return [this, &autocomplete_row](Tooltip &tooltip) mutable { + insert_documentation(this, tooltip, autocomplete_row.detail, autocomplete_row.documentation); + }; }; } void Source::LanguageProtocolView::update_diagnostics_async(std::vector &&diagnostics) { - update_diagnostics_async_count++; - size_t last_count = update_diagnostics_async_count; + size_t last_count = ++update_diagnostics_async_count; if(capabilities.code_action && !diagnostics.empty()) { dispatcher.post([this, diagnostics = std::move(diagnostics), last_count]() mutable { if(last_count != update_diagnostics_async_count) return; - std::stringstream diagnostics_ss; - for(auto it = diagnostics.begin(); it != diagnostics.end(); ++it) { - if(it != diagnostics.begin()) - diagnostics_ss << ","; - JSON::write(diagnostics_ss, it->ptree, false, not_string_keys); + std::stringstream ss; + bool first = true; + for(auto &diagnostic : diagnostics) { + if(!first) + ss << ','; + ss << *diagnostic.object; + first = false; } std::pair range; if(diagnostics.size() == 1) // Use diagnostic range if only one diagnostic, otherwise use whole buffer @@ -1883,31 +1889,30 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vectorend(); range = make_range({start.get_line(), get_line_pos(start)}, {end.get_line(), get_line_pos(end)}); } - std::vector> params = {range, {"context", '{' + to_string({{"diagnostics", '[' + diagnostics_ss.str() + ']'}, {"only", "[\"quickfix\"]"}}) + '}'}}; + std::vector> params = {range, {"context", '{' + to_string({{"diagnostics", '[' + ss.str() + ']'}, {"only", "[\"quickfix\"]"}}) + '}'}}; thread_pool.push([this, diagnostics = std::move(diagnostics), params = std::move(params), last_count]() mutable { if(last_count != update_diagnostics_async_count) return; std::promise result_processed; - write_request("textDocument/codeAction", to_string(params), [this, &result_processed, &diagnostics, last_count](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/codeAction", to_string(params), [this, &result_processed, &diagnostics, last_count](JSON &&result, bool error) { if(!error && last_count == update_diagnostics_async_count) { try { - for(auto it = result.begin(); it != result.end(); ++it) { - auto kind = it->second.get("kind", ""); + for(auto &code_action : result.array_or_empty()) { + auto kind = code_action.string_or("kind", ""); if(kind == "quickfix" || kind.empty()) { // Workaround for typescript-language-server (kind.empty()) - auto title = it->second.get("title"); + auto title = code_action.string("title"); std::vector quickfix_diagnostics; - if(auto diagnostics_pt = it->second.get_child_optional("diagnostics")) { - for(auto it = diagnostics_pt->begin(); it != diagnostics_pt->end(); ++it) - quickfix_diagnostics.emplace_back(it->second); - } - auto edit_pt = it->second.get_child_optional("edit"); - if(!edit_pt) { - auto arguments = it->second.get_child_optional("arguments"); // Workaround for typescript-language-server (arguments) - if(arguments && arguments->begin() != arguments->end()) - edit_pt = arguments->begin()->second; + for(auto &diagnostic : code_action.array_or_empty("diagnostics")) + quickfix_diagnostics.emplace_back(std::move(diagnostic)); + auto edit = code_action.object_optional("edit"); + if(!edit) { + if(auto arguments = code_action.array_optional("arguments")) { + if(!arguments->empty()) + edit = std::move((*arguments)[0]); + } } - if(edit_pt) { - LanguageProtocol::WorkspaceEdit workspace_edit(*edit_pt, file_path); + if(edit) { + LanguageProtocol::WorkspaceEdit workspace_edit(*edit, file_path); for(auto &document_edit : workspace_edit.document_edits) { for(auto &text_edit : document_edit.text_edits) { if(!quickfix_diagnostics.empty()) { @@ -1916,8 +1921,8 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vector{}); pair.first->second.emplace( - std::move(text_edit.new_text), - std::move(document_edit.file), + text_edit.new_text, + document_edit.file, std::make_pair(Offset(text_edit.range.start.line, text_edit.range.start.character), Offset(text_edit.range.end.line, text_edit.range.end.character))); break; @@ -1930,8 +1935,8 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vector{}); pair.first->second.emplace( - std::move(text_edit.new_text), - std::move(document_edit.file), + text_edit.new_text, + document_edit.file, std::make_pair(Offset(text_edit.range.start.line, text_edit.range.start.character), Offset(text_edit.range.end.line, text_edit.range.end.character))); break; @@ -1952,7 +1957,8 @@ void Source::LanguageProtocolView::update_diagnostics_async(std::vector contents; - auto contents_pt = result.get_child_optional("contents"); + auto contents_pt = result.child_optional("contents"); if(!contents_pt) return; - auto value = contents_pt->get_value(""); - if(!value.empty()) - contents.emplace_back(Content{value, "markdown"}); - else { - auto value_pt = contents_pt->get_optional("value"); - if(value_pt) { - auto kind = contents_pt->get("kind", ""); + if(auto string = contents_pt->string_optional()) + contents.emplace_back(Content{std::move(*string), "markdown"}); + else if(auto object = contents_pt->object_optional()) { + auto value = object->string_or("value", ""); + if(!value.empty()) { + auto kind = object->string_or("kind", ""); if(kind.empty()) - kind = contents_pt->get("language", ""); - contents.emplace_back(Content{*value_pt, kind}); + kind = object->string_or("language", ""); + contents.emplace_front(Content{std::move(value), std::move(kind)}); } - else { - bool first_value = true; - for(auto it = contents_pt->begin(); it != contents_pt->end(); ++it) { - auto value = it->second.get("value", ""); + } + else if(auto array = contents_pt->array_optional()) { + bool first_value_in_object = true; + for(auto &object_or_string : *array) { + if(auto object = object_or_string.object_optional()) { + auto value = object_or_string.string_or("value", ""); if(!value.empty()) { - auto kind = it->second.get("kind", ""); + auto kind = object_or_string.string_or("kind", ""); if(kind.empty()) - kind = it->second.get("language", ""); - if(first_value) // Place first value, which most likely is type information, to front (workaround for flow-bin's language server) - contents.emplace_front(Content{value, kind}); + kind = object_or_string.string_or("language", ""); + if(first_value_in_object) // Place first value, which most likely is type information, to front (workaround for flow-bin's language server) + contents.emplace_front(Content{std::move(value), std::move(kind)}); else - contents.emplace_back(Content{value, kind}); - first_value = false; - } - else { - value = it->second.get_value(""); - if(!value.empty()) - contents.emplace_back(Content{value, "markdown"}); + contents.emplace_back(Content{std::move(value), std::move(kind)}); + first_value_in_object = false; } } + else if(auto string = object_or_string.string_optional()) { + if(!string->empty()) + contents.emplace_back(Content{std::move(*string), "markdown"}); + } } } if(!contents.empty()) { @@ -2221,13 +2228,12 @@ void Source::LanguageProtocolView::apply_similar_symbol_tag() { static int request_count = 0; request_count++; auto current_request = request_count; - write_request("textDocument/documentHighlight", to_string({make_position(iter.get_line(), get_line_pos(iter)), {"context", "{\"includeDeclaration\":true}"}}), [this, current_request](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/documentHighlight", to_string({make_position(iter.get_line(), get_line_pos(iter)), {"context", "{\"includeDeclaration\":true}"}}), [this, current_request](JSON &&result, bool error) { if(!error) { std::vector ranges; - for(auto it = result.begin(); it != result.end(); ++it) { + for(auto &location : result.array_or_empty()) { try { - if(capabilities.document_highlight || it->second.get("uri") == uri) - ranges.emplace_back(it->second.get_child("range")); + ranges.emplace_back(location.object("range")); } catch(...) { } @@ -2250,15 +2256,17 @@ void Source::LanguageProtocolView::apply_clickable_tag(const Gtk::TextIter &iter static int request_count = 0; request_count++; auto current_request = request_count; - write_request("textDocument/definition", to_string({make_position(iter.get_line(), get_line_pos(iter))}), [this, current_request, line = iter.get_line(), line_offset = iter.get_line_offset()](const boost::property_tree::ptree &result, bool error) { - if(!error && !result.empty()) { - dispatcher.post([this, current_request, line, line_offset] { - if(current_request != request_count || !clickable_tag_applied) - return; - get_buffer()->remove_tag(clickable_tag, get_buffer()->begin(), get_buffer()->end()); - auto range = get_token_iters(get_iter_at_line_offset(line, line_offset)); - get_buffer()->apply_tag(clickable_tag, range.first, range.second); - }); + write_request("textDocument/definition", to_string({make_position(iter.get_line(), get_line_pos(iter))}), [this, current_request, line = iter.get_line(), line_offset = iter.get_line_offset()](JSON &&result, bool error) { + if(!error) { + if(result.array_optional() || result.object_optional()) { + dispatcher.post([this, current_request, line, line_offset] { + if(current_request != request_count || !clickable_tag_applied) + return; + get_buffer()->remove_tag(clickable_tag, get_buffer()->begin(), get_buffer()->end()); + auto range = get_token_iters(get_iter_at_line_offset(line, line_offset)); + get_buffer()->apply_tag(clickable_tag, range.first, range.second); + }); + } } }); } @@ -2266,11 +2274,16 @@ void Source::LanguageProtocolView::apply_clickable_tag(const Gtk::TextIter &iter Source::Offset Source::LanguageProtocolView::get_declaration(const Gtk::TextIter &iter) { auto offset = std::make_shared(); std::promise result_processed; - write_request("textDocument/definition", to_string({make_position(iter.get_line(), get_line_pos(iter))}), [offset, &result_processed](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/definition", to_string({make_position(iter.get_line(), get_line_pos(iter))}), [offset, &result_processed](JSON &&result, bool error) { if(!error) { - for(auto it = result.begin(); it != result.end(); ++it) { + auto locations = result.array_or_empty(); + if(locations.empty()) { + if(auto object = result.object_optional()) + locations.emplace_back(std::move(*object)); + } + for(auto &location_object : locations) { try { - LanguageProtocol::Location location(it->second); + LanguageProtocol::Location location(location_object); offset->file_path = std::move(location.file); offset->line = location.range.start.line; offset->index = location.range.start.character; @@ -2289,11 +2302,16 @@ Source::Offset Source::LanguageProtocolView::get_declaration(const Gtk::TextIter Source::Offset Source::LanguageProtocolView::get_type_declaration(const Gtk::TextIter &iter) { auto offset = std::make_shared(); std::promise result_processed; - write_request("textDocument/typeDefinition", to_string({make_position(iter.get_line(), get_line_pos(iter))}), [offset, &result_processed](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/typeDefinition", to_string({make_position(iter.get_line(), get_line_pos(iter))}), [offset, &result_processed](JSON &&result, bool error) { if(!error) { - for(auto it = result.begin(); it != result.end(); ++it) { + auto locations = result.array_or_empty(); + if(locations.empty()) { + if(auto object = result.object_optional()) + locations.emplace_back(std::move(*object)); + } + for(auto &location_object : locations) { try { - LanguageProtocol::Location location(it->second); + LanguageProtocol::Location location(location_object); offset->file_path = std::move(location.file); offset->line = location.range.start.line; offset->index = location.range.start.character; @@ -2312,11 +2330,16 @@ Source::Offset Source::LanguageProtocolView::get_type_declaration(const Gtk::Tex std::vector Source::LanguageProtocolView::get_implementations(const Gtk::TextIter &iter) { auto offsets = std::make_shared>(); std::promise result_processed; - write_request("textDocument/implementation", to_string({make_position(iter.get_line(), get_line_pos(iter))}), [offsets, &result_processed](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/implementation", to_string({make_position(iter.get_line(), get_line_pos(iter))}), [offsets, &result_processed](JSON &&result, bool error) { if(!error) { - for(auto it = result.begin(); it != result.end(); ++it) { + auto locations = result.array_or_empty(); + if(locations.empty()) { + if(auto object = result.object_optional()) + locations.emplace_back(std::move(*object)); + } + for(auto &location_object : locations) { try { - LanguageProtocol::Location location(it->second); + LanguageProtocol::Location location(location_object); offsets->emplace_back(location.range.start.line, location.range.start.character, location.file); } catch(...) { @@ -2337,7 +2360,7 @@ boost::optional Source::LanguageProtocolView::get_named_parameter_symbol() void Source::LanguageProtocolView::update_type_coverage() { if(capabilities.type_coverage) { - write_request("textDocument/typeCoverage", {}, [this](const boost::property_tree::ptree &result, bool error) { + write_request("textDocument/typeCoverage", {}, [this](JSON &&result, bool error) { if(error) { if(update_type_coverage_retries > 0) { // Retry typeCoverage request, since these requests can fail while waiting for language server to start dispatcher.post([this] { @@ -2356,10 +2379,9 @@ void Source::LanguageProtocolView::update_type_coverage() { update_type_coverage_retries = 0; std::vector ranges; - auto uncoveredRanges = result.get_child("uncoveredRanges", boost::property_tree::ptree()); - for(auto it = uncoveredRanges.begin(); it != uncoveredRanges.end(); ++it) { + for(auto &uncovered_range : result.array_or_empty("uncoveredRanges")) { try { - ranges.emplace_back(it->second.get_child("range")); + ranges.emplace_back(uncovered_range.object("range")); } catch(...) { } diff --git a/src/source_language_protocol.hpp b/src/source_language_protocol.hpp index 135190b..76ec4e4 100644 --- a/src/source_language_protocol.hpp +++ b/src/source_language_protocol.hpp @@ -1,11 +1,11 @@ #pragma once #include "autocomplete.hpp" +#include "json.hpp" #include "mutex.hpp" #include "process.hpp" #include "source.hpp" #include #include -#include #include #include #include @@ -16,23 +16,23 @@ namespace Source { } namespace LanguageProtocol { - class Offset { + class Position { public: - Offset(const boost::property_tree::ptree &pt); + Position(const JSON &position); int line, character; - bool operator<(const Offset &rhs) const { + bool operator<(const Position &rhs) const { return line < rhs.line || (line == rhs.line && character < rhs.character); } - bool operator==(const Offset &rhs) const { + bool operator==(const Position &rhs) const { return line == rhs.line && character == rhs.character; } }; class Range { public: - Range(const boost::property_tree::ptree &pt); - Offset start, end; + Range(const JSON &range); + Position start, end; bool operator<(const Range &rhs) const { return start < rhs.start || (start == rhs.start && end < rhs.end); @@ -44,7 +44,7 @@ namespace LanguageProtocol { class Location { public: - Location(const boost::property_tree::ptree &pt, std::string file_ = {}); + Location(const JSON &location, std::string file_ = {}); Location(std::string _file, Range _range) : file(std::move(_file)), range(std::move(_range)) {} std::string file; Range range; @@ -59,8 +59,8 @@ namespace LanguageProtocol { class Documentation { public: - Documentation(const boost::property_tree::ptree &pt); - Documentation(std::string value) : value(std::move(value)) {} + Documentation(const boost::optional &documentation); + Documentation(std::string value = {}) : value(std::move(value)) {} std::string value; std::string kind; }; @@ -69,12 +69,12 @@ namespace LanguageProtocol { public: class RelatedInformation { public: - RelatedInformation(const boost::property_tree::ptree &pt); + RelatedInformation(const JSON &related_information); std::string message; Location location; }; - Diagnostic(const boost::property_tree::ptree &pt); + Diagnostic(JSON &&diagnostic); std::string message; Range range; /// 1: error, 2: warning, 3: information, 4: hint @@ -82,19 +82,20 @@ namespace LanguageProtocol { std::string code; std::vector related_informations; std::map> quickfixes; - boost::property_tree::ptree ptree; + /// Diagnostic object for textDocument/codeAction on new diagnostics + std::shared_ptr object; }; class TextEdit { public: - TextEdit(const boost::property_tree::ptree &pt, std::string new_text_ = {}); + TextEdit(const JSON &text_edit, std::string new_text_ = {}); Range range; std::string new_text; }; class TextDocumentEdit { public: - TextDocumentEdit(const boost::property_tree::ptree &pt); + TextDocumentEdit(const JSON &text_document_edit); TextDocumentEdit(std::string file, std::vector text_edits); std::string file; @@ -104,7 +105,7 @@ namespace LanguageProtocol { class WorkspaceEdit { public: WorkspaceEdit() = default; - WorkspaceEdit(const boost::property_tree::ptree &pt, boost::filesystem::path file_path); + WorkspaceEdit(const JSON &workspace_edit, boost::filesystem::path file_path); std::vector document_edits; }; @@ -161,7 +162,7 @@ namespace LanguageProtocol { size_t message_id GUARDED_BY(read_write_mutex) = 0; - std::map>> handlers GUARDED_BY(read_write_mutex); + std::map>> handlers GUARDED_BY(read_write_mutex); Mutex timeout_threads_mutex; std::vector timeout_threads GUARDED_BY(timeout_threads_mutex); @@ -176,11 +177,11 @@ namespace LanguageProtocol { 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 &&function = nullptr); + void write_request(Source::LanguageProtocolView *view, const std::string &method, const std::string ¶ms, std::function &&function = nullptr); void write_response(size_t id, const std::string &result); void write_notification(const std::string &method, const std::string ¶ms = {}); - void handle_server_notification(const std::string &method, const boost::property_tree::ptree ¶ms); - void handle_server_request(size_t id, const std::string &method, const boost::property_tree::ptree ¶ms); + void handle_server_notification(const std::string &method, JSON &¶ms); + void handle_server_request(size_t id, const std::string &method, JSON &¶ms); std::function on_exit_status; }; @@ -208,7 +209,7 @@ namespace Source { std::string to_string(const std::pair ¶m); std::string to_string(const std::vector> ¶ms); /// Helper method for calling client->write_request - void write_request(const std::string &method, const std::string ¶ms, std::function &&function); + void write_request(const std::string &method, const std::string ¶ms, std::function &&function); /// Helper method for calling client->write_notification void write_notification(const std::string &method); /// Helper method for calling client->write_notification @@ -264,7 +265,7 @@ namespace Source { std::string detail; LanguageProtocol::Documentation documentation; /// CompletionItem for completionItem/resolve - boost::property_tree::ptree ptree; + std::shared_ptr item_object; std::vector additional_text_edits; }; std::vector autocomplete_rows; @@ -280,6 +281,10 @@ namespace Source { /// for instance '=' for Python boost::optional get_named_parameter_symbol(); + /// Used when calling completionItem/resolve. Also increased in autocomplete->on_hide. + std::atomic set_tooltip_count = {0}; + + /// Used by update_type_coverage only (capabilities.type_coverage == true) std::vector last_diagnostics; sigc::connection update_type_coverage_connection; diff --git a/src/window.cpp b/src/window.cpp index 03b64b1..3910e25 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -153,7 +153,7 @@ Window::Window() { }); about.set_logo_icon_name("juci"); - about.set_version(Config::get().version); + about.set_version(JUCI_VERSION); about.set_authors({"(in order of appearance)", "Ted Johan Kristoffersen", "Jørgen Lien Sellæg", @@ -2257,124 +2257,118 @@ void Window::rename_token_entry() { void Window::save_session() { try { - boost::property_tree::ptree root_pt; - root_pt.put("folder", Directories::get().path.string()); + auto last_session = JSON(); + last_session.set("folder", Directories::get().path.string()); - boost::property_tree::ptree files_pt; + auto files = JSON(JSON::StructureType::array); 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 file = JSON(); + file.set("path", notebook_view.second->file_path.string()); + file.set("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)); + file.set("line", iter.get_line()); + file.set("line_offset", iter.get_line_offset()); + files.emplace_back(std::move(file)); } - root_pt.add_child("files", files_pt); + last_session.set("files", std::move(files)); - 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); + last_session.set("current_file", current_path); - boost::property_tree::ptree run_arguments_pt; + auto run_arguments = JSON(JSON::StructureType::array); for(auto &run_argument : Project::run_arguments) { if(run_argument.second.empty()) continue; if(boost::filesystem::exists(run_argument.first) && boost::filesystem::is_directory(run_argument.first)) { - 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)); + auto run_argument_object = JSON(); + run_argument_object.set("path", run_argument.first); + run_argument_object.set("arguments", run_argument.second); + run_arguments.emplace_back(std::move(run_argument_object)); } } - root_pt.add_child("run_arguments", run_arguments_pt); + last_session.set("run_arguments", std::move(run_arguments)); - boost::property_tree::ptree debug_run_arguments_pt; + auto debug_run_arguments = JSON(JSON::StructureType::array); 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; if(boost::filesystem::exists(debug_run_argument.first) && boost::filesystem::is_directory(debug_run_argument.first)) { - 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)); + auto debug_run_argument_object = JSON(); + debug_run_argument_object.set("path", debug_run_argument.first); + debug_run_argument_object.set("arguments", debug_run_argument.second.arguments); + debug_run_argument_object.set("remote_enabled", debug_run_argument.second.remote_enabled); + debug_run_argument_object.set("remote_host_port", debug_run_argument.second.remote_host_port); + debug_run_arguments.emplace_back(std::move(debug_run_argument_object)); } } - root_pt.add_child("debug_run_arguments", debug_run_arguments_pt); + last_session.set("debug_run_arguments", std::move(debug_run_arguments)); 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); + auto window = JSON(); + window.set("width", width); + window.set("height", height); + last_session.set("window", std::move(window)); - auto path = Config::get().home_juci_path / "last_session.json"; - std::ofstream output(path.string(), std::ios::binary); - if(output) { - JSON::write(output, root_pt); - output << '\n'; - } - else - Terminal::get().print("\e[31mError\e[m: could not write session file: " + filesystem::get_short_path(path).string() + "\n", true); + last_session.to_file(Config::get().home_juci_path / "last_session.json", 2); } - catch(...) { + catch(const std::exception &e) { + std::cerr << "Error writing session file: " << e.what(); } } -void Window::load_session(std::vector &directories, std::vector> &files, std::vector> &file_offsets, std::string ¤t_file, bool read_directories_and_files) { +void Window::load_session(std::vector &directories, std::vector> &files, std::vector> &file_offsets, boost::filesystem::path ¤t_file, bool read_directories_and_files) { + int default_width = 800, default_height = 600; try { - boost::property_tree::ptree root_pt; - boost::property_tree::read_json((Config::get().home_juci_path / "last_session.json").string(), root_pt); + auto last_session_file = Config::get().home_juci_path / "last_session.json"; + if(!boost::filesystem::exists(last_session_file)) { + set_default_size(default_width, default_height); + return; + } + JSON last_session(last_session_file); if(read_directories_and_files) { - auto folder = root_pt.get("folder"); + auto folder = boost::filesystem::path(last_session.string_or("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("path"); - auto notebook = file_pt.second.get("notebook"); - auto line = file_pt.second.get("line"); - auto line_offset = file_pt.second.get("line_offset"); + for(auto &file_object : last_session.array_or_empty("files")) { + auto file = boost::filesystem::path(file_object.string_or("path", "")); + auto notebook = file_object.integer_or("notebook", 0, JSON::ParseOptions::accept_string); + auto line = file_object.integer_or("line", 0, JSON::ParseOptions::accept_string); + auto line_offset = file_object.integer_or("line_offset", 0, JSON::ParseOptions::accept_string); 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("current_file"); + current_file = boost::filesystem::path(last_session.string_or("current_file", "")); } - for(auto &run_argument : root_pt.get_child(("run_arguments"))) { - auto path = run_argument.second.get("path"); + for(auto &run_argument : last_session.array_or_empty("run_arguments")) { + auto path = boost::filesystem::path(run_argument.string_or("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("arguments")); + if(!path.empty() && boost::filesystem::exists(path, ec) && boost::filesystem::is_directory(path, ec)) + Project::run_arguments.emplace(path.string(), run_argument.string_or("arguments", "")); } - for(auto &debug_run_argument : root_pt.get_child(("debug_run_arguments"))) { - auto path = debug_run_argument.second.get("path"); + for(auto &debug_run_argument : last_session.array_or_empty("debug_run_arguments")) { + auto path = boost::filesystem::path(debug_run_argument.string_or("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("arguments"), - debug_run_argument.second.get("remote_enabled"), - debug_run_argument.second.get("remote_host_port")}); + if(!path.empty() && boost::filesystem::exists(path, ec) && boost::filesystem::is_directory(path, ec)) + Project::debug_run_arguments.emplace(path.string(), Project::DebugRunArguments{debug_run_argument.string_or("arguments", ""), + debug_run_argument.boolean_or("remote_enabled", false, JSON::ParseOptions::accept_string), + debug_run_argument.string_or("remote_host_port", "")}); } - auto window_pt = root_pt.get_child("window"); - set_default_size(window_pt.get("width"), window_pt.get("height")); + if(auto window = last_session.object_optional("window")) + set_default_size(window->integer_or("width", default_width, JSON::ParseOptions::accept_string), window->integer_or("height", default_height, JSON::ParseOptions::accept_string)); + else + set_default_size(default_width, default_height); } - catch(...) { - set_default_size(800, 600); + catch(const std::exception &e) { + set_default_size(default_width, default_height); + std::cerr << "Error reading session file: " << e.what(); } } diff --git a/src/window.hpp b/src/window.hpp index 7a58199..7b9455f 100644 --- a/src/window.hpp +++ b/src/window.hpp @@ -14,7 +14,7 @@ public: } void add_widgets(); void save_session(); - void load_session(std::vector &directories, std::vector> &files, std::vector> &file_offsets, std::string ¤t_file, bool read_directories_and_files); + void load_session(std::vector &directories, std::vector> &files, std::vector> &file_offsets, boost::filesystem::path ¤t_file, bool read_directories_and_files); protected: bool on_key_press_event(GdkEventKey *event) override; diff --git a/tests/json_test.cpp b/tests/json_test.cpp index 92057ef..11511cb 100644 --- a/tests/json_test.cpp +++ b/tests/json_test.cpp @@ -1,5 +1,7 @@ +#include "files.hpp" #include "json.hpp" #include +#include #include #include @@ -9,6 +11,12 @@ int main() { "integer_as_string": "3", "string": "some\ntext", "string2": "1test", + "boolean": true, + "boolean_as_integer": 1, + "boolean_as_string1": "true", + "boolean_as_string2": "1", + "pi": 3.14, + "pi_as_string": "3.14", "array": [ 1, 3, @@ -29,30 +37,285 @@ int main() { ] } })"; + + std::string json_no_indent; + for(auto &chr : json) { + if(chr != ' ' && chr != '\n') + json_no_indent += chr; + } + + { + JSON j(default_config_file); + g_assert(!j.string("version").empty()); + } + { - std::istringstream istream(json); + JSON j(json); + g_assert(j.to_string(2) == json); + g_assert(j.to_string() == json_no_indent); + + { + std::ostringstream ss; + ss << std::setw(2) << j; + g_assert(ss.str() == json); + } + { + std::ostringstream ss; + ss << j; + g_assert(ss.str() == json_no_indent); + } + + g_assert(j.integer("integer_as_string", JSON::ParseOptions::accept_string) == 3); + g_assert(j.boolean("boolean_as_string1", JSON::ParseOptions::accept_string) == true); + g_assert(j.boolean("boolean_as_string2", JSON::ParseOptions::accept_string) == true); + g_assert(j.floating_point("pi_as_string", JSON::ParseOptions::accept_string) > 3.1 && j.floating_point("pi_as_string", JSON::ParseOptions::accept_string) < 3.2); + + g_assert(j.string("integer_as_string") == "3"); + try { + j.integer("integer_as_string"); + g_assert(false); + } + catch(...) { + } + g_assert(j.string("boolean_as_string1") == "true"); + g_assert(j.string("boolean_as_string2") == "1"); + try { + j.boolean("boolean_as_string1"); + j.boolean("boolean_as_string2"); + g_assert(false); + } + catch(...) { + } + g_assert(j.string("pi_as_string") == "3.14"); + try { + j.boolean("pi_as_string"); + g_assert(false); + } + catch(...) { + } + + g_assert(j.boolean("boolean_as_integer") == true); + g_assert(j.floating_point("pi") >= 3.13 && j.floating_point("pi") <= 3.15); + g_assert(j.floating_point("integer") > 2.9 && j.floating_point("integer") < 3.1); + g_assert(j.integer("pi") == 3); + + j.object(); + g_assert(!j.children().empty()); + g_assert(!j.array("array").empty()); + j.child("array"); - boost::property_tree::ptree pt; - boost::property_tree::read_json(istream, pt); + try { + j.object("array"); + g_assert(false); + } + catch(...) { + } - std::ostringstream ostream; - JSON::write(ostream, pt, true, {"integer", "array"}); - g_assert(ostream.str() == json); + j.object("object"); + j.child("object"); + try { + j.array("object"); + g_assert(false); + } + catch(...) { + } } + { - std::istringstream istream(json); + JSON j(json); + j.set("test", 2); + g_assert(j.integer("test") == 2); + g_assert(j.array("array").size() == 3); + j.child("array").emplace_back(JSON()); + g_assert(j.array("array").size() == 4); - boost::property_tree::ptree pt; - boost::property_tree::read_json(istream, pt); + try { + j.child("object").emplace_back(JSON()); + g_assert(false); + } + catch(...) { + } + } - std::ostringstream ostream; - JSON::write(ostream, pt, false, {"integer", "array"}); + { + JSON j(json); + g_assert(j.owner); + g_assert(j.object("object").children().size() == 3); + g_assert(!j.object("object").owner); + auto owner = JSON::make_owner(j.object("object")); + g_assert(owner.owner); + g_assert(owner.children().size() == 3); + for(auto &child : owner.children()) { + g_assert(!child.first.empty()); + g_assert(!child.second.owner); + } + g_assert(owner.array("array").size() == 3); + size_t count = 0; + for(auto &e : owner.array("array")) { + ++count; + g_assert(e.integer() >= 1); + g_assert(e.floating_point() > 0.5); + g_assert(!e.owner); + } + g_assert(count == 3); - std::string non_pretty; - for(auto &chr : json) { - if(chr != ' ' && chr != '\n') - non_pretty += chr; + g_assert(j.owner); + g_assert(j.child("object").to_string() == "null"); + g_assert(!j.child("object").owner); + g_assert(!j.child("array").owner); + for(auto &child : j.children()) { + g_assert(!child.first.empty()); + g_assert(!child.second.owner); + } + for(auto &e : j.array("array")) + g_assert(!e.owner); + } + + { + JSON j; + JSON child; + g_assert(j.owner); + g_assert(child.owner); + child.set("a_string", "test"); + child.set("a_bool", true); + g_assert(child.string("a_string") == "test"); + g_assert(child.child("a_string").string() == "test"); + g_assert(child.boolean("a_bool") == true); + g_assert(child.child("a_bool").boolean() == true); + + j.set("an_object", std::move(child)); + assert(child.to_string() == "null"); + assert(!child.owner); + assert(j.owner); + assert(!j.object("an_object").owner); + assert(!j.object("an_object").child("a_string").owner); + assert(!j.object("an_object").child("a_bool").owner); + g_assert(j.object("an_object").string("a_string") == "test"); + g_assert(j.object("an_object").child("a_string").string() == "test"); + g_assert(j.object("an_object").boolean("a_bool") == true); + g_assert(j.object("an_object").child("a_bool").boolean() == true); + } + + { + JSON j(json); + g_assert(!j.string_optional()); + g_assert(!j.integer_optional()); + g_assert(!j.boolean_optional()); + g_assert(!j.floating_point_optional()); + g_assert(!j.array_optional()); + g_assert(j.object_optional()); + + g_assert(!j.string_optional("integer")); + g_assert(j.integer_optional("integer")); + g_assert(!j.boolean_optional("integer")); + g_assert(j.floating_point_optional("integer")); + g_assert(!j.array_optional("integer")); + g_assert(!j.object_optional("integer")); + + g_assert(j.string_optional("string")); + g_assert(!j.integer_optional("string")); + g_assert(!j.boolean_optional("string")); + g_assert(!j.floating_point_optional("string")); + g_assert(!j.array_optional("string")); + g_assert(!j.object_optional("string")); + + g_assert(!j.string_optional("array")); + g_assert(!j.integer_optional("array")); + g_assert(!j.boolean_optional("array")); + g_assert(!j.floating_point_optional("array")); + g_assert(j.array_optional("array")); + g_assert(!j.object_optional("array")); + + g_assert(!j.string_optional("boolean")); + g_assert(!j.integer_optional("boolean")); + g_assert(j.boolean_optional("boolean")); + g_assert(!j.floating_point_optional("boolean")); + g_assert(!j.array_optional("boolean")); + g_assert(!j.object_optional("boolean")); + + g_assert(!j.string_optional("pi")); + g_assert(j.integer_optional("pi")); + g_assert(!j.boolean_optional("pi")); + g_assert(j.floating_point_optional("pi")); + g_assert(!j.array_optional("pi")); + g_assert(!j.object_optional("pi")); + } + + { + JSON j(json); + g_assert(j.string_or("fail") == "fail"); + g_assert(j.integer_or(-1) == -1); + g_assert(j.boolean_or(false) == false); + g_assert(j.boolean_or(true) == true); + g_assert(j.floating_point_or(-1.5) >= -1.6 && j.floating_point_or(-1.5) < -1.4); + g_assert(j.array_or_empty().empty()); + g_assert(!j.children_or_empty().empty()); + + g_assert(j.string_or("integer", "fail") == "fail"); + g_assert(j.integer_or("integer", -1) == 3); + g_assert(j.boolean_or("integer", false) == false); + g_assert(j.boolean_or("integer", true) == true); + g_assert(j.floating_point_or("integer", -1.5) >= 2.9 && j.floating_point_or("integer", -1.5) < 3.1); + g_assert(j.array_or_empty("integer").empty()); + g_assert(j.children_or_empty("integer").empty()); + + g_assert(j.string_or("string", "fail") == "some\ntext"); + g_assert(j.integer_or("string", -1) == -1); + g_assert(j.boolean_or("string", false) == false); + g_assert(j.boolean_or("string", true) == true); + g_assert(j.floating_point_or("string", -1.5) >= -1.6 && j.floating_point_or("string", -1.5) < -1.4); + g_assert(j.array_or_empty("string").empty()); + g_assert(j.children_or_empty("string").empty()); + + g_assert(j.string_or("array", "fail") == "fail"); + g_assert(j.integer_or("array", -1) == -1); + g_assert(j.boolean_or("array", false) == false); + g_assert(j.boolean_or("array", true) == true); + g_assert(j.floating_point_or("array", -1.5) >= -1.6 && j.floating_point_or("array", -1.5) < -1.4); + g_assert(!j.array_or_empty("array").empty()); + g_assert(j.children_or_empty("array").empty()); + + g_assert(j.string_or("boolean", "fail") == "fail"); + g_assert(j.integer_or("boolean", -1) == -1); + g_assert(j.boolean_or("boolean", false) == true); + g_assert(j.floating_point_or("boolean", -1.5) >= -1.6 && j.floating_point_or("boolean", -1.5) < -1.4); + g_assert(j.array_or_empty("boolean").empty()); + g_assert(j.children_or_empty("boolean").empty()); + + g_assert(j.string_or("pi", "fail") == "fail"); + g_assert(j.integer_or("pi", -1) == 3); + g_assert(j.boolean_or("pi", false) == false); + g_assert(j.boolean_or("pi", true) == true); + g_assert(j.floating_point_or("pi", -1.5) >= 3.1 && j.floating_point_or("pi", -1.5) < 3.2); + g_assert(j.array_or_empty("pi").empty()); + g_assert(j.children_or_empty("pi").empty()); + } + + { + JSON j(json); + j.child("integer"); + j.child("array"); + j.child("object"); + j.remove("integer"); + j.remove("array"); + j.remove("object"); + try { + j.child("integer"); + g_assert(false); + } + catch(...) { + } + try { + j.child("array"); + g_assert(false); + } + catch(...) { + } + try { + j.child("object"); + g_assert(false); + } + catch(...) { } - g_assert(ostream.str() == non_pretty); } } diff --git a/tests/language_protocol_server_test.cpp b/tests/language_protocol_server_test.cpp index ef3172d..d3a26fc 100644 --- a/tests/language_protocol_server_test.cpp +++ b/tests/language_protocol_server_test.cpp @@ -1,6 +1,7 @@ #include "json.hpp" #include #include +#include #ifdef _WIN32 #include @@ -26,36 +27,16 @@ int main() { buffer.resize(size); std::cin.read(&buffer[0], size); std::stringstream ss(buffer); - boost::property_tree::ptree pt; - boost::property_tree::json_parser::read_json(ss, pt); - if(pt.get("method") != "initialize") + JSON object(ss); + if(object.string("method") != "initialize") return 1; std::string result = R"({ + "id": 0, "jsonrpc": "2.0", - "id": "0", "result": { "capabilities": { - "textDocumentSync": { - "openClose": "true", - "change": "2", - "save": "" - }, - "selectionRangeProvider": "true", - "hoverProvider": "true", - "completionProvider": { - "triggerCharacters": [":", ".", "'"] - }, - "signatureHelpProvider": { - "triggerCharacters": ["(", ","] - }, - "definitionProvider": "true", - "typeDefinitionProvider": "true", - "implementationProvider": "true", - "referencesProvider": "true", - "documentHighlightProvider": "true", - "documentSymbolProvider": "true", - "workspaceSymbolProvider": "true", + "callHierarchyProvider": true, "codeActionProvider": { "codeActionKinds": [ "", @@ -65,45 +46,75 @@ int main() { "refactor.inline", "refactor.rewrite" ], - "resolveProvider": "true" + "resolveProvider": true }, "codeLensProvider": { - "resolveProvider": "true" + "resolveProvider": true }, - "documentFormattingProvider": "true", + "completionProvider": { + "triggerCharacters": [ + ":", + ".", + "'" + ] + }, + "definitionProvider": true, + "documentFormattingProvider": true, + "documentHighlightProvider": true, "documentOnTypeFormattingProvider": { "firstTriggerCharacter": "=", - "moreTriggerCharacter": [".", ">", "{"] + "moreTriggerCharacter": [ + ".", + ">", + "{" + ] }, - "renameProvider": { - "prepareProvider": "true" + "documentSymbolProvider": true, + "experimental": { + "joinLines": true, + "onEnter": true, + "parentModule": true, + "runnables": { + "kinds": [ + "cargo" + ] + }, + "ssr": true, + "workspaceSymbolScopeKindFiltering": true }, - "foldingRangeProvider": "true", - "workspace": { - "fileOperations": { - "willRename": { - "filters": [ - { - "scheme": "file", - "pattern": { - "glob": "**/*.rs", - "matches": "file" - } - }, - { - "scheme": "file", - "pattern": { - "glob": "**", - "matches": "folder" - } - } - ] - } - } + "foldingRangeProvider": true, + "hoverProvider": true, + "implementationProvider": true, + "referencesProvider": true, + "renameProvider": { + "prepareProvider": true }, - "callHierarchyProvider": "true", + "selectionRangeProvider": true, "semanticTokensProvider": { + "full": { + "delta": true + }, "legend": { + "tokenModifiers": [ + "documentation", + "declaration", + "definition", + "static", + "abstract", + "deprecated", + "readonly", + "constant", + "controlFlow", + "injected", + "mutable", + "consuming", + "async", + "unsafe", + "attribute", + "trait", + "callable", + "intraDocLink" + ], "tokenTypes": [ "comment", "keyword", @@ -153,43 +164,45 @@ int main() { "typeAlias", "union", "unresolvedReference" - ], - "tokenModifiers": [ - "documentation", - "declaration", - "definition", - "static", - "abstract", - "deprecated", - "readonly", - "constant", - "controlFlow", - "injected", - "mutable", - "consuming", - "async", - "unsafe", - "attribute", - "trait", - "callable", - "intraDocLink" ] }, - "range": "true", - "full": { - "delta": "true" + "range": true + }, + "signatureHelpProvider": { + "triggerCharacters": [ + "(", + "," + ] + }, + "textDocumentSync": { + "change": 2, + "openClose": true, + "save": {} + }, + "typeDefinitionProvider": true, + "workspace": { + "fileOperations": { + "willRename": { + "filters": [ + { + "pattern": { + "glob": "**/*.rs", + "matches": "file" + }, + "scheme": "file" + }, + { + "pattern": { + "glob": "**", + "matches": "folder" + }, + "scheme": "file" + } + ] + } } }, - "experimental": { - "joinLines": "true", - "ssr": "true", - "onEnter": "true", - "parentModule": "true", - "runnables": { - "kinds": ["cargo"] - }, - "workspaceSymbolScopeKindFiltering": "true" - } + "workspaceSymbolProvider": true }, "serverInfo": { "name": "rust-analyzer", @@ -211,9 +224,8 @@ int main() { buffer.resize(size); std::cin.read(&buffer[0], size); std::stringstream ss(buffer); - boost::property_tree::ptree pt; - boost::property_tree::json_parser::read_json(ss, pt); - if(pt.get("method") != "initialized") + JSON object(ss); + if(object.string("method") != "initialized") return 1; } @@ -226,9 +238,8 @@ int main() { buffer.resize(size); std::cin.read(&buffer[0], size); std::stringstream ss(buffer); - boost::property_tree::ptree pt; - boost::property_tree::json_parser::read_json(ss, pt); - if(pt.get("method") != "textDocument/didOpen") + JSON object(ss); + if(object.string("method") != "textDocument/didOpen") return 1; } @@ -241,9 +252,8 @@ int main() { buffer.resize(size); std::cin.read(&buffer[0], size); std::stringstream ss(buffer); - boost::property_tree::ptree pt; - boost::property_tree::json_parser::read_json(ss, pt); - if(pt.get("method") != "textDocument/didChange") + JSON object(ss); + if(object.string("method") != "textDocument/didChange") return 1; } @@ -256,24 +266,23 @@ int main() { buffer.resize(size); std::cin.read(&buffer[0], size); std::stringstream ss(buffer); - boost::property_tree::ptree pt; - boost::property_tree::json_parser::read_json(ss, pt); - if(pt.get("method") != "textDocument/formatting") + JSON object(ss); + if(object.string("method") != "textDocument/formatting") return 1; std::string result = R"({ "jsonrpc": "2.0", - "id": "1", + "id": 1, "result": [ { "range": { "start": { - "line": "0", - "character": "0" + "line": 0, + "character": 0 }, "end": { - "line": "0", - "character": "1" + "line": 0, + "character": 1 } }, "newText": "" @@ -294,9 +303,8 @@ int main() { buffer.resize(size); std::cin.read(&buffer[0], size); std::stringstream ss(buffer); - boost::property_tree::ptree pt; - boost::property_tree::json_parser::read_json(ss, pt); - if(pt.get("method") != "textDocument/didChange") + JSON object(ss); + if(object.string("method") != "textDocument/didChange") return 1; } @@ -309,25 +317,24 @@ int main() { buffer.resize(size); std::cin.read(&buffer[0], size); std::stringstream ss(buffer); - boost::property_tree::ptree pt; - boost::property_tree::json_parser::read_json(ss, pt); - if(pt.get("method") != "textDocument/definition") + JSON object(ss); + if(object.string("method") != "textDocument/definition") return 1; std::string result = R"({ "jsonrpc": "2.0", - "id": "2", + "id": 2, "result": [ { "uri": "file://main.rs", "range": { "start": { - "line": "0", - "character": "3" + "line": 0, + "character": 3 }, "end": { - "line": "0", - "character": "7" + "line": 0, + "character": 7 } } } @@ -347,25 +354,24 @@ int main() { buffer.resize(size); std::cin.read(&buffer[0], size); std::stringstream ss(buffer); - boost::property_tree::ptree pt; - boost::property_tree::json_parser::read_json(ss, pt); - if(pt.get("method") != "textDocument/typeDefinition") + JSON object(ss); + if(object.string("method") != "textDocument/typeDefinition") return 1; std::string result = R"({ "jsonrpc": "2.0", - "id": "3", + "id": 3, "result": [ { "uri": "file://main.rs", "range": { "start": { - "line": "0", - "character": "4" + "line": 0, + "character": 4 }, "end": { - "line": "0", - "character": "7" + "line": 0, + "character": 7 } } } @@ -385,25 +391,24 @@ int main() { buffer.resize(size); std::cin.read(&buffer[0], size); std::stringstream ss(buffer); - boost::property_tree::ptree pt; - boost::property_tree::json_parser::read_json(ss, pt); - if(pt.get("method") != "textDocument/implementation") + JSON object(ss); + if(object.string("method") != "textDocument/implementation") return 1; std::string result = R"({ "jsonrpc": "2.0", - "id": "4", + "id": 4, "result": [ { "uri": "file://main.rs", "range": { "start": { - "line": "0", - "character": "0" + "line": 0, + "character": 0 }, "end": { - "line": "0", - "character": "1" + "line": 0, + "character": 1 } } }, @@ -411,12 +416,12 @@ int main() { "uri": "file://main.rs", "range": { "start": { - "line": "1", - "character": "0" + "line": 1, + "character": 0 }, "end": { - "line": "1", - "character": "1" + "line": 1, + "character": 1 } } } @@ -436,26 +441,25 @@ int main() { buffer.resize(size); std::cin.read(&buffer[0], size); std::stringstream ss(buffer); - boost::property_tree::ptree pt; - boost::property_tree::json_parser::read_json(ss, pt); - if(pt.get("method") != "textDocument/references") + JSON object(ss); + if(object.string("method") != "textDocument/references") return 1; std::string result = R"({ "jsonrpc": "2.0", - "id": "5", + "id": 5, "result": [ { "uri": "file://)" + JSON::escape_string(file_path.string()) + R"(", "range": { "start": { - "line": "2", - "character": "19" + "line": 2, + "character": 19 }, "end": { - "line": "2", - "character": "20" + "line": 2, + "character": 20 } } }, @@ -464,12 +468,12 @@ int main() { R"(", "range": { "start": { - "line": "1", - "character": "8" + "line": 1, + "character": 8 }, "end": { - "line": "1", - "character": "9" + "line": 1, + "character": 9 } } } @@ -489,30 +493,29 @@ int main() { buffer.resize(size); std::cin.read(&buffer[0], size); std::stringstream ss(buffer); - boost::property_tree::ptree pt; - boost::property_tree::json_parser::read_json(ss, pt); - if(pt.get("method") != "textDocument/documentSymbol") + JSON object(ss); + if(object.string("method") != "textDocument/documentSymbol") return 1; std::string result = R"({ "jsonrpc": "2.0", - "id": "6", + "id": 6, "result": [ { "name": "main", - "kind": "12", + "kind": 12, "tags": "", - "deprecated": "false", + "deprecated": false, "location": { "uri": "file://main.rs", "range": { "start": { - "line": "0", - "character": "0" + "line": 0, + "character": 0 }, "end": { - "line": "3", - "character": "1" + "line": 3, + "character": 1 } } } @@ -533,9 +536,8 @@ int main() { buffer.resize(size); std::cin.read(&buffer[0], size); std::stringstream ss(buffer); - boost::property_tree::ptree pt; - boost::property_tree::json_parser::read_json(ss, pt); - if(pt.get("method") != "textDocument/didClose") + JSON object(ss); + if(object.string("method") != "textDocument/didClose") return 1; } @@ -548,14 +550,13 @@ int main() { buffer.resize(size); std::cin.read(&buffer[0], size); std::stringstream ss(buffer); - boost::property_tree::ptree pt; - boost::property_tree::json_parser::read_json(ss, pt); - if(pt.get("method") != "shutdown") + JSON object(ss); + if(object.string("method") != "shutdown") return 1; std::string result = R"({ "jsonrpc": "2.0", - "id": "7", + "id": 7, "result": {} })"; std::cout << "Content-Length: " << result.size() << "\r\n\r\n" @@ -571,9 +572,8 @@ int main() { buffer.resize(size); std::cin.read(&buffer[0], size); std::stringstream ss(buffer); - boost::property_tree::ptree pt; - boost::property_tree::json_parser::read_json(ss, pt); - if(pt.get("method") != "exit") + JSON object(ss); + if(object.string("method") != "exit") return 1; } } From db63cedcd681da170487db0ac18cac56704cfe05 Mon Sep 17 00:00:00 2001 From: eidheim Date: Thu, 1 Jul 2021 12:17:02 +0200 Subject: [PATCH 155/375] Improved positioning of tooltips through more exactly computing the size of the tooltip to be shown --- src/tooltips.cpp | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/tooltips.cpp b/src/tooltips.cpp index 08f2f98..ed3af23 100644 --- a/src/tooltips.cpp +++ b/src/tooltips.cpp @@ -193,11 +193,23 @@ void Tooltip::show(bool disregard_drawn, const std::function &on_motion) return false; }); - auto layout = Pango::Layout::create(tooltip_text_view->get_pango_context()); - if(auto tag = code_tag ? code_tag : code_block_tag) - layout->set_font_description(tag->property_font_desc()); - layout->set_text(buffer->get_text()); - layout->get_pixel_size(size.first, size.second); + Gdk::Rectangle rect; + auto iter = buffer->begin(); + tooltip_text_view->get_iter_location(iter, rect); + int min_x = rect.get_x(); + int max_x = rect.get_x() + rect.get_width(); + int height = 0; + while(true) { + if(iter.ends_line()) { + tooltip_text_view->get_iter_location(iter, rect); + max_x = std::max(max_x, rect.get_x() + rect.get_width()); + height += rect.get_height(); + } + if(iter.is_end()) + break; + iter.forward_char(); + } + size = {max_x - min_x, height}; size.first += 6; // 2xpadding size.second += 8; // 2xpadding + 2 From 8fb81c11d96d32a63ffcf0660f452a566278faaa Mon Sep 17 00:00:00 2001 From: eidheim Date: Thu, 1 Jul 2021 20:31:14 +0200 Subject: [PATCH 156/375] Improved placement of tooltips: now places tooltips below cursor if no space above or right --- src/tooltips.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/tooltips.cpp b/src/tooltips.cpp index ed3af23..2863cb1 100644 --- a/src/tooltips.cpp +++ b/src/tooltips.cpp @@ -280,12 +280,25 @@ void Tooltip::show(bool disregard_drawn, const std::function &on_motion) if(!disregard_drawn) { if(Tooltips::drawn_tooltips_rectangle.get_width() != 0) { - if(rectangle.intersects(Tooltips::drawn_tooltips_rectangle)) { + if(rectangle.intersects(Tooltips::drawn_tooltips_rectangle)) { // Put tooltip above other tooltips 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); + else { // Put tooltip to the right if the tooltip would be placed above the screen + int new_x = Tooltips::drawn_tooltips_rectangle.get_x() + Tooltips::drawn_tooltips_rectangle.get_width() + 2; + if(new_x + size.first < Gdk::Screen::get_default()->get_width()) + rectangle.set_x(new_x); + else { // Put tooltip below if the tooltip would be placed outside of the screen on the right side + new_y = Tooltips::drawn_tooltips_rectangle.get_y() + Tooltips::drawn_tooltips_rectangle.get_height(); + int root_x, root_y; + if(view) { + view->get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->get_root_coords(activation_rectangle.get_x(), activation_rectangle.get_y(), root_x, root_y); + if(new_y < root_y + activation_rectangle.get_height()) + new_y = root_y + activation_rectangle.get_height() + 2; + } + rectangle.set_y(new_y); + } + } } Tooltips::drawn_tooltips_rectangle.join(rectangle); } From 40905d54ed138ac55352b0f9034779022505be97 Mon Sep 17 00:00:00 2001 From: eidheim Date: Sat, 3 Jul 2021 07:52:32 +0200 Subject: [PATCH 157/375] Made similar symbol tags more visibile when using dark themes --- src/source.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/source.cpp b/src/source.cpp index 299f1f6..df12e6b 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -138,6 +138,7 @@ Source::View::View(const boost::filesystem::path &file_path, const Glib::RefPtr< similar_symbol_tag = get_buffer()->create_tag(); similar_symbol_tag->property_weight() = Pango::WEIGHT_ULTRAHEAVY; + similar_symbol_tag->property_background_rgba() = Gdk::RGBA("rgba(255, 255, 255, 0.075)"); clickable_tag = get_buffer()->create_tag(); clickable_tag->property_underline() = Pango::Underline::UNDERLINE_SINGLE; clickable_tag->property_underline_set() = true; From 603835abdc7e7132af0eb3c3ff8742fcd641d8f4 Mon Sep 17 00:00:00 2001 From: eidheim Date: Sun, 4 Jul 2021 08:37:04 +0200 Subject: [PATCH 158/375] Added style class to selection dialogs --- src/selection_dialog.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/selection_dialog.cpp b/src/selection_dialog.cpp index ea68c91..8bef1ab 100644 --- a/src/selection_dialog.cpp +++ b/src/selection_dialog.cpp @@ -44,6 +44,8 @@ SelectionDialogBase::SelectionDialogBase(Gtk::TextView *text_view, const boost:: window.set_type_hint(Gdk::WindowTypeHint::WINDOW_TYPE_HINT_COMBO); + window.get_style_context()->add_class("juci_selection_dialog"); + search_entry.signal_changed().connect( [this] { if(on_search_entry_changed) From 10a1ccba3c56229785ccd802591ce96784bfaeee Mon Sep 17 00:00:00 2001 From: eidheim Date: Sun, 4 Jul 2021 10:07:45 +0200 Subject: [PATCH 159/375] Reduced width of C/C++ completions slightly --- src/source_clang.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/source_clang.cpp b/src/source_clang.cpp index c77596d..8adf276 100644 --- a/src/source_clang.cpp +++ b/src/source_clang.cpp @@ -1049,7 +1049,7 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa break; } if(kind == clangmm::CompletionChunk_ResultType) - return_text = std::string(" → ") + chunk_cstr.c_str; + return_text = std::string(" → ") + chunk_cstr.c_str; else text += chunk_cstr.c_str; } @@ -1105,7 +1105,7 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa } std::string row; - auto pos = text.find(" → "); + auto pos = text.find(" → "); if(pos != std::string::npos) row = text.substr(0, pos); else From d5eabfd4e7c79f6ab7977bc6d01a46ef58796333 Mon Sep 17 00:00:00 2001 From: eidheim Date: Tue, 6 Jul 2021 10:59:10 +0200 Subject: [PATCH 160/375] Made use of prettier library when possible to speed up prettier formatting --- src/source.cpp | 264 ++++++++++++++++++++++++++++++++++++------- src/source.hpp | 3 + src/utility.cpp | 23 ++++ src/utility.hpp | 5 + src/window.cpp | 6 + tests/CMakeLists.txt | 1 + 6 files changed, 259 insertions(+), 43 deletions(-) diff --git a/src/source.cpp b/src/source.cpp index df12e6b..44f8584 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -33,6 +33,8 @@ inline pid_t get_current_process_id() { } #endif +std::unique_ptr Source::View::prettier_background_process = {}; + Glib::RefPtr Source::LanguageManager::get_default() { static auto instance = Gsv::LanguageManager::create(); return instance; @@ -748,7 +750,6 @@ void Source::View::setup_format_style(bool is_generic_view) { }); } format_style = [this, is_generic_view](bool continue_without_style_file) { - auto command = prettier.string(); if(!continue_without_style_file) { auto search_path = file_path.parent_path(); while(true) { @@ -779,63 +780,240 @@ void Source::View::setup_format_style(bool is_generic_view) { } } - command += " --stdin-filepath " + filesystem::escape_argument(this->file_path.string()); - - 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 = std::stoi(line); - if(offset < get_buffer()->size()) { - get_buffer()->place_cursor(get_buffer()->get_iter_at_offset(offset)); + static auto get_prettier_library = [] { + std::string library; + TinyProcessLib::Process process( + "npm root -g", "", + [&library](const char *buffer, size_t length) { + library += std::string(buffer, length); + }, + [](const char *, size_t) {}); + if(process.get_exit_status() == 0) { + while(!library.empty() && (library.back() == '\n' || library.back() == '\r')) + library.pop_back(); + library += "/prettier"; + boost::system::error_code ec; + if(boost::filesystem::is_directory(library, ec)) + return library; + else { + auto parent_path = prettier.parent_path(); + if(parent_path.filename() == "bin") { + auto path = parent_path.parent_path() / "lib" / "prettier"; + if(boost::filesystem::is_directory(path, ec)) + return path.string(); + } + // Try find prettier library installed with homebrew on MacOS + boost::filesystem::path path = "/usr/local/opt/prettier/libexec/lib/node_modules/prettier"; + if(boost::filesystem::is_directory(path, ec)) + return path.string(); + path = "/opt/homebrew/opt/prettier/libexec/lib/node_modules/prettier"; + if(boost::filesystem::is_directory(path, ec)) + return path.string(); + } + } + return std::string(); + }; + static auto prettier_library = get_prettier_library(); + + auto buffer = get_buffer()->get_text().raw(); + if(!prettier_library.empty() && buffer.size() < 25000) { // Node.js repl seems to be limited to around 28786 bytes + struct Error { + std::string message; + int line = -1, index = -1; + }; + struct Result { + std::string text; + int cursor_offset; + }; + static Mutex mutex; + static boost::optional result GUARDED_BY(mutex); + static boost::optional error GUARDED_BY(mutex); + { + LockGuard lock(mutex); + result = {}; + error = {}; + } + if(prettier_background_process) { + int exit_status; + if(prettier_background_process->try_get_exit_status(exit_status)) + prettier_background_process = {}; + } + if(!prettier_background_process) { + prettier_background_process = std::make_unique( + "node -e \"const repl = require('repl');repl.start({prompt: '', ignoreUndefined: true, preview: false});\"", + "", + [](const char *bytes, size_t n) { + try { + JSON json(std::string(bytes, n)); + LockGuard lock(mutex); + result = Result{json.string("formatted"), static_cast(json.integer_or("cursorOffset", -1))}; + } + catch(const std::exception &e) { + LockGuard lock(mutex); + error = Error{e.what()}; + error->message += "\nOutput from prettier: " + std::string(bytes, n); + } + }, + [](const char *bytes, size_t n) { + size_t i = 0; + for(; i < n; ++i) { + if(bytes[i] == '\n') + break; + } + std::string first_line(bytes, i); + std::string message; + int line = -1, line_index = -1; + + if(starts_with(first_line, "ConfigError: ")) + message = std::string(bytes + 13, n - 13); + else if(starts_with(first_line, "ParseError: ")) { + const static std::regex regex(R"(^(.*) \(([0-9]*):([0-9]*)\)$)", std::regex::optimize); + std::smatch sm; + first_line.erase(0, 12); + if(std::regex_match(first_line, sm, regex)) { + message = sm[1].str(); + try { + line = std::stoi(sm[2].str()); + line_index = std::stoi(sm[3].str()); + } + catch(...) { + line = -1; + line_index = -1; + } + } + else + message = std::string(bytes + 12, n - 12); + } + else + message = std::string(bytes, n); + + LockGuard lock(mutex); + error = Error{std::move(message), line, line_index}; + }, + true, TinyProcessLib::Config{1048576}); + + prettier_background_process->write("const prettier = require(\"" + escape(prettier_library, {'"'}) + "\");\n"); + } + + std::string options = "filepath: \"" + escape(file_path.string(), {'"'}) + "\""; + if(get_buffer()->get_has_selection()) { // Cannot be used together with cursorOffset + Gtk::TextIter start, end; + get_buffer()->get_selection_bounds(start, end); + options += ", rangeStart: " + std::to_string(start.get_offset()) + ", rangeEnd: " + std::to_string(end.get_offset()); + } + else + options += ", cursorOffset: " + std::to_string(get_buffer()->get_insert()->get_iter().get_offset()); + prettier_background_process->write("{prettier.clearConfigCache();let _ = prettier.resolveConfig(\"" + escape(file_path.string(), {'"'}) + "\").then(options => {try{let _ = process.stdout.write(JSON.stringify(prettier.formatWithCursor(Buffer.from('"); + prettier_background_process->write(to_hex_string(buffer)); + buffer.clear(); + prettier_background_process->write("', 'hex').toString(), {...options, " + options + "})));}catch(error){let _ = process.stderr.write('ParseError: ' + error.message);}}).catch(error => {let _ = process.stderr.write('ConfigError: ' + error.message);});}\n"); + + while(true) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + int exit_status; + if(prettier_background_process->try_get_exit_status(exit_status)) + break; + LockGuard lock(mutex); + if(result || error) + break; + } + { + LockGuard lock(mutex); + if(result) { + replace_text(result->text); + if(result->cursor_offset >= 0 && result->cursor_offset < get_buffer()->size()) { + get_buffer()->place_cursor(get_buffer()->get_iter_at_offset(result->cursor_offset)); hide_tooltips(); } } - catch(...) { + else if(error) { + if(error->line != -1 && error->index != -1) { + if(is_generic_view) { + auto start = get_iter_at_line_offset(error->line - 1, error->index - 1); + ++num_errors; + while(start.ends_line() && start.backward_char()) { + } + auto end = start; + end.forward_char(); + if(start == end) + start.backward_char(); + + add_diagnostic_tooltip(start, end, true, [error_message = std::move(error->message)](Tooltip &tooltip) { + tooltip.insert_with_links_tagged(error_message); + }); + } + } + else + Terminal::get().print("\e[31mError (prettier)\e[m: " + error->message + '\n', true); } } } - else if(is_generic_view) { - const static std::regex regex(R"(^\[.*error.*\] [^:]*: (.*) \(([0-9]*):([0-9]*)\)$)", std::regex::optimize); - 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(std::stoi(sm[2].str()) - 1, std::stoi(sm[3].str()) - 1); - ++num_errors; - while(start.ends_line() && start.backward_char()) { - } - auto end = start; - end.forward_char(); - if(start == end) - start.backward_char(); + else { + auto command = prettier.string(); + command += " --stdin-filepath " + filesystem::escape_argument(this->file_path.string()); - add_diagnostic_tooltip(start, end, true, [error_message = sm[1].str()](Tooltip &tooltip) { - tooltip.insert_with_links_tagged(error_message); - }); + 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()); + + std::stringstream stdin_stream(buffer), stdout_stream, stderr_stream; + buffer.clear(); + 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 = std::stoi(line); + if(offset < get_buffer()->size()) { + get_buffer()->place_cursor(get_buffer()->get_iter_at_offset(offset)); + hide_tooltips(); + } + } + catch(...) { + } } - catch(...) { + } + else { + const static std::regex regex(R"(^\[.*error.*\] [^:]*: (.*) \(([0-9]*):([0-9]*)\)$)", std::regex::optimize); + std::string line; + std::getline(stderr_stream, line); + std::smatch sm; + if(std::regex_match(line, sm, regex)) { + if(is_generic_view) { + try { + auto start = get_iter_at_line_offset(std::stoi(sm[2].str()) - 1, std::stoi(sm[3].str()) - 1); + ++num_errors; + while(start.ends_line() && start.backward_char()) { + } + auto end = start; + end.forward_char(); + if(start == end) + start.backward_char(); + + add_diagnostic_tooltip(start, end, true, [error_message = sm[1].str()](Tooltip &tooltip) { + tooltip.insert_with_links_tagged(error_message); + }); + } + catch(...) { + } + } } + else + Terminal::get().print("\e[31mError (prettier)\e[m: " + stderr_stream.str(), true); } } + if(is_generic_view) { status_diagnostics = std::make_tuple(num_warnings, num_errors, num_fix_its); if(update_status_diagnostics) diff --git a/src/source.hpp b/src/source.hpp index 1369486..93edaf2 100644 --- a/src/source.hpp +++ b/src/source.hpp @@ -1,4 +1,5 @@ #pragma once +#include "process.hpp" #include "source_diff.hpp" #include "source_spellcheck.hpp" #include "tooltips.hpp" @@ -115,6 +116,8 @@ namespace Source { virtual void soft_reparse(bool delayed = false) { soft_reparse_needed = false; } virtual void full_reparse() { full_reparse_needed = false; } + static std::unique_ptr prettier_background_process; + protected: std::atomic parsed = {true}; Tooltips diagnostic_tooltips; diff --git a/src/utility.cpp b/src/utility.cpp index cc5938e..94dbf5d 100644 --- a/src/utility.cpp +++ b/src/utility.cpp @@ -1,4 +1,5 @@ #include "utility.hpp" +#include #include ScopeGuard::~ScopeGuard() { @@ -169,3 +170,25 @@ bool ends_with(const std::string &str, const char *test) noexcept { return false; return str.compare(str.size() - test_size, test_size, test) == 0; } + +std::string escape(const std::string &input, const std::set &escape_chars) { + std::string result; + result.reserve(input.size()); + for(auto &chr : input) { + if(escape_chars.find(chr) != escape_chars.end()) + result += '\\'; + result += chr; + } + return result; +} + +std::string to_hex_string(const std::string &input) { + std::string result; + result.reserve(input.size() * 2); + std::string hex_chars = "0123456789abcdef"; + for(auto &chr : input) { + result += hex_chars[static_cast(chr) >> 4]; + result += hex_chars[static_cast(chr) & 0x0f]; + } + return result; +} diff --git a/src/utility.hpp b/src/utility.hpp index 6faa5d3..7d2281f 100644 --- a/src/utility.hpp +++ b/src/utility.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include #include class ScopeGuard { @@ -27,3 +28,7 @@ bool starts_with(const std::string &str, size_t pos, const char *test) noexcept; bool ends_with(const std::string &str, const std::string &test) noexcept; bool ends_with(const std::string &str, const char *test) noexcept; + +std::string escape(const std::string &input, const std::set &escape_chars); + +std::string to_hex_string(const std::string &input); diff --git a/src/window.cpp b/src/window.cpp index 3910e25..0e463d7 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -1999,6 +1999,12 @@ bool Window::on_delete_event(GdkEventAny *event) { } Terminal::get().kill_async_processes(); + if(Source::View::prettier_background_process) { + int exit_status; + if(!Source::View::prettier_background_process->try_get_exit_status(exit_status)) + Source::View::prettier_background_process->kill(); + } + #ifdef JUCI_ENABLE_DEBUG Debug::LLDB::destroy(); #endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 08fcbf8..e081405 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -6,6 +6,7 @@ endif() add_definitions(-DJUCI_BUILD_PATH="${CMAKE_BINARY_DIR}" -DJUCI_TESTS_PATH="${CMAKE_CURRENT_SOURCE_DIR}") include_directories(${CMAKE_SOURCE_DIR}/src) +include_directories(${CMAKE_SOURCE_DIR}/lib/tiny-process-library) add_library(test_stubs OBJECT stubs/config.cpp From 257668730c6a72130053b845fdde53f76586f169 Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 7 Jul 2021 13:14:44 +0200 Subject: [PATCH 161/375] Improvement to prettier library output parsing --- src/source.cpp | 48 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/src/source.cpp b/src/source.cpp index 44f8584..1149350 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -819,8 +819,7 @@ void Source::View::setup_format_style(bool is_generic_view) { }; static auto prettier_library = get_prettier_library(); - auto buffer = get_buffer()->get_text().raw(); - if(!prettier_library.empty() && buffer.size() < 25000) { // Node.js repl seems to be limited to around 28786 bytes + if(!prettier_library.empty()) { struct Error { std::string message; int line = -1, index = -1; @@ -847,15 +846,38 @@ void Source::View::setup_format_style(bool is_generic_view) { "node -e \"const repl = require('repl');repl.start({prompt: '', ignoreUndefined: true, preview: false});\"", "", [](const char *bytes, size_t n) { - try { - JSON json(std::string(bytes, n)); - LockGuard lock(mutex); - result = Result{json.string("formatted"), static_cast(json.integer_or("cursorOffset", -1))}; + static std::stringstream stdout_buffer; + static int curly_count = 0; + static bool key_or_value = false; + for(size_t i = 0; i < n; ++i) { + if(!key_or_value) { + if(bytes[i] == '{') + ++curly_count; + else if(bytes[i] == '}') + --curly_count; + else if(bytes[i] == '"') + key_or_value = true; + } + else { + if(bytes[i] == '\\') + ++i; + else if(bytes[i] == '"') + key_or_value = false; + } } - catch(const std::exception &e) { - LockGuard lock(mutex); - error = Error{e.what()}; - error->message += "\nOutput from prettier: " + std::string(bytes, n); + stdout_buffer.write(bytes, n); + if(curly_count == 0) { + key_or_value = false; + try { + JSON json(stdout_buffer); + LockGuard lock(mutex); + result = Result{json.string("formatted"), static_cast(json.integer_or("cursorOffset", -1))}; + } + catch(const std::exception &e) { + LockGuard lock(mutex); + error = Error{std::string(e.what()) + "\nOutput from prettier: " + stdout_buffer.str()}; + } + stdout_buffer.clear(); } }, [](const char *bytes, size_t n) { @@ -908,8 +930,7 @@ void Source::View::setup_format_style(bool is_generic_view) { else options += ", cursorOffset: " + std::to_string(get_buffer()->get_insert()->get_iter().get_offset()); prettier_background_process->write("{prettier.clearConfigCache();let _ = prettier.resolveConfig(\"" + escape(file_path.string(), {'"'}) + "\").then(options => {try{let _ = process.stdout.write(JSON.stringify(prettier.formatWithCursor(Buffer.from('"); - prettier_background_process->write(to_hex_string(buffer)); - buffer.clear(); + prettier_background_process->write(to_hex_string(get_buffer()->get_text().raw())); prettier_background_process->write("', 'hex').toString(), {...options, " + options + "})));}catch(error){let _ = process.stderr.write('ParseError: ' + error.message);}}).catch(error => {let _ = process.stderr.write('ConfigError: ' + error.message);});}\n"); while(true) { @@ -965,8 +986,7 @@ 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()); - std::stringstream stdin_stream(buffer), stdout_stream, stderr_stream; - buffer.clear(); + std::stringstream stdin_stream(get_buffer()->get_text().raw()), 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()); From 5adfeaa1c1616b93e1b9b961fff6d60e17d5559d Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 7 Jul 2021 13:31:17 +0200 Subject: [PATCH 162/375] Use stringstream reconstruction instead of using clear() --- src/source.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/source.cpp b/src/source.cpp index 1149350..1f5b0ba 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -867,7 +867,6 @@ void Source::View::setup_format_style(bool is_generic_view) { } stdout_buffer.write(bytes, n); if(curly_count == 0) { - key_or_value = false; try { JSON json(stdout_buffer); LockGuard lock(mutex); @@ -877,7 +876,8 @@ void Source::View::setup_format_style(bool is_generic_view) { LockGuard lock(mutex); error = Error{std::string(e.what()) + "\nOutput from prettier: " + stdout_buffer.str()}; } - stdout_buffer.clear(); + stdout_buffer = std::stringstream(); + key_or_value = false; } }, [](const char *bytes, size_t n) { From aa44fc0a50de4317138f0ad9512b97620d0110cc Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 7 Jul 2021 16:43:37 +0200 Subject: [PATCH 163/375] Reset stdout_buffer and related variables if prettier background process has to be recreated due to failure --- src/source.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/source.cpp b/src/source.cpp index 1f5b0ba..c8706bf 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -836,19 +836,22 @@ void Source::View::setup_format_style(bool is_generic_view) { result = {}; error = {}; } + static std::stringstream stdout_buffer; + static int curly_count = 0; + static bool key_or_value = false; if(prettier_background_process) { int exit_status; if(prettier_background_process->try_get_exit_status(exit_status)) prettier_background_process = {}; } if(!prettier_background_process) { + stdout_buffer = std::stringstream(); + curly_count = 0; + key_or_value = false; prettier_background_process = std::make_unique( "node -e \"const repl = require('repl');repl.start({prompt: '', ignoreUndefined: true, preview: false});\"", "", [](const char *bytes, size_t n) { - static std::stringstream stdout_buffer; - static int curly_count = 0; - static bool key_or_value = false; for(size_t i = 0; i < n; ++i) { if(!key_or_value) { if(bytes[i] == '{') From 6a5b74b87b31a4eab6663546468822c94209149f Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 7 Jul 2021 17:50:03 +0200 Subject: [PATCH 164/375] Added error message if background prettier process exited prematurely --- src/source.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/source.cpp b/src/source.cpp index c8706bf..7a64464 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -936,9 +936,9 @@ void Source::View::setup_format_style(bool is_generic_view) { prettier_background_process->write(to_hex_string(get_buffer()->get_text().raw())); prettier_background_process->write("', 'hex').toString(), {...options, " + options + "})));}catch(error){let _ = process.stderr.write('ParseError: ' + error.message);}}).catch(error => {let _ = process.stderr.write('ConfigError: ' + error.message);});}\n"); + int exit_status; while(true) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); - int exit_status; if(prettier_background_process->try_get_exit_status(exit_status)) break; LockGuard lock(mutex); @@ -974,6 +974,8 @@ void Source::View::setup_format_style(bool is_generic_view) { else Terminal::get().print("\e[31mError (prettier)\e[m: " + error->message + '\n', true); } + else if(exit_status > 0) + Terminal::get().print("\e[31mError (prettier)\e[m: process exited with exit status " + std::to_string(exit_status) + '\n', true); } } else { From 9fe68e48919c0a75ecdf5e2a180328e27b7c0e98 Mon Sep 17 00:00:00 2001 From: eidheim Date: Wed, 7 Jul 2021 17:55:06 +0200 Subject: [PATCH 165/375] Improvement of error message when background prettier process exits prematurely --- src/source.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/source.cpp b/src/source.cpp index 7a64464..8608628 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -936,7 +936,7 @@ void Source::View::setup_format_style(bool is_generic_view) { prettier_background_process->write(to_hex_string(get_buffer()->get_text().raw())); prettier_background_process->write("', 'hex').toString(), {...options, " + options + "})));}catch(error){let _ = process.stderr.write('ParseError: ' + error.message);}}).catch(error => {let _ = process.stderr.write('ConfigError: ' + error.message);});}\n"); - int exit_status; + int exit_status = -1; while(true) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); if(prettier_background_process->try_get_exit_status(exit_status)) @@ -974,7 +974,7 @@ void Source::View::setup_format_style(bool is_generic_view) { else Terminal::get().print("\e[31mError (prettier)\e[m: " + error->message + '\n', true); } - else if(exit_status > 0) + else if(exit_status >= 0) Terminal::get().print("\e[31mError (prettier)\e[m: process exited with exit status " + std::to_string(exit_status) + '\n', true); } } From 7e56744972ae670fa857945e8b3acedd483fa92e Mon Sep 17 00:00:00 2001 From: eidheim Date: Thu, 8 Jul 2021 09:13:08 +0200 Subject: [PATCH 166/375] Language protocol: client message logs are now formatted --- src/source_language_protocol.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 799218b..0555548 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -460,7 +460,7 @@ void LanguageProtocol::Client::write_request(Source::LanguageProtocolView *view, } std::string content("{\"jsonrpc\":\"2.0\",\"id\":" + std::to_string(message_id++) + ",\"method\":\"" + method + "\"" + (params.empty() ? "" : ",\"params\":{" + params + '}') + '}'); if(Config::get().log.language_server) - std::cout << "Language client: " << content << std::endl; + std::cout << "Language client: " << std::setw(2) << JSON(content) << std::endl; if(!process->write("Content-Length: " + std::to_string(content.size()) + "\r\n\r\n" + content)) { Terminal::get().async_print("\e[31mError\e[m: could not write to language server. Please close and reopen all project files.\n", true); auto id_it = handlers.find(message_id - 1); @@ -478,7 +478,7 @@ void LanguageProtocol::Client::write_response(size_t id, const std::string &resu LockGuard lock(read_write_mutex); std::string content("{\"jsonrpc\":\"2.0\",\"id\":" + std::to_string(id) + ",\"result\":{" + result + "}}"); if(Config::get().log.language_server) - std::cout << "Language client: " << content << std::endl; + std::cout << "Language client: " << std::setw(2) << JSON(content) << std::endl; process->write("Content-Length: " + std::to_string(content.size()) + "\r\n\r\n" + content); } @@ -486,7 +486,7 @@ void LanguageProtocol::Client::write_notification(const std::string &method, con LockGuard lock(read_write_mutex); std::string content("{\"jsonrpc\":\"2.0\",\"method\":\"" + method + "\",\"params\":{" + params + "}}"); if(Config::get().log.language_server) - std::cout << "Language client: " << content << std::endl; + std::cout << "Language client: " << std::setw(2) << JSON(content) << std::endl; process->write("Content-Length: " + std::to_string(content.size()) + "\r\n\r\n" + content); } From 37341bf12e071b58ab6096b6c8a6f74b3e5163e7 Mon Sep 17 00:00:00 2001 From: eidheim Date: Thu, 8 Jul 2021 10:51:52 +0200 Subject: [PATCH 167/375] Added automatic installation of rust-analyzer through rustup if component is found --- src/notebook.cpp | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/notebook.cpp b/src/notebook.cpp index 75403f1..f5bd89c 100644 --- a/src/notebook.cpp +++ b/src/notebook.cpp @@ -6,6 +6,7 @@ #include "source_clang.hpp" #include "source_generic.hpp" #include "source_language_protocol.hpp" +#include "utility.hpp" #include #include #include @@ -183,6 +184,41 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position boost::system::error_code ec; if(boost::filesystem::exists(rust_analyzer, ec)) source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, rust_analyzer.string())); + else { + static bool first = true; + std::stringstream stdin_stream, stdout_stream; + if(first && !filesystem::find_executable("rustup").empty()) { + first = false; + if(Terminal::get().process(stdin_stream, stdout_stream, "rustup component list") == 0) { + std::string line; + while(std::getline(stdout_stream, line)) { + if(starts_with(line, "rust-analyzer")) { + Gtk::MessageDialog dialog(*static_cast(get_toplevel()), "Install rust-analyzer (Rust language server)", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO); + dialog.set_default_response(Gtk::RESPONSE_YES); + dialog.set_secondary_text("Would you like to install rust-analyzer through rustup?"); + int result = dialog.run(); + if(result == Gtk::RESPONSE_YES) { + boost::optional exit_status; + Terminal::get().async_process(std::string("rustup component add rust-src ") + (starts_with(line, "rust-analyzer-preview") ? "rust-analyzer-preview" : "rust-analyzer"), "", [&exit_status](int exit_status_) { + exit_status = exit_status_; + }); + while(!exit_status) { + while(Gtk::Main::events_pending()) + Gtk::Main::iteration(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + if(exit_status == 0) { + Terminal::get().print("\e[32mSuccessfully\e[m installed rust-analyzer.\n"); + if(boost::filesystem::exists(rust_analyzer, ec)) + source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, rust_analyzer.string())); + } + } + break; + } + } + } + } + } } } } From e9b5548b5737fbb50089d8ab813144dfa398a6b1 Mon Sep 17 00:00:00 2001 From: eidheim Date: Thu, 8 Jul 2021 12:49:53 +0200 Subject: [PATCH 168/375] Improved installer for rust-analyzer --- docs/language_servers.md | 11 ++-- src/filesystem.cpp | 18 +++++-- src/filesystem.hpp | 2 + src/notebook.cpp | 86 ++++++++++++++++++++------------ src/source_language_protocol.cpp | 2 +- src/window.cpp | 4 +- 6 files changed, 77 insertions(+), 46 deletions(-) diff --git a/docs/language_servers.md b/docs/language_servers.md index 5a547a3..bf702cf 100644 --- a/docs/language_servers.md +++ b/docs/language_servers.md @@ -78,17 +78,12 @@ ln -s `which pyls` /usr/local/bin/python-language-server - Prerequisites: - [Rust](https://www.rust-lang.org/tools/install) -Install language server, and create symbolic link to enable server in juCi++: +Install language server: ```sh rustup component add rust-src - -git clone https://github.com/rust-analyzer/rust-analyzer -cd rust-analyzer -cargo xtask install --server - -# Usually as root: -ln -s ~/.cargo/bin/rust-analyzer /usr/local/bin/rust-language-server +rustup toolchain install nightly +rustup component add --toolchain nightly rust-src rust-analyzer-preview ``` - Additional setup within a Rust project: diff --git a/src/filesystem.cpp b/src/filesystem.cpp index a1a75aa..f28fa85 100644 --- a/src/filesystem.cpp +++ b/src/filesystem.cpp @@ -129,9 +129,12 @@ boost::filesystem::path filesystem::get_home_path() noexcept { boost::filesystem::path filesystem::get_rust_sysroot_path() noexcept { auto rust_sysroot_path = [] { std::string path; - TinyProcessLib::Process process("rustc --print sysroot", "", [&path](const char *buffer, size_t length) { - path += std::string(buffer, length); - }); + TinyProcessLib::Process process( + "rustc --print sysroot", "", + [&path](const char *buffer, size_t length) { + path += std::string(buffer, length); + }, + [](const char *buffer, size_t n) {}); if(process.get_exit_status() == 0) { while(!path.empty() && (path.back() == '\n' || path.back() == '\r')) path.pop_back(); @@ -144,6 +147,15 @@ boost::filesystem::path filesystem::get_rust_sysroot_path() noexcept { return path; } +boost::filesystem::path filesystem::get_rust_nightly_sysroot_path() noexcept { + auto path = get_rust_sysroot_path(); + if(path.empty()) + return {}; + auto filename = path.filename().string(); + auto pos = filename.find('-'); + return path.parent_path() / (pos != std::string::npos ? "nightly" + filename.substr(pos) : "nightly"); +} + boost::filesystem::path filesystem::get_short_path(const boost::filesystem::path &path) noexcept { #ifdef _WIN32 return path; diff --git a/src/filesystem.hpp b/src/filesystem.hpp index 4cd2135..41955ca 100644 --- a/src/filesystem.hpp +++ b/src/filesystem.hpp @@ -22,6 +22,8 @@ public: static boost::filesystem::path get_home_path() noexcept; /// Returns empty path on failure static boost::filesystem::path get_rust_sysroot_path() noexcept; + /// Returns empty path on failure + static boost::filesystem::path get_rust_nightly_sysroot_path() noexcept; /// Replaces home path with ~ static boost::filesystem::path get_short_path(const boost::filesystem::path &path) noexcept; /// Replaces ~ with home path (boost::filesystem does not recognize ~) diff --git a/src/notebook.cpp b/src/notebook.cpp index f5bd89c..1fdf325 100644 --- a/src/notebook.cpp +++ b/src/notebook.cpp @@ -173,7 +173,7 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position if(language_id == "chdr" || language_id == "cpphdr" || language_id == "c" || language_id == "cpp" || language_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, language_protocol_language_id + "-language-server")); + source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, filesystem::escape_argument(language_protocol_language_id + "-language-server"))); else if(language && language_protocol_language_id == "rust") { if(!filesystem::find_executable("rust-analyzer").empty()) source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, "rust-analyzer")); @@ -183,37 +183,54 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position auto rust_analyzer = sysroot / "bin" / "rust-analyzer"; boost::system::error_code ec; if(boost::filesystem::exists(rust_analyzer, ec)) - source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, rust_analyzer.string())); + source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, filesystem::escape_argument(rust_analyzer.string()))); else { - static bool first = true; - std::stringstream stdin_stream, stdout_stream; - if(first && !filesystem::find_executable("rustup").empty()) { - first = false; - if(Terminal::get().process(stdin_stream, stdout_stream, "rustup component list") == 0) { - std::string line; - while(std::getline(stdout_stream, line)) { - if(starts_with(line, "rust-analyzer")) { - Gtk::MessageDialog dialog(*static_cast(get_toplevel()), "Install rust-analyzer (Rust language server)", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO); - dialog.set_default_response(Gtk::RESPONSE_YES); - dialog.set_secondary_text("Would you like to install rust-analyzer through rustup?"); - int result = dialog.run(); - if(result == Gtk::RESPONSE_YES) { - boost::optional exit_status; - Terminal::get().async_process(std::string("rustup component add rust-src ") + (starts_with(line, "rust-analyzer-preview") ? "rust-analyzer-preview" : "rust-analyzer"), "", [&exit_status](int exit_status_) { - exit_status = exit_status_; - }); - while(!exit_status) { - while(Gtk::Main::events_pending()) - Gtk::Main::iteration(); - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - if(exit_status == 0) { - Terminal::get().print("\e[32mSuccessfully\e[m installed rust-analyzer.\n"); - if(boost::filesystem::exists(rust_analyzer, ec)) - source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, rust_analyzer.string())); + auto nightly_sysroot = filesystem::get_rust_nightly_sysroot_path(); + if(!nightly_sysroot.empty()) { + auto nightly_rust_analyzer = nightly_sysroot / "bin" / "rust-analyzer"; + boost::system::error_code ec; + if(boost::filesystem::exists(nightly_rust_analyzer, ec)) + source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, filesystem::escape_argument(nightly_rust_analyzer.string()))); + else { + auto install_rust_analyzer = [this](const std::string &command) { + Gtk::MessageDialog dialog(*static_cast(get_toplevel()), "Install rust-analyzer (Rust language server)", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO); + dialog.set_default_response(Gtk::RESPONSE_YES); + dialog.set_secondary_text("Would you like to install rust-analyzer through rustup?"); + int result = dialog.run(); + if(result == Gtk::RESPONSE_YES) { + boost::optional exit_status; + Terminal::get().async_process(std::string(command), "", [&exit_status](int exit_status_) { + exit_status = exit_status_; + }); + while(!exit_status) { + while(Gtk::Main::events_pending()) + Gtk::Main::iteration(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + if(exit_status == 0) { + Terminal::get().print("\e[32mSuccessfully\e[m installed rust-analyzer.\n"); + return true; + } + } + return false; + }; + static bool first = true; + if(first && !filesystem::find_executable("rustup").empty()) { + first = false; + std::stringstream stdin_stream, stdout_stream; + if(Terminal::get().process(stdin_stream, stdout_stream, "rustup component list") == 0) { + std::string line; + bool found = false; + while(std::getline(stdout_stream, line)) { + if(starts_with(line, "rust-analyzer")) { + if(install_rust_analyzer(std::string("rustup component add rust-src ") + (starts_with(line, "rust-analyzer-preview") ? "rust-analyzer-preview" : "rust-analyzer"))) + source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, filesystem::escape_argument(rust_analyzer.string()))); + found = true; + break; } } - break; + if(!found && install_rust_analyzer("rustup component add rust-src && rustup toolchain install nightly && rustup component add --toolchain nightly rust-src rust-analyzer-preview")) + source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, "rustup run nightly rust-analyzer")); } } } @@ -238,10 +255,15 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position } else if(language_id == "rust") { auto rust_installed = !filesystem::get_rust_sysroot_path().empty(); - Terminal::get().print(std::string("\e[33mWarning\e[m: could not find Rust ") + (rust_installed ? "language server" : "installation") + ".\n"); - Terminal::get().print("For installation instructions please visit: https://gitlab.com/cppit/jucipp/-/blob/master/docs/language_servers.md#rust.\n"); - if(!rust_installed) + if(!rust_installed) { + Terminal::get().print("\e[33mWarning\e[m: could not find Rust. You can install Rust by running the following command in a terminal:\n\n"); + Terminal::get().print("curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n\n"); Terminal::get().print("You will need to restart juCi++ after installing Rust.\n"); + } + else { + Terminal::get().print("\e[33mWarning\e[m: could not find Rust language server.\n"); + Terminal::get().print("For installation instructions please visit: https://gitlab.com/cppit/jucipp/-/blob/master/docs/language_servers.md#rust.\n"); + } shown.emplace(language_id); } else if(language_id == "go") { diff --git a/src/source_language_protocol.cpp b/src/source_language_protocol.cpp index 0555548..8c980d4 100644 --- a/src/source_language_protocol.cpp +++ b/src/source_language_protocol.cpp @@ -102,7 +102,7 @@ LanguageProtocol::WorkspaceEdit::WorkspaceEdit(const JSON &workspace_edit, boost LanguageProtocol::Client::Client(boost::filesystem::path root_path_, std::string language_id_, const std::string &language_server) : root_path(std::move(root_path_)), language_id(std::move(language_id_)) { process = std::make_unique( - filesystem::escape_argument(language_server), root_path.string(), + language_server, root_path.string(), [this](const char *bytes, size_t n) { server_message_stream.write(bytes, n); parse_server_message(); diff --git a/src/window.cpp b/src/window.cpp index 0e463d7..171e3b0 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -449,8 +449,8 @@ void Window::set_menu_actions() { menu.add_action("file_new_project_rust", []() { auto sysroot = filesystem::get_rust_sysroot_path(); if(sysroot.empty()) { - Terminal::get().print("\e[33mWarning\e[m: could not find Rust installation.\n"); - Terminal::get().print("For installation instructions please visit: https://gitlab.com/cppit/jucipp/-/blob/master/docs/language_servers.md#rust.\n"); + Terminal::get().print("\e[31mError\e[m: could not find Rust. You can install Rust by running the following command in a terminal:\n\n"); + Terminal::get().print("curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n\n"); Terminal::get().print("You will need to restart juCi++ after installing Rust.\n"); return; } From 09d569873f12b2c80f6318efebff7b0be4fbcaeb Mon Sep 17 00:00:00 2001 From: eidheim Date: Thu, 8 Jul 2021 14:36:19 +0200 Subject: [PATCH 169/375] Cleanup of rust-analyzer installation --- src/notebook.cpp | 75 ++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/src/notebook.cpp b/src/notebook.cpp index 1fdf325..47bdcd4 100644 --- a/src/notebook.cpp +++ b/src/notebook.cpp @@ -191,47 +191,46 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position boost::system::error_code ec; if(boost::filesystem::exists(nightly_rust_analyzer, ec)) source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, filesystem::escape_argument(nightly_rust_analyzer.string()))); - else { - auto install_rust_analyzer = [this](const std::string &command) { - Gtk::MessageDialog dialog(*static_cast(get_toplevel()), "Install rust-analyzer (Rust language server)", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO); - dialog.set_default_response(Gtk::RESPONSE_YES); - dialog.set_secondary_text("Would you like to install rust-analyzer through rustup?"); - int result = dialog.run(); - if(result == Gtk::RESPONSE_YES) { - boost::optional exit_status; - Terminal::get().async_process(std::string(command), "", [&exit_status](int exit_status_) { - exit_status = exit_status_; - }); - while(!exit_status) { - while(Gtk::Main::events_pending()) - Gtk::Main::iteration(); - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - if(exit_status == 0) { - Terminal::get().print("\e[32mSuccessfully\e[m installed rust-analyzer.\n"); - return true; - } + } + if(source_views_previous_size == source_views.size()) { + auto install_rust_analyzer = [this](const std::string &command) { + Gtk::MessageDialog dialog(*static_cast(get_toplevel()), "Install rust-analyzer (Rust language server)", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO); + dialog.set_default_response(Gtk::RESPONSE_YES); + dialog.set_secondary_text("Would you like to install rust-analyzer through rustup?"); + int result = dialog.run(); + if(result == Gtk::RESPONSE_YES) { + boost::optional exit_status; + Terminal::get().async_process(std::string(command), "", [&exit_status](int exit_status_) { + exit_status = exit_status_; + }); + while(!exit_status) { + while(Gtk::Main::events_pending()) + Gtk::Main::iteration(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } - return false; - }; - static bool first = true; - if(first && !filesystem::find_executable("rustup").empty()) { - first = false; - std::stringstream stdin_stream, stdout_stream; - if(Terminal::get().process(stdin_stream, stdout_stream, "rustup component list") == 0) { - std::string line; - bool found = false; - while(std::getline(stdout_stream, line)) { - if(starts_with(line, "rust-analyzer")) { - if(install_rust_analyzer(std::string("rustup component add rust-src ") + (starts_with(line, "rust-analyzer-preview") ? "rust-analyzer-preview" : "rust-analyzer"))) - source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, filesystem::escape_argument(rust_analyzer.string()))); - found = true; - break; - } + if(exit_status == 0) { + Terminal::get().print("\e[32mSuccessfully\e[m installed rust-analyzer.\n"); + return true; + } + } + return false; + }; + static bool first = true; + if(first && !filesystem::find_executable("rustup").empty()) { + first = false; + std::stringstream stdin_stream, stdout_stream; + if(Terminal::get().process(stdin_stream, stdout_stream, "rustup component list") == 0) { + std::string line; + while(std::getline(stdout_stream, line)) { + if(starts_with(line, "rust-analyzer")) { + if(install_rust_analyzer(std::string("rustup component add rust-src ") + (starts_with(line, "rust-analyzer-preview") ? "rust-analyzer-preview" : "rust-analyzer"))) + source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, filesystem::escape_argument(rust_analyzer.string()))); + break; } - if(!found && install_rust_analyzer("rustup component add rust-src && rustup toolchain install nightly && rustup component add --toolchain nightly rust-src rust-analyzer-preview")) - source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, "rustup run nightly rust-analyzer")); } + if(source_views_previous_size == source_views.size() && + install_rust_analyzer("rustup component add rust-src && rustup toolchain install nightly && rustup component add --toolchain nightly rust-src rust-analyzer-preview")) + source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, "rustup run nightly rust-analyzer")); } } } From a0a587b8fd916d294c88b5a7d0405c6cedf75662 Mon Sep 17 00:00:00 2001 From: eidheim Date: Thu, 8 Jul 2021 15:25:02 +0200 Subject: [PATCH 170/375] Added cancellation dialog to rust-analyzer installation --- src/notebook.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/notebook.cpp b/src/notebook.cpp index 47bdcd4..70d4e50 100644 --- a/src/notebook.cpp +++ b/src/notebook.cpp @@ -1,5 +1,6 @@ #include "notebook.hpp" #include "config.hpp" +#include "dialog.hpp" #include "filesystem.hpp" #include "project.hpp" #include "selection_dialog.hpp" @@ -198,12 +199,22 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position dialog.set_default_response(Gtk::RESPONSE_YES); dialog.set_secondary_text("Would you like to install rust-analyzer through rustup?"); int result = dialog.run(); + dialog.hide(); if(result == Gtk::RESPONSE_YES) { + bool canceled = false; + Dialog::Message message("Installing rust-analyzer", [&canceled] { + canceled = true; + }); boost::optional exit_status; - Terminal::get().async_process(std::string(command), "", [&exit_status](int exit_status_) { + auto process = Terminal::get().async_process(std::string(command), "", [&exit_status](int exit_status_) { exit_status = exit_status_; }); + bool killed = false; while(!exit_status) { + if(canceled && !killed) { + process->kill(); + killed = true; + } while(Gtk::Main::events_pending()) Gtk::Main::iteration(); std::this_thread::sleep_for(std::chrono::milliseconds(10)); @@ -220,16 +231,17 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position first = false; std::stringstream stdin_stream, stdout_stream; if(Terminal::get().process(stdin_stream, stdout_stream, "rustup component list") == 0) { + bool rust_analyzer_in_toolchain = false; std::string line; while(std::getline(stdout_stream, line)) { if(starts_with(line, "rust-analyzer")) { if(install_rust_analyzer(std::string("rustup component add rust-src ") + (starts_with(line, "rust-analyzer-preview") ? "rust-analyzer-preview" : "rust-analyzer"))) source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, filesystem::escape_argument(rust_analyzer.string()))); + rust_analyzer_in_toolchain = true; break; } } - if(source_views_previous_size == source_views.size() && - install_rust_analyzer("rustup component add rust-src && rustup toolchain install nightly && rustup component add --toolchain nightly rust-src rust-analyzer-preview")) + if(!rust_analyzer_in_toolchain && install_rust_analyzer("rustup component add rust-src && rustup toolchain install nightly && rustup component add --toolchain nightly rust-src rust-analyzer-preview")) source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, "rustup run nightly rust-analyzer")); } } From 1dd8cb3e36c914dc22d2f059cfe4224fceab4450 Mon Sep 17 00:00:00 2001 From: eidheim Date: Thu, 8 Jul 2021 21:03:39 +0200 Subject: [PATCH 171/375] Added Rust installer --- docs/language_servers.md | 4 +- src/debug_lldb.cpp | 17 +--- src/filesystem.cpp | 63 ++++++++---- src/filesystem.hpp | 13 ++- src/notebook.cpp | 209 ++++++++++++++++++++++++++------------- src/notebook.hpp | 1 + src/window.cpp | 23 ++--- 7 files changed, 211 insertions(+), 119 deletions(-) diff --git a/docs/language_servers.md b/docs/language_servers.md index bf702cf..8d4b883 100644 --- a/docs/language_servers.md +++ b/docs/language_servers.md @@ -81,9 +81,7 @@ ln -s `which pyls` /usr/local/bin/python-language-server Install language server: ```sh -rustup component add rust-src -rustup toolchain install nightly -rustup component add --toolchain nightly rust-src rust-analyzer-preview +rustup toolchain install nightly --component rust-analyzer-preview ``` - Additional setup within a Rust project: diff --git a/src/debug_lldb.cpp b/src/debug_lldb.cpp index 327f6c3..fe5023d 100644 --- a/src/debug_lldb.cpp +++ b/src/debug_lldb.cpp @@ -1,8 +1,4 @@ #include "debug_lldb.hpp" -#include -#ifdef __APPLE__ -#include -#endif #include "config.hpp" #include "filesystem.hpp" #include "process.hpp" @@ -20,18 +16,11 @@ void log(const char *msg, void *) { } Debug::LLDB::LLDB() : state(lldb::StateType::eStateInvalid), buffer_size(131072) { - if(!getenv("LLDB_DEBUGSERVER_PATH")) { #ifndef __APPLE__ - auto debug_server_path = filesystem::get_executable("lldb-server").string(); - if(debug_server_path != "lldb-server") { -#ifdef _WIN32 - Glib::setenv("LLDB_DEBUGSERVER_PATH", debug_server_path.c_str(), 0); -#else - 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") + Glib::setenv("LLDB_DEBUGSERVER_PATH", debug_server_path, false); #endif - } -#endif - } } void Debug::LLDB::destroy_() { diff --git a/src/filesystem.cpp b/src/filesystem.cpp index f28fa85..8255643 100644 --- a/src/filesystem.cpp +++ b/src/filesystem.cpp @@ -6,6 +6,10 @@ #include #include +boost::optional filesystem::rust_sysroot_path; +boost::optional filesystem::rust_nightly_sysroot_path; +boost::optional> filesystem::executable_search_paths; + //Only use on small files std::string filesystem::read(const std::string &path) { std::string str; @@ -82,8 +86,8 @@ std::string filesystem::unescape_argument(const std::string &argument) { return unescaped; } -boost::filesystem::path filesystem::get_current_path() noexcept { - auto current_path = [] { +const boost::filesystem::path &filesystem::get_current_path() noexcept { + auto get_path = [] { #ifdef _WIN32 boost::system::error_code ec; auto path = boost::filesystem::current_path(ec); @@ -104,12 +108,12 @@ boost::filesystem::path filesystem::get_current_path() noexcept { #endif }; - static boost::filesystem::path path = current_path(); + static boost::filesystem::path path = get_path(); return path; } -boost::filesystem::path filesystem::get_home_path() noexcept { - auto home_path = [] { +const boost::filesystem::path &filesystem::get_home_path() noexcept { + auto get_path = [] { std::vector environment_variables = {"HOME", "AppData"}; for(auto &variable : environment_variables) { if(auto ptr = std::getenv(variable.c_str())) { @@ -122,12 +126,12 @@ boost::filesystem::path filesystem::get_home_path() noexcept { return boost::filesystem::path(); }; - static boost::filesystem::path path = home_path(); + static boost::filesystem::path path = get_path(); return path; } -boost::filesystem::path filesystem::get_rust_sysroot_path() noexcept { - auto rust_sysroot_path = [] { +const boost::filesystem::path &filesystem::get_rust_sysroot_path() noexcept { + auto get_path = [] { std::string path; TinyProcessLib::Process process( "rustc --print sysroot", "", @@ -143,17 +147,32 @@ boost::filesystem::path filesystem::get_rust_sysroot_path() noexcept { return boost::filesystem::path(); }; - static boost::filesystem::path path = rust_sysroot_path(); - return path; + if(!rust_sysroot_path) + rust_sysroot_path = get_path(); + return *rust_sysroot_path; } boost::filesystem::path filesystem::get_rust_nightly_sysroot_path() noexcept { - auto path = get_rust_sysroot_path(); - if(path.empty()) - return {}; - auto filename = path.filename().string(); - auto pos = filename.find('-'); - return path.parent_path() / (pos != std::string::npos ? "nightly" + filename.substr(pos) : "nightly"); + auto get_path = [] { + std::string path; + TinyProcessLib::Process process( + // Slightly complicated since "RUSTUP_TOOLCHAIN=nightly rustc --print sysroot" actually installs nightly toolchain if missing... + "rustup toolchain list|grep nightly > /dev/null && RUSTUP_TOOLCHAIN=nightly rustc --print sysroot", "", + [&path](const char *buffer, size_t length) { + path += std::string(buffer, length); + }, + [](const char *buffer, size_t n) {}); + if(process.get_exit_status() == 0) { + while(!path.empty() && (path.back() == '\n' || path.back() == '\r')) + path.pop_back(); + return boost::filesystem::path(path); + } + return boost::filesystem::path(); + }; + + if(!rust_nightly_sysroot_path) + rust_nightly_sysroot_path = get_path(); + return *rust_nightly_sysroot_path; } boost::filesystem::path filesystem::get_short_path(const boost::filesystem::path &path) noexcept { @@ -299,10 +318,13 @@ boost::filesystem::path filesystem::get_executable(const boost::filesystem::path // Based on https://stackoverflow.com/a/11295568 const std::vector &filesystem::get_executable_search_paths() { - auto executable_search_paths = [] { + auto get_paths = [] { std::vector paths; - const std::string env = getenv("PATH"); + auto c_env = std::getenv("PATH"); + if(!c_env) + return paths; + const std::string env = c_env; const char delimiter = ':'; size_t previous = 0; @@ -316,8 +338,9 @@ const std::vector &filesystem::get_executable_search_pa return paths; }; - static std::vector paths = executable_search_paths(); - return paths; + if(!executable_search_paths) + executable_search_paths = get_paths(); + return *executable_search_paths; } boost::filesystem::path filesystem::find_executable(const std::string &executable_name) { diff --git a/src/filesystem.hpp b/src/filesystem.hpp index 41955ca..dc39a2f 100644 --- a/src/filesystem.hpp +++ b/src/filesystem.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include @@ -17,13 +18,17 @@ public: static std::string unescape_argument(const std::string &argument); /// Does not resolve symbolic links. Returns empty path on failure. - static boost::filesystem::path get_current_path() noexcept; + static const boost::filesystem::path &get_current_path() noexcept; /// Returns empty path on failure - static boost::filesystem::path get_home_path() noexcept; + static const boost::filesystem::path &get_home_path() noexcept; /// Returns empty path on failure - static boost::filesystem::path get_rust_sysroot_path() noexcept; + static const boost::filesystem::path &get_rust_sysroot_path() noexcept; + /// Set to {} to reset get_rust_sysroot_path + static boost::optional rust_sysroot_path; /// Returns empty path on failure static boost::filesystem::path get_rust_nightly_sysroot_path() noexcept; + /// Set to {} to reset get_rust_sysroot_path + static boost::optional rust_nightly_sysroot_path; /// Replaces home path with ~ static boost::filesystem::path get_short_path(const boost::filesystem::path &path) noexcept; /// Replaces ~ with home path (boost::filesystem does not recognize ~) @@ -43,6 +48,8 @@ public: static boost::filesystem::path get_executable(const boost::filesystem::path &executable_name) noexcept; static const std::vector &get_executable_search_paths(); + /// Set to {} to reset get_executable_search_paths + static boost::optional> executable_search_paths; /// Returns full executable path if found, or empty path otherwise. static boost::filesystem::path find_executable(const std::string &executable_name); diff --git a/src/notebook.cpp b/src/notebook.cpp index 70d4e50..51cdeb6 100644 --- a/src/notebook.cpp +++ b/src/notebook.cpp @@ -68,6 +68,21 @@ Notebook::Notebook() : Gtk::Paned(), notebooks(2) { }); } pack1(notebooks[0], true, true); + + if(filesystem::find_executable("rustup").empty()) { + // PATH might not be set (for instance after installation) + auto cargo_bin = filesystem::get_home_path() / ".cargo" / "bin"; + boost::system::error_code ec; + if(boost::filesystem::is_directory(cargo_bin, ec)) { + std::string env; + if(auto c_env = std::getenv("PATH")) + env = c_env; + Glib::setenv("PATH", !env.empty() ? env + ':' + cargo_bin.string() : cargo_bin.string()); + filesystem::rust_sysroot_path = {}; + filesystem::rust_nightly_sysroot_path = {}; + filesystem::executable_search_paths = {}; + } + } } size_t Notebook::size() { @@ -179,70 +194,83 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position if(!filesystem::find_executable("rust-analyzer").empty()) source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, "rust-analyzer")); else { - auto sysroot = filesystem::get_rust_sysroot_path(); - if(!sysroot.empty()) { - auto rust_analyzer = sysroot / "bin" / "rust-analyzer"; - boost::system::error_code ec; - if(boost::filesystem::exists(rust_analyzer, ec)) - source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, filesystem::escape_argument(rust_analyzer.string()))); - else { - auto nightly_sysroot = filesystem::get_rust_nightly_sysroot_path(); - if(!nightly_sysroot.empty()) { - auto nightly_rust_analyzer = nightly_sysroot / "bin" / "rust-analyzer"; - boost::system::error_code ec; - if(boost::filesystem::exists(nightly_rust_analyzer, ec)) - source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, filesystem::escape_argument(nightly_rust_analyzer.string()))); + if(filesystem::find_executable("rustup").empty()) + install_rust(); + if(!filesystem::find_executable("rustup").empty()) { + // Try find rust-analyzer installed with rustup + auto sysroot = filesystem::get_rust_sysroot_path(); + if(!sysroot.empty()) { + auto rust_analyzer = sysroot / "bin" / "rust-analyzer"; + boost::system::error_code ec; + if(boost::filesystem::exists(rust_analyzer, ec)) + source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, filesystem::escape_argument(rust_analyzer.string()))); + else { + // Workaround while rust-analyzer is in nightly toolchain only + auto nightly_sysroot = filesystem::get_rust_nightly_sysroot_path(); + if(!nightly_sysroot.empty()) { + auto nightly_rust_analyzer = nightly_sysroot / "bin" / "rust-analyzer"; + boost::system::error_code ec; + if(boost::filesystem::exists(nightly_rust_analyzer, ec)) + source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, filesystem::escape_argument(nightly_rust_analyzer.string()))); + } } - if(source_views_previous_size == source_views.size()) { - auto install_rust_analyzer = [this](const std::string &command) { - Gtk::MessageDialog dialog(*static_cast(get_toplevel()), "Install rust-analyzer (Rust language server)", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO); - dialog.set_default_response(Gtk::RESPONSE_YES); - dialog.set_secondary_text("Would you like to install rust-analyzer through rustup?"); - int result = dialog.run(); - dialog.hide(); - if(result == Gtk::RESPONSE_YES) { - bool canceled = false; - Dialog::Message message("Installing rust-analyzer", [&canceled] { - canceled = true; - }); - boost::optional exit_status; - auto process = Terminal::get().async_process(std::string(command), "", [&exit_status](int exit_status_) { - exit_status = exit_status_; - }); - bool killed = false; - while(!exit_status) { - if(canceled && !killed) { - process->kill(); - killed = true; - } - while(Gtk::Main::events_pending()) - Gtk::Main::iteration(); - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - if(exit_status == 0) { - Terminal::get().print("\e[32mSuccessfully\e[m installed rust-analyzer.\n"); - return true; + } + if(source_views_previous_size == source_views.size()) { + // Install rust-analyzer + auto install_rust_analyzer = [this](const std::string &command) { + Gtk::MessageDialog dialog(*static_cast(get_toplevel()), "Install Rust language server", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO); + dialog.set_default_response(Gtk::RESPONSE_YES); + dialog.set_secondary_text("Rust language server not found. Would you like to install rust-analyzer?"); + int result = dialog.run(); + dialog.hide(); + if(result == Gtk::RESPONSE_YES) { + bool canceled = false; + Dialog::Message message("Installing rust-analyzer", [&canceled] { + canceled = true; + }); + boost::optional exit_status; + + Terminal::get().print("\e[2mRunning: " + command + "\e[m\n"); + auto process = Terminal::get().async_process(command, "", [&exit_status](int exit_status_) { + exit_status = exit_status_; + }); + bool killed = false; + while(!exit_status) { + if(canceled && !killed) { + process->kill(); + killed = true; } + while(Gtk::Main::events_pending()) + Gtk::Main::iteration(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + if(exit_status == 0) { + Terminal::get().print("\e[32mSuccess\e[m: installed rust-analyzer\n"); + return true; } - return false; - }; - static bool first = true; - if(first && !filesystem::find_executable("rustup").empty()) { - first = false; - std::stringstream stdin_stream, stdout_stream; - if(Terminal::get().process(stdin_stream, stdout_stream, "rustup component list") == 0) { - bool rust_analyzer_in_toolchain = false; - std::string line; - while(std::getline(stdout_stream, line)) { - if(starts_with(line, "rust-analyzer")) { - if(install_rust_analyzer(std::string("rustup component add rust-src ") + (starts_with(line, "rust-analyzer-preview") ? "rust-analyzer-preview" : "rust-analyzer"))) - source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, filesystem::escape_argument(rust_analyzer.string()))); - rust_analyzer_in_toolchain = true; - break; - } + } + return false; + }; + static bool first = true; + if(first) { + first = false; + std::stringstream stdin_stream, stdout_stream; + // Workaround while rust-analyzer is called rust-analyzer-preview instead of rust-analyzer + if(Terminal::get().process(stdin_stream, stdout_stream, "rustup component list") == 0) { + bool rust_analyzer_in_toolchain = false; + std::string line; + while(std::getline(stdout_stream, line)) { + if(starts_with(line, "rust-analyzer")) { + if(install_rust_analyzer(std::string("rustup component add ") + (starts_with(line, "rust-analyzer-preview") ? "rust-analyzer-preview" : "rust-analyzer"))) + source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, filesystem::escape_argument((filesystem::get_rust_sysroot_path() / "bin" / "rust-analyzer").string()))); + rust_analyzer_in_toolchain = true; + break; } - if(!rust_analyzer_in_toolchain && install_rust_analyzer("rustup component add rust-src && rustup toolchain install nightly && rustup component add --toolchain nightly rust-src rust-analyzer-preview")) - source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, "rustup run nightly rust-analyzer")); + } + // Workaround while rust-analyzer is in nightly toolchain only + if(!rust_analyzer_in_toolchain && install_rust_analyzer("rustup toolchain install nightly --component rust-analyzer-preview")) { + filesystem::rust_nightly_sysroot_path = {}; + source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, filesystem::escape_argument((filesystem::get_rust_nightly_sysroot_path() / "bin" / "rust-analyzer").string()))); } } } @@ -266,15 +294,12 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position } else if(language_id == "rust") { auto rust_installed = !filesystem::get_rust_sysroot_path().empty(); - if(!rust_installed) { - Terminal::get().print("\e[33mWarning\e[m: could not find Rust. You can install Rust by running the following command in a terminal:\n\n"); - Terminal::get().print("curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n\n"); + Terminal::get().print(std::string("\e[33mWarning\e[m: could not find Rust") + (rust_installed ? " language server" : "") + ".\n"); + Terminal::get().print("For installation instructions please visit: https://gitlab.com/cppit/jucipp/-/blob/master/docs/language_servers.md#rust.\n"); + if(!rust_installed) Terminal::get().print("You will need to restart juCi++ after installing Rust.\n"); - } - else { - Terminal::get().print("\e[33mWarning\e[m: could not find Rust language server.\n"); - Terminal::get().print("For installation instructions please visit: https://gitlab.com/cppit/jucipp/-/blob/master/docs/language_servers.md#rust.\n"); - } + shown.emplace(language_id); + shown.emplace(language_id); } else if(language_id == "go") { @@ -579,6 +604,54 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position return true; } +void Notebook::install_rust() { + static bool first = true; + if(first) { + first = false; + Gtk::MessageDialog dialog(*static_cast(get_toplevel()), "Install Rust", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO); + dialog.set_default_response(Gtk::RESPONSE_YES); + dialog.set_secondary_text("Rust not found. Would you like to install Rust?"); + int result = dialog.run(); + dialog.hide(); + if(result == Gtk::RESPONSE_YES) { + bool canceled = false; + Dialog::Message message("Installing Rust", [&canceled] { + canceled = true; + }); + boost::optional exit_status; + + std::string command = "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y"; + Terminal::get().print("\e[2mRunning: " + command + "\e[m\n"); + auto process = Terminal::get().async_process(command, "", [&exit_status](int exit_status_) { + exit_status = exit_status_; + }); + bool killed = false; + while(!exit_status) { + if(canceled && !killed) { + process->kill(); + killed = true; + } + while(Gtk::Main::events_pending()) + Gtk::Main::iteration(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + if(exit_status == 0) { + if(filesystem::find_executable("rustup").empty()) { + std::string env; + if(auto c_env = std::getenv("PATH")) + env = c_env; + auto cargo_bin = filesystem::get_home_path() / ".cargo" / "bin"; + Glib::setenv("PATH", !env.empty() ? env + ':' + cargo_bin.string() : cargo_bin.string()); + } + filesystem::rust_sysroot_path = {}; + filesystem::rust_nightly_sysroot_path = {}; + filesystem::executable_search_paths = {}; + Terminal::get().print("\e[32mSuccess\e[m: installed Rust and updated PATH environment variable\n"); + } + } + } +} + void Notebook::open_uri(const std::string &uri) { #ifdef __APPLE__ Terminal::get().process("open " + filesystem::escape_argument(uri)); diff --git a/src/notebook.hpp b/src/notebook.hpp index 6b30171..cda86ae 100644 --- a/src/notebook.hpp +++ b/src/notebook.hpp @@ -45,6 +45,7 @@ public: }; bool open(Source::View *view); bool open(const boost::filesystem::path &file_path, Position position = Position::infer); + void install_rust(); void open_uri(const std::string &uri); void configure(size_t index); bool save(size_t index); diff --git a/src/window.cpp b/src/window.cpp index 171e3b0..c8b0830 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -381,9 +381,9 @@ void Window::set_menu_actions() { Directories::get().open(project_path); Notebook::get().open(c_main_path); Directories::get().update(); - Terminal::get().print("C project "); + Terminal::get().print("\e[32mSuccess\e[m: created C project "); Terminal::get().print(project_name, true); - Terminal::get().print(" \e[32mcreated\e[m\n"); + Terminal::get().print("\n"); } else Terminal::get().print("\e[31mError\e[m: could not create project " + filesystem::get_short_path(project_path).string() + "\n", true); @@ -438,19 +438,20 @@ void Window::set_menu_actions() { Directories::get().open(project_path); Notebook::get().open(cpp_main_path); Directories::get().update(); - Terminal::get().print("C++ project "); + Terminal::get().print("\e[32mSuccess\e[m: created C++ project "); Terminal::get().print(project_name, true); - Terminal::get().print(" \e[32mcreated\e[m\n"); + Terminal::get().print("\n"); } else Terminal::get().print("\e[31mError\e[m: could not create project " + filesystem::get_short_path(project_path).string() + "\n", true); } }); menu.add_action("file_new_project_rust", []() { - auto sysroot = filesystem::get_rust_sysroot_path(); - if(sysroot.empty()) { - Terminal::get().print("\e[31mError\e[m: could not find Rust. You can install Rust by running the following command in a terminal:\n\n"); - Terminal::get().print("curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n\n"); + if(filesystem::find_executable("rustup").empty()) + Notebook::get().install_rust(); + if(filesystem::find_executable("rustup").empty()) { + Terminal::get().print("\e[33mWarning\e[m: could not find Rust.\n"); + Terminal::get().print("For installation instructions please visit: https://gitlab.com/cppit/jucipp/-/blob/master/docs/language_servers.md#rust.\n"); Terminal::get().print("You will need to restart juCi++ after installing Rust.\n"); return; } @@ -467,9 +468,9 @@ void Window::set_menu_actions() { Directories::get().open(project_path); Notebook::get().open(project_path / "src" / "main.rs"); Directories::get().update(); - Terminal::get().print("Rust project "); - Terminal::get().print(project_path.filename().string(), true); - Terminal::get().print(" \e[32mcreated\e[m\n"); + Terminal::get().print("\e[32mSuccess\e[m: created Rust project "); + Terminal::get().print(project_name, true); + Terminal::get().print("\n"); } else Terminal::get().print("\e[31mError\e[m: could not create project " + filesystem::get_short_path(project_path).string() + "\n", true); From d18e26ba803ec6b16dfb093a2800c74ac39d9c14 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 9 Jul 2021 14:15:42 +0200 Subject: [PATCH 172/375] Improved entry in selection dialog on MacOS --- src/selection_dialog.cpp | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/selection_dialog.cpp b/src/selection_dialog.cpp index 8bef1ab..505d91b 100644 --- a/src/selection_dialog.cpp +++ b/src/selection_dialog.cpp @@ -311,30 +311,38 @@ bool SelectionDialog::on_key_press(GdkEventKey *event) { } 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(event->keyval == GDK_KEY_dead_tilde) { - search_entry.insert_text("~", 1, search_entry_length); - return true; - } - else if(event->keyval == GDK_KEY_dead_circumflex) { - search_entry.insert_text("^", 1, search_entry_length); - return true; - } - else if(event->is_modifier) + if(event->is_modifier) return true; else if(event->keyval == GDK_KEY_BackSpace) { + int start_pos, end_pos; + if(search_entry.get_selection_bounds(start_pos, end_pos)) { + search_entry.delete_selection(); + return true; + } auto length = search_entry.get_text_length(); if(length > 0) search_entry.delete_text(length - 1, length); return true; } + else if(event->keyval == GDK_KEY_v && event->state & GDK_META_MASK) { + search_entry.paste_clipboard(); + return true; + } + else if(event->keyval == GDK_KEY_c && event->state & GDK_META_MASK) { + search_entry.copy_clipboard(); + return true; + } + else if(event->keyval == GDK_KEY_x && event->state & GDK_META_MASK) { + search_entry.cut_clipboard(); + return true; + } + else if(event->keyval == GDK_KEY_a && event->state & GDK_META_MASK) { + search_entry.select_region(0, -1); + return true; + } else { - gunichar unicode = gdk_keyval_to_unicode(event->keyval); - if(unicode >= 32 && unicode != 126) { - auto ustr = Glib::ustring(1, unicode); - search_entry.insert_text(ustr, ustr.bytes(), search_entry_length); - return true; - } + search_entry.on_key_press_event(event); + return true; } #else search_entry.on_key_press_event(event); From b2aade877b50bbe11616f0518345514f78c1712f Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 9 Jul 2021 14:57:52 +0200 Subject: [PATCH 173/375] Fixed Rust installation on MSYS2 --- src/filesystem.cpp | 8 ++++++++ src/notebook.cpp | 19 ++++++++++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/filesystem.cpp b/src/filesystem.cpp index 8255643..5dcc6c6 100644 --- a/src/filesystem.cpp +++ b/src/filesystem.cpp @@ -325,7 +325,11 @@ const std::vector &filesystem::get_executable_search_pa if(!c_env) return paths; const std::string env = c_env; +#ifdef _WIN32 + const char delimiter = ';'; +#else const char delimiter = ':'; +#endif size_t previous = 0; size_t pos; @@ -346,7 +350,11 @@ const std::vector &filesystem::get_executable_search_pa boost::filesystem::path filesystem::find_executable(const std::string &executable_name) { for(auto &path : get_executable_search_paths()) { boost::system::error_code ec; +#ifdef _WIN32 + auto executable_path = path / (executable_name + ".exe"); +#else auto executable_path = path / executable_name; +#endif if(boost::filesystem::exists(executable_path, ec)) return executable_path; } diff --git a/src/notebook.cpp b/src/notebook.cpp index 51cdeb6..b4aa127 100644 --- a/src/notebook.cpp +++ b/src/notebook.cpp @@ -77,7 +77,12 @@ Notebook::Notebook() : Gtk::Paned(), notebooks(2) { std::string env; if(auto c_env = std::getenv("PATH")) env = c_env; - Glib::setenv("PATH", !env.empty() ? env + ':' + cargo_bin.string() : cargo_bin.string()); +#ifdef _WIN32 + const char delimiter = ';'; +#else + const char delimiter = ':'; +#endif + Glib::setenv("PATH", !env.empty() ? env + delimiter + cargo_bin.string() : cargo_bin.string()); filesystem::rust_sysroot_path = {}; filesystem::rust_nightly_sysroot_path = {}; filesystem::executable_search_paths = {}; @@ -619,8 +624,11 @@ void Notebook::install_rust() { canceled = true; }); boost::optional exit_status; - +#ifdef _WIN32 + std::string command = "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | RUSTUP_HOME=$HOME/.rustup CARGO_HOME=$HOME/.cargo sh -s -- -y"; +#else std::string command = "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y"; +#endif Terminal::get().print("\e[2mRunning: " + command + "\e[m\n"); auto process = Terminal::get().async_process(command, "", [&exit_status](int exit_status_) { exit_status = exit_status_; @@ -641,7 +649,12 @@ void Notebook::install_rust() { if(auto c_env = std::getenv("PATH")) env = c_env; auto cargo_bin = filesystem::get_home_path() / ".cargo" / "bin"; - Glib::setenv("PATH", !env.empty() ? env + ':' + cargo_bin.string() : cargo_bin.string()); +#ifdef _WIN32 + const char delimiter = ';'; +#else + const char delimiter = ':'; +#endif + Glib::setenv("PATH", !env.empty() ? env + delimiter + cargo_bin.string() : cargo_bin.string()); } filesystem::rust_sysroot_path = {}; filesystem::rust_nightly_sysroot_path = {}; From af652278c3e6e68013c6bb6d3b59390d85d832c9 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 9 Jul 2021 16:05:09 +0200 Subject: [PATCH 174/375] Further improvements to MSYS2 Rust installer --- src/notebook.cpp | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/notebook.cpp b/src/notebook.cpp index b4aa127..d878929 100644 --- a/src/notebook.cpp +++ b/src/notebook.cpp @@ -71,17 +71,20 @@ Notebook::Notebook() : Gtk::Paned(), notebooks(2) { if(filesystem::find_executable("rustup").empty()) { // PATH might not be set (for instance after installation) +#ifdef _WIN32 + boost::filesystem::path cargo_bin; + if(auto userprofile = std::getenv("USERPROFILE")) + cargo_bin = boost::filesystem::path(userprofile) / ".cargo" / "bin"; + const char delimiter = ';'; +#else auto cargo_bin = filesystem::get_home_path() / ".cargo" / "bin"; + const char delimiter = ':'; +#endif boost::system::error_code ec; - if(boost::filesystem::is_directory(cargo_bin, ec)) { + if(!cargo_bin.empty() && boost::filesystem::is_directory(cargo_bin, ec)) { std::string env; if(auto c_env = std::getenv("PATH")) env = c_env; -#ifdef _WIN32 - const char delimiter = ';'; -#else - const char delimiter = ':'; -#endif Glib::setenv("PATH", !env.empty() ? env + delimiter + cargo_bin.string() : cargo_bin.string()); filesystem::rust_sysroot_path = {}; filesystem::rust_nightly_sysroot_path = {}; @@ -205,7 +208,11 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position // Try find rust-analyzer installed with rustup auto sysroot = filesystem::get_rust_sysroot_path(); if(!sysroot.empty()) { +#ifdef _WIN32 + auto rust_analyzer = sysroot / "bin" / "rust-analyzer.exe"; +#else auto rust_analyzer = sysroot / "bin" / "rust-analyzer"; +#endif boost::system::error_code ec; if(boost::filesystem::exists(rust_analyzer, ec)) source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, filesystem::escape_argument(rust_analyzer.string()))); @@ -213,7 +220,11 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position // Workaround while rust-analyzer is in nightly toolchain only auto nightly_sysroot = filesystem::get_rust_nightly_sysroot_path(); if(!nightly_sysroot.empty()) { +#ifdef _WIN32 + auto nightly_rust_analyzer = nightly_sysroot / "bin" / "rust-analyzer.exe"; +#else auto nightly_rust_analyzer = nightly_sysroot / "bin" / "rust-analyzer"; +#endif boost::system::error_code ec; if(boost::filesystem::exists(nightly_rust_analyzer, ec)) source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, filesystem::escape_argument(nightly_rust_analyzer.string()))); @@ -624,11 +635,7 @@ void Notebook::install_rust() { canceled = true; }); boost::optional exit_status; -#ifdef _WIN32 - std::string command = "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | RUSTUP_HOME=$HOME/.rustup CARGO_HOME=$HOME/.cargo sh -s -- -y"; -#else std::string command = "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y"; -#endif Terminal::get().print("\e[2mRunning: " + command + "\e[m\n"); auto process = Terminal::get().async_process(command, "", [&exit_status](int exit_status_) { exit_status = exit_status_; @@ -648,13 +655,17 @@ void Notebook::install_rust() { std::string env; if(auto c_env = std::getenv("PATH")) env = c_env; - auto cargo_bin = filesystem::get_home_path() / ".cargo" / "bin"; #ifdef _WIN32 + boost::filesystem::path cargo_bin; + if(auto userprofile = std::getenv("USERPROFILE")) + cargo_bin = boost::filesystem::path(userprofile) / ".cargo" / "bin"; const char delimiter = ';'; #else + auto cargo_bin = filesystem::get_home_path() / ".cargo" / "bin"; const char delimiter = ':'; #endif - Glib::setenv("PATH", !env.empty() ? env + delimiter + cargo_bin.string() : cargo_bin.string()); + if(!cargo_bin.empty()) + Glib::setenv("PATH", !env.empty() ? env + delimiter + cargo_bin.string() : cargo_bin.string()); } filesystem::rust_sysroot_path = {}; filesystem::rust_nightly_sysroot_path = {}; From c3afe1009b3c7c24e3ab96e174e8068b4ea2f7a5 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 9 Jul 2021 19:11:51 +0200 Subject: [PATCH 175/375] Added filesystem::is_executable --- src/filesystem.cpp | 42 ++++++++++++++++++++++++--------------- src/filesystem.hpp | 21 +++++++++++--------- src/notebook.cpp | 14 ++----------- tests/filesystem_test.cpp | 22 ++++++++++++++++---- 4 files changed, 58 insertions(+), 41 deletions(-) diff --git a/src/filesystem.cpp b/src/filesystem.cpp index 5dcc6c6..902d76d 100644 --- a/src/filesystem.cpp +++ b/src/filesystem.cpp @@ -36,7 +36,7 @@ bool filesystem::write(const std::string &path, const std::string &new_content) return true; } -std::string filesystem::escape_argument(const std::string &argument) { +std::string filesystem::escape_argument(const std::string &argument) noexcept { auto escaped = argument; for(size_t pos = 0; pos < escaped.size(); ++pos) { if(escaped[pos] == ' ' || escaped[pos] == '(' || escaped[pos] == ')' || escaped[pos] == '\'' || escaped[pos] == '"') { @@ -47,7 +47,7 @@ std::string filesystem::escape_argument(const std::string &argument) { return escaped; } -std::string filesystem::unescape_argument(const std::string &argument) { +std::string filesystem::unescape_argument(const std::string &argument) noexcept { auto unescaped = argument; if(unescaped.size() >= 2) { @@ -204,13 +204,13 @@ boost::filesystem::path filesystem::get_long_path(const boost::filesystem::path #endif } -bool filesystem::file_in_path(const boost::filesystem::path &file_path, const boost::filesystem::path &path) { +bool filesystem::file_in_path(const boost::filesystem::path &file_path, const boost::filesystem::path &path) noexcept { 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) { +boost::filesystem::path filesystem::find_file_in_path_parents(const std::string &file_name, const boost::filesystem::path &path) noexcept { auto current_path = path; boost::system::error_code ec; while(true) { @@ -283,7 +283,7 @@ boost::filesystem::path filesystem::get_executable(const boost::filesystem::path try { for(auto &path : bin_paths) { - if(boost::filesystem::exists(path / executable_name)) + if(is_executable(path / executable_name)) return executable_name; } @@ -317,7 +317,7 @@ boost::filesystem::path filesystem::get_executable(const boost::filesystem::path } // Based on https://stackoverflow.com/a/11295568 -const std::vector &filesystem::get_executable_search_paths() { +const std::vector &filesystem::get_executable_search_paths() noexcept { auto get_paths = [] { std::vector paths; @@ -347,21 +347,16 @@ const std::vector &filesystem::get_executable_search_pa return *executable_search_paths; } -boost::filesystem::path filesystem::find_executable(const std::string &executable_name) { +boost::filesystem::path filesystem::find_executable(const std::string &executable_name) noexcept { for(auto &path : get_executable_search_paths()) { - boost::system::error_code ec; -#ifdef _WIN32 - auto executable_path = path / (executable_name + ".exe"); -#else auto executable_path = path / executable_name; -#endif - if(boost::filesystem::exists(executable_path, ec)) + if(is_executable(executable_path)) return executable_path; } return boost::filesystem::path(); } -std::string filesystem::get_uri_from_path(const boost::filesystem::path &path) { +std::string filesystem::get_uri_from_path(const boost::filesystem::path &path) noexcept { std::string uri{"file://"}; static auto hex_chars = "0123456789ABCDEF"; @@ -378,7 +373,7 @@ std::string filesystem::get_uri_from_path(const boost::filesystem::path &path) { return uri; } -boost::filesystem::path filesystem::get_path_from_uri(const std::string &uri) { +boost::filesystem::path filesystem::get_path_from_uri(const std::string &uri) noexcept { std::string encoded; if(starts_with(uri, "file://")) @@ -403,7 +398,7 @@ boost::filesystem::path filesystem::get_path_from_uri(const std::string &uri) { return unencoded; } -boost::filesystem::path filesystem::get_canonical_path(const boost::filesystem::path &path) { +boost::filesystem::path filesystem::get_canonical_path(const boost::filesystem::path &path) noexcept { try { return boost::filesystem::canonical(path); } @@ -411,3 +406,18 @@ boost::filesystem::path filesystem::get_canonical_path(const boost::filesystem:: return path; } } + +bool filesystem::is_executable(const boost::filesystem::path &path) noexcept { + if(path.empty()) + return false; + boost::system::error_code ec; +#ifdef _WIN32 + // Cannot for sure identify executable files in MSYS2 + if(boost::filesystem::exists(path, ec)) + return !boost::filesystem::is_directory(path, ec); + auto filename = path.filename().string() + ".exe"; + return boost::filesystem::exists(path.has_parent_path() ? path.parent_path() / filename : filename, ec); +#else + return boost::filesystem::exists(path, ec) && !boost::filesystem::is_directory(path, ec) && boost::filesystem::status(path, ec).permissions() & (boost::filesystem::perms::owner_exe | boost::filesystem::perms::group_exe | boost::filesystem::perms::others_exe); +#endif +} diff --git a/src/filesystem.hpp b/src/filesystem.hpp index dc39a2f..90d53d1 100644 --- a/src/filesystem.hpp +++ b/src/filesystem.hpp @@ -14,8 +14,8 @@ public: 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 std::string escape_argument(const std::string &argument) noexcept; + static std::string unescape_argument(const std::string &argument) noexcept; /// Does not resolve symbolic links. Returns empty path on failure. static const boost::filesystem::path &get_current_path() noexcept; @@ -34,8 +34,8 @@ public: /// Replaces ~ with home path (boost::filesystem does not recognize ~) static boost::filesystem::path get_long_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); + static bool file_in_path(const boost::filesystem::path &file_path, const boost::filesystem::path &path) noexcept; + static boost::filesystem::path find_file_in_path_parents(const std::string &file_name, 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; @@ -47,18 +47,21 @@ public: /// 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 &get_executable_search_paths(); + static const std::vector &get_executable_search_paths() noexcept; /// Set to {} to reset get_executable_search_paths static boost::optional> executable_search_paths; /// Returns full executable path if found, or empty path otherwise. - static boost::filesystem::path find_executable(const std::string &executable_name); + static boost::filesystem::path find_executable(const std::string &executable_name) noexcept; /// Get uri from path - static std::string get_uri_from_path(const boost::filesystem::path &path); + static std::string get_uri_from_path(const boost::filesystem::path &path) noexcept; /// Get path from file uri - static boost::filesystem::path get_path_from_uri(const std::string &uri); + static boost::filesystem::path get_path_from_uri(const std::string &uri) noexcept; /// Returns path on error. Do not use boost::filesystem::canonical_path since it is bugged when current_folder() fails. - static boost::filesystem::path get_canonical_path(const boost::filesystem::path &path); + static boost::filesystem::path get_canonical_path(const boost::filesystem::path &path) noexcept; + + /// Platform independent check if path is executable + static bool is_executable(const boost::filesystem::path &path) noexcept; }; diff --git a/src/notebook.cpp b/src/notebook.cpp index d878929..3cbf489 100644 --- a/src/notebook.cpp +++ b/src/notebook.cpp @@ -208,25 +208,15 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position // Try find rust-analyzer installed with rustup auto sysroot = filesystem::get_rust_sysroot_path(); if(!sysroot.empty()) { -#ifdef _WIN32 - auto rust_analyzer = sysroot / "bin" / "rust-analyzer.exe"; -#else auto rust_analyzer = sysroot / "bin" / "rust-analyzer"; -#endif - boost::system::error_code ec; - if(boost::filesystem::exists(rust_analyzer, ec)) + if(filesystem::is_executable(rust_analyzer)) source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, filesystem::escape_argument(rust_analyzer.string()))); else { // Workaround while rust-analyzer is in nightly toolchain only auto nightly_sysroot = filesystem::get_rust_nightly_sysroot_path(); if(!nightly_sysroot.empty()) { -#ifdef _WIN32 - auto nightly_rust_analyzer = nightly_sysroot / "bin" / "rust-analyzer.exe"; -#else auto nightly_rust_analyzer = nightly_sysroot / "bin" / "rust-analyzer"; -#endif - boost::system::error_code ec; - if(boost::filesystem::exists(nightly_rust_analyzer, ec)) + if(filesystem::is_executable(nightly_rust_analyzer)) source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, filesystem::escape_argument(nightly_rust_analyzer.string()))); } } diff --git a/tests/filesystem_test.cpp b/tests/filesystem_test.cpp index ee9835e..695ef8c 100644 --- a/tests/filesystem_test.cpp +++ b/tests/filesystem_test.cpp @@ -20,10 +20,10 @@ int main() { g_assert(!paths.empty()); for(auto &path : paths) { g_assert(!path.empty()); -#ifndef _WIN32 - g_assert(boost::filesystem::exists(path)); - g_assert(boost::filesystem::is_directory(path)); -#endif + if(path.string() != "C:\\msys64\\usr\\local\\bin") { // Workaround for MSYS2 + g_assert(boost::filesystem::exists(path)); + g_assert(boost::filesystem::is_directory(path)); + } } } @@ -119,4 +119,18 @@ int main() { g_assert(uri == "file:///ro%20ot/te%20st%C3%A6%C3%B8%C3%A5.txt"); g_assert(path == filesystem::get_path_from_uri(uri)); } + + { + g_assert(!filesystem::is_executable(filesystem::get_home_path())); + g_assert(!filesystem::is_executable(filesystem::get_current_path())); + g_assert(!filesystem::is_executable(tests_path)); +#ifdef _WIN32 + g_assert(filesystem::is_executable(tests_path / ".." / "LICENSE")); +#else + g_assert(!filesystem::is_executable(tests_path / ".." / "LICENSE")); +#endif + auto ls = filesystem::find_executable("ls"); + g_assert(!ls.empty()); + g_assert(filesystem::is_executable(ls)); + } } From 8bf8148c63584aca2f59bd6df44e0a51b1a76ec1 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 9 Jul 2021 21:53:04 +0200 Subject: [PATCH 176/375] Fixed filesystem_test for MSYS2 --- tests/filesystem_test.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/filesystem_test.cpp b/tests/filesystem_test.cpp index 695ef8c..a5c7c66 100644 --- a/tests/filesystem_test.cpp +++ b/tests/filesystem_test.cpp @@ -18,13 +18,15 @@ int main() { { auto paths = filesystem::get_executable_search_paths(); g_assert(!paths.empty()); + size_t count = 0; for(auto &path : paths) { g_assert(!path.empty()); - if(path.string() != "C:\\msys64\\usr\\local\\bin") { // Workaround for MSYS2 - g_assert(boost::filesystem::exists(path)); + if(boost::filesystem::exists(path)) { g_assert(boost::filesystem::is_directory(path)); + ++count; } } + g_assert(count > 0); } { From b67b919394993b8c467c7c1bbd836fc75cfbe9ba Mon Sep 17 00:00:00 2001 From: eidheim Date: Sat, 10 Jul 2021 06:32:41 +0200 Subject: [PATCH 177/375] Additional utility tests --- tests/utility_test.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/utility_test.cpp b/tests/utility_test.cpp index 1c26bcf..1739c28 100644 --- a/tests/utility_test.cpp +++ b/tests/utility_test.cpp @@ -143,4 +143,19 @@ int main() { g_assert(!starts_with(empty, 0, test)); g_assert(!ends_with(empty, test.c_str())); g_assert(!ends_with(empty, test)); + + { + g_assert(escape("", {}) == ""); + std::string test("^t'e\"st$"); + g_assert(escape(test, {'\"'}) == "^t'e\\\"st$"); + g_assert(escape(test, {'\''}) == "^t\\'e\"st$"); + g_assert(escape(test, {'\'', '"', '$'}) == "^t\\'e\\\"st\\$"); + g_assert(escape(test, {'^', '$'}) == "\\^t'e\"st\\$"); + } + { + g_assert(to_hex_string("") == ""); + g_assert(to_hex_string("\n") == "0a"); + g_assert(to_hex_string("\n!") == "0a21"); + g_assert(to_hex_string("\n!z") == "0a217a"); + } } \ No newline at end of file From da8b1f7f986b654bde9761005d330466bc6c972e Mon Sep 17 00:00:00 2001 From: eidheim Date: Sat, 10 Jul 2021 06:41:07 +0200 Subject: [PATCH 178/375] Minor cleanup of rustup PATH modification --- src/notebook.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/notebook.cpp b/src/notebook.cpp index 3cbf489..7a2ea54 100644 --- a/src/notebook.cpp +++ b/src/notebook.cpp @@ -73,7 +73,7 @@ Notebook::Notebook() : Gtk::Paned(), notebooks(2) { // PATH might not be set (for instance after installation) #ifdef _WIN32 boost::filesystem::path cargo_bin; - if(auto userprofile = std::getenv("USERPROFILE")) + if(auto userprofile = std::getenv("USERPROFILE")) // rustup uses this environment variable on MSYS2 as well cargo_bin = boost::filesystem::path(userprofile) / ".cargo" / "bin"; const char delimiter = ';'; #else @@ -642,20 +642,21 @@ void Notebook::install_rust() { } if(exit_status == 0) { if(filesystem::find_executable("rustup").empty()) { - std::string env; - if(auto c_env = std::getenv("PATH")) - env = c_env; #ifdef _WIN32 boost::filesystem::path cargo_bin; - if(auto userprofile = std::getenv("USERPROFILE")) + if(auto userprofile = std::getenv("USERPROFILE")) // rustup uses this environment variable on MSYS2 as well cargo_bin = boost::filesystem::path(userprofile) / ".cargo" / "bin"; const char delimiter = ';'; #else auto cargo_bin = filesystem::get_home_path() / ".cargo" / "bin"; const char delimiter = ':'; #endif - if(!cargo_bin.empty()) + if(!cargo_bin.empty()) { + std::string env; + if(auto c_env = std::getenv("PATH")) + env = c_env; Glib::setenv("PATH", !env.empty() ? env + delimiter + cargo_bin.string() : cargo_bin.string()); + } } filesystem::rust_sysroot_path = {}; filesystem::rust_nightly_sysroot_path = {}; From dc57a8749cba465e875af6446489a6bf8fb6d0e8 Mon Sep 17 00:00:00 2001 From: eidheim Date: Sat, 10 Jul 2021 21:39:46 +0200 Subject: [PATCH 179/375] Added version_compare and adds parallel to default value in preference item project.cmake.compile_command --- src/CMakeLists.txt | 2 +- src/config.cpp | 493 +++++++++++++++++++++++++++++++++++++- src/config.hpp | 8 + src/files.hpp | 440 ---------------------------------- src/filesystem.cpp | 45 ++-- src/filesystem.hpp | 4 +- src/utility.cpp | 40 ++++ src/utility.hpp | 3 + tests/CMakeLists.txt | 1 - tests/filesystem_test.cpp | 8 + tests/json_test.cpp | 4 +- tests/source_test.cpp | 2 +- tests/stubs/config.cpp | 3 - tests/utility_test.cpp | 22 ++ 14 files changed, 593 insertions(+), 482 deletions(-) delete mode 100644 src/files.hpp delete mode 100644 tests/stubs/config.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 71cb1d9..1612307 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,6 +3,7 @@ set(JUCI_SHARED_FILES autocomplete.cpp cmake.cpp commands.cpp + config.cpp compile_commands.cpp ctags.cpp dispatcher.cpp @@ -44,7 +45,6 @@ target_link_libraries(juci_shared ) set(JUCI_FILES - config.cpp dialog.cpp directories.cpp entrybox.cpp diff --git a/src/config.cpp b/src/config.cpp index 283955f..b26349f 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -1,10 +1,12 @@ #include "config.hpp" -#include "files.hpp" #include "filesystem.hpp" +#include "process.hpp" #include "terminal.hpp" +#include "utility.hpp" #include #include #include +#include Config::Config() { home_path = filesystem::get_home_path(); @@ -20,22 +22,22 @@ void Config::load() { boost::filesystem::create_directories(config_dir); if(!boost::filesystem::exists(config_json)) - filesystem::write(config_json, default_config_file); + filesystem::write(config_json, default_config()); auto juci_style_path = home_juci_path / "styles"; boost::filesystem::create_directories(juci_style_path); juci_style_path /= "juci-light.xml"; if(!boost::filesystem::exists(juci_style_path)) - filesystem::write(juci_style_path, juci_light_style); + 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); + 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); + filesystem::write(juci_style_path, juci_dark_blue_style()); JSON cfg(config_json); update(cfg); @@ -45,7 +47,7 @@ void Config::load() { dispatcher.post([config_json = std::move(config_json), e_what = std::string(e.what())] { ::Terminal::get().print("\e[31mError\e[m: could not parse " + filesystem::get_short_path(config_json).string() + ": " + e_what + "\n", true); }); - JSON default_cfg(default_config_file); + JSON default_cfg(default_config()); read(default_cfg); } } @@ -54,7 +56,7 @@ void Config::update(JSON &cfg) { auto version = cfg.string("version"); if(version == JUCI_VERSION) return; - JSON default_cfg(default_config_file); + JSON default_cfg(default_config()); make_version_dependent_corrections(cfg, default_cfg, version); cfg.set("version", JUCI_VERSION); @@ -64,14 +66,14 @@ void Config::update(JSON &cfg) { cfg.to_file(home_juci_path / "config" / "config.json", 2); 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); + 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()); } void Config::make_version_dependent_corrections(JSON &cfg, const JSON &default_cfg, const std::string &version) { try { - if(version <= "1.2.4") { + if(version_compare(version, "1.2.4") <= 0) { auto keybindings = cfg.object("keybindings"); auto print = keybindings.string_optional("print"); if(print && *print == "p") { @@ -197,3 +199,472 @@ void Config::read(const JSON &cfg) { log.libclang = log_json.boolean("libclang", JSON::ParseOptions::accept_string); log.language_server = log_json.boolean("language_server", JSON::ParseOptions::accept_string); } + +std::string Config::default_config() { + static auto get_config = [] { + std::string cmake_version; + unsigned thread_count = 0; + std::stringstream ss; + TinyProcessLib::Process process( + "cmake --version", "", + [&ss](const char *buffer, size_t n) { + ss.write(buffer, n); + }, + [](const char *buffer, size_t n) {}); + if(process.get_exit_status() == 0) { + std::string line; + if(std::getline(ss, line)) { + auto pos = line.rfind(" "); + if(pos != std::string::npos) { + cmake_version = line.substr(pos + 1); + thread_count = std::thread::hardware_concurrency(); + } + } + } + + return std::string(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": "", + "variant_comment": "Use \"\" for default variant, and \"dark\" for dark theme variant. Note that not all themes support dark variant, but for instance Adwaita does", + "variant": "", + "font_comment": "Set to override theme font, for instance: \"Arial 12\"", + "font": "" + }, + "source": { + "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( + "font": "Menlo",)RAW" +#else +#ifdef _WIN32 + R"RAW( + "font": "Consolas",)RAW" +#else + R"RAW( + "font": "Monospace",)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", + "show_whitespace_characters": "", + "format_style_on_save_comment": "Performs style format on save if supported on language in buffer", + "format_style_on_save": false, + "format_style_on_save_if_style_file_found_comment": "Format style if format file is found, even if format_style_on_save is false", + "format_style_on_save_if_style_file_found": true, + "smart_brackets_comment": "If smart_inserts is enabled, this option is automatically enabled. When inserting an already closed bracket, the cursor might instead be moved, avoiding the need of arrow keys after autocomplete", + "smart_brackets": true, + "smart_inserts_comment": "When for instance inserting (, () gets inserted. Applies to: (), [], \", '. Also enables pressing ; inside an expression before a final ) to insert ; at the end of line, and deletions of empty insertions", + "smart_inserts": true, + "show_map": true, + "map_font_size": 1, + "show_git_diff": true, + "show_background_pattern": true, + "show_right_margin": false, + "right_margin_position": 80, + "spellcheck_language_comment": "Use \"\" to set language from your locale settings", + "spellcheck_language": "en_US", + "auto_tab_char_and_size_comment": "Use false to always use default tab char and size", + "auto_tab_char_and_size": true, + "default_tab_char_comment": "Use \"\t\" for regular tab", + "default_tab_char": " ", + "default_tab_size": 2, + "tab_indents_line": true, + "word_wrap_comment": "Specify language ids that should enable word wrap, for instance: chdr, c, cpphdr, cpp, js, python, or all to enable word wrap for all languages", + "word_wrap": "markdown, latex", + "highlight_current_line": true, + "show_line_numbers": true, + "enable_multiple_cursors": false, + "auto_reload_changed_files": true, + "search_for_selection": true, + "clang_format_style_comment": "IndentWidth, AccessModifierOffset and UseTab are set automatically. See http://clang.llvm.org/docs/ClangFormatStyleOptions.html", + "clang_format_style": "ColumnLimit: 0, NamespaceIndentation: All", + "clang_tidy_enable_comment": "Enable clang-tidy in new C/C++ buffers", + "clang_tidy_enable": false, + "clang_tidy_checks_comment": "In new C/C++ buffers, these checks are appended to the value of 'Checks' in the .clang-tidy file, if any", + "clang_tidy_checks": "", + "clang_usages_threads_comment": "The number of threads used in finding usages in unparsed files. -1 corresponds to the number of cores available, and 0 disables the search", + "clang_usages_threads": -1, + "clang_detailed_preprocessing_record_comment": "Set to true to, at the cost of increased resource use, include all macro definitions and instantiations when parsing new C/C++ buffers. You should reopen buffers and delete build/.usages_clang after changing this option.", + "clang_detailed_preprocessing_record": false, + "debug_place_cursor_at_stop": false + }, + "terminal": { + "history_size": 10000, + "font_comment": "Use \"\" to use source.font with slightly smaller size", + "font": "", + "clear_on_compile": true, + "clear_on_run_command": false, + "hide_entry_on_run_command": true + }, + "project": { + "default_build_path_comment": "Use to insert the project top level directory name", + "default_build_path": "./build", + "debug_build_path_comment": "Use to insert the project top level directory name, and to insert your default_build_path setting.", + "debug_build_path": "/debug", + "cmake": {)RAW" +#ifdef _WIN32 + R"RAW( + "command": "cmake -G\"MSYS Makefiles\"",)RAW" +#else + R"RAW( + "command": "cmake",)RAW" +#endif + R"RAW( + "compile_command": "cmake --build .)RAW" + + (thread_count > 1 && !cmake_version.empty() && version_compare(cmake_version, "3.12") >= 0 ? " --parallel " + std::to_string(thread_count) : "") + + R"RAW(" + }, + "meson": { + "command": "meson", + "compile_command": "ninja" + }, + "default_build_management_system_comment": "Select which build management system to use when creating a new C or C++ project, for instance \"cmake\" or \"meson\"", + "default_build_management_system": "cmake", + "save_on_compile_or_run": true,)RAW" +#ifdef JUCI_USE_UCTAGS + R"RAW( + "ctags_command": "uctags",)RAW" +#else + R"RAW( + "ctags_command": "ctags",)RAW" +#endif + R"RAW( + "grep_command": "grep", + "cargo_command": "cargo", + "python_command": "python -u", + "markdown_command": "grip -b" + }, + "keybindings": { + "preferences": "comma", + "snippets": "", + "commands": "", + "quit": "q", + "file_new_file": "n", + "file_new_folder": "n", + "file_open_file": "o", + "file_open_folder": "o", + "file_reload_file": "", + "file_save": "s", + "file_save_as": "s", + "file_close_file": "w", + "file_close_folder": "", + "file_close_project": "", + "file_print": "", + "edit_undo": "z", + "edit_redo": "z", + "edit_cut": "x", + "edit_cut_lines": "x", + "edit_copy": "c", + "edit_copy_lines": "c", + "edit_paste": "v", + "edit_extend_selection": "a", + "edit_shrink_selection": "a", + "edit_show_or_hide": "", + "edit_find": "f", + "source_spellcheck": "", + "source_spellcheck_clear": "", + "source_spellcheck_next_error": "e", + "source_git_next_diff": "k", + "source_git_show_diff": "k", + "source_indentation_set_buffer_tab": "", + "source_indentation_auto_indent_buffer": "i", + "source_goto_line": "g", + "source_center_cursor": "l", + "source_cursor_history_back": "Left", + "source_cursor_history_forward": "Right", + "source_show_completion_comment": "Add completion keybinding to disable interactive autocompletion", + "source_show_completion": "", + "source_find_file": "p", + "source_find_symbol": "f", + "source_find_pattern": "f", + "source_comments_toggle": "slash", + "source_comments_add_documentation": "slash", + "source_find_documentation": "d", + "source_goto_declaration": "d", + "source_goto_type_declaration": "d", + "source_goto_implementation": "i", + "source_goto_usage": "u", + "source_goto_method": "m", + "source_rename": "r", + "source_implement_method": "m", + "source_goto_next_diagnostic": "e", + "source_apply_fix_its": "space", + "project_set_run_arguments": "", + "project_compile_and_run": "Return", + "project_compile": "Return", + "project_run_command": "Return", + "project_kill_last_running": "Escape", + "project_force_kill_last_running": "Escape", + "debug_set_run_arguments": "", + "debug_start_continue": "y", + "debug_stop": "y", + "debug_kill": "k", + "debug_step_over": "j", + "debug_step_into": "t", + "debug_step_out": "t", + "debug_backtrace": "j", + "debug_show_variables": "b", + "debug_run_command": "Return", + "debug_toggle_breakpoint": "b", + "debug_show_breakpoints": "b", + "debug_goto_stop": "l",)RAW" +#ifdef __linux + R"RAW( + "window_next_tab": "Tab", + "window_previous_tab": "Tab",)RAW" +#else + R"RAW( + "window_next_tab": "Right", + "window_previous_tab": "Left",)RAW" +#endif + R"RAW( + "window_goto_tab": "", + "window_toggle_split": "", + "window_split_source_buffer": "",)RAW" +#ifdef __APPLE__ + R"RAW( + "window_toggle_full_screen": "f",)RAW" +#else + R"RAW( + "window_toggle_full_screen": "F11",)RAW" +#endif + R"RAW( + "window_toggle_directories": "", + "window_toggle_terminal": "", + "window_toggle_menu": "", + "window_toggle_tabs": "", + "window_toggle_zen_mode": "", + "window_clear_terminal": "" + }, + "documentation_searches": { + "clang": { + "separator": "::", + "queries": { + "@empty": "https://www.google.com/search?q=c%2B%2B+", + "std": "https://www.google.com/search?q=site:http://www.cplusplus.com/reference/+", + "boost": "https://www.google.com/search?q=site:http://www.boost.org/doc/libs/1_59_0/+", + "Gtk": "https://www.google.com/search?q=site:https://developer.gnome.org/gtkmm/stable/+", + "@any": "https://www.google.com/search?q=" + } + } + }, + "log": { + "libclang_comment": "Outputs diagnostics for new C/C++ buffers", + "libclang": false, + "language_server": false + } +} +)RAW"); + }; + static auto config = get_config(); + return config; +} + +const char *Config::juci_light_style() { + return R"RAW( + + + juCi++ team + <_description>Default juCi++ style + + + + + + + + + + + + + + +