Browse Source

Added JSON::write_json, and some various cleanup

pipelines/353213535
eidheim 5 years ago
parent
commit
c7340b709d
  1. 1
      src/CMakeLists.txt
  2. 2
      src/cmake.cpp
  3. 13
      src/config.cpp
  4. 437
      src/files.hpp
  5. 4
      src/filesystem.cpp
  6. 101
      src/json.cpp
  7. 19
      src/json.hpp
  8. 2
      src/meson.cpp
  9. 2
      src/project.cpp
  10. 2
      src/project_build.cpp
  11. 4
      src/source_base.cpp
  12. 367
      src/source_language_protocol.cpp
  13. 16
      src/source_language_protocol.hpp
  14. 2
      src/usages_clang.cpp
  15. 20
      src/window.cpp
  16. 6
      tests/CMakeLists.txt
  17. 58
      tests/json_test.cpp
  18. 32
      tests/language_protocol_server_test.cpp

1
src/CMakeLists.txt

@ -10,6 +10,7 @@ set(JUCI_SHARED_FILES
filesystem.cpp filesystem.cpp
git.cpp git.cpp
grep.cpp grep.cpp
json.cpp
menu.cpp menu.cpp
meson.cpp meson.cpp
project_build.cpp project_build.cpp

2
src/cmake.cpp

@ -12,7 +12,7 @@
CMake::CMake(const boost::filesystem::path &path) { CMake::CMake(const boost::filesystem::path &path) {
const auto find_cmake_project = [](const boost::filesystem::path &file_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) { if(input) {
std::string line; std::string line;
while(std::getline(input, line)) { while(std::getline(input, line)) {

13
src/config.cpp

@ -1,6 +1,7 @@
#include "config.hpp" #include "config.hpp"
#include "files.hpp" #include "files.hpp"
#include "filesystem.hpp" #include "filesystem.hpp"
#include "json.hpp"
#include "terminal.hpp" #include "terminal.hpp"
#include <algorithm> #include <algorithm>
#include <exception> #include <exception>
@ -77,8 +78,16 @@ void Config::update(boost::property_tree::ptree &cfg) {
return; return;
cfg_ok &= add_missing_nodes(cfg, default_cfg); cfg_ok &= add_missing_nodes(cfg, default_cfg);
cfg_ok &= remove_deprecated_nodes(cfg, default_cfg); cfg_ok &= remove_deprecated_nodes(cfg, default_cfg);
if(!cfg_ok) if(!cfg_ok) {
boost::property_tree::write_json((home_juci_path / "config" / "config.json").string(), cfg); 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) { void Config::make_version_dependent_corrections(boost::property_tree::ptree &cfg, const boost::property_tree::ptree &default_cfg, const std::string &version) {

437
src/files.hpp

@ -4,244 +4,243 @@
/// If you add or remove nodes from the default_config_file, increase the juci /// If you add or remove nodes from the default_config_file, increase the juci
/// version number (JUCI_VERSION) in ../CMakeLists.txt to automatically apply /// version number (JUCI_VERSION) in ../CMakeLists.txt to automatically apply
/// the changes to user's ~/.juci/config/config.json files /// the changes to user's ~/.juci/config/config.json files
const std::string default_config_file = const std::string default_config_file = R"RAW({
R"RAW({ "version": ")RAW" +
"version": ")RAW" + std::string(JUCI_VERSION) +
std::string(JUCI_VERSION) + R"RAW(",
R"RAW(", "gtk_theme": {
"gtk_theme": { "name_comment": "Use \"\" for default theme, At least these two exist on all systems: Adwaita, Raleigh",
"name_comment": "Use \"\" for default theme, At least these two exist on all systems: Adwaita, Raleigh", "name": "",
"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_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": "",
"variant": "", "font_comment": "Set to override theme font, for instance: \"Arial 12\"",
"font_comment": "Set to override theme font, for instance: \"Arial 12\"", "font": ""
"font": "" },
}, "source": {
"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_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",
"style": "juci-light", "font_comment": "Use \"\" for default font, and for instance \"Monospace 12\" to also set size",)RAW"
"font_comment": "Use \"\" for default font, and for instance \"Monospace 12\" to also set size",)RAW"
#ifdef __APPLE__ #ifdef __APPLE__
R"RAW( R"RAW(
"font": "Menlo",)RAW" "font": "Menlo",)RAW"
#else #else
#ifdef _WIN32 #ifdef _WIN32
R"RAW( R"RAW(
"font": "Consolas",)RAW" "font": "Consolas",)RAW"
#else #else
R"RAW( R"RAW(
"font": "Monospace",)RAW" "font": "Monospace",)RAW"
#endif #endif
#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_comment": "Remove trailing whitespace characters on save, and add trailing newline if missing",
"cleanup_whitespace_characters": false, "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_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": "", "show_whitespace_characters": "",
"format_style_on_save_comment": "Performs style format on save if supported on language in buffer", "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": 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_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, "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_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_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_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, "smart_inserts": true,
"show_map": true, "show_map": true,
"map_font_size": "1", "map_font_size": 1,
"show_git_diff": true, "show_git_diff": true,
"show_background_pattern": true, "show_background_pattern": true,
"show_right_margin": false, "show_right_margin": false,
"right_margin_position": 80, "right_margin_position": 80,
"spellcheck_language_comment": "Use \"\" to set language from your locale settings", "spellcheck_language_comment": "Use \"\" to set language from your locale settings",
"spellcheck_language": "en_US", "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_comment": "Use false to always use default tab char and size",
"auto_tab_char_and_size": true, "auto_tab_char_and_size": true,
"default_tab_char_comment": "Use \"\t\" for regular tab", "default_tab_char_comment": "Use \"\t\" for regular tab",
"default_tab_char": " ", "default_tab_char": " ",
"default_tab_size": 2, "default_tab_size": 2,
"tab_indents_line": true, "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_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", "word_wrap": "markdown, latex",
"highlight_current_line": true, "highlight_current_line": true,
"show_line_numbers": true, "show_line_numbers": true,
"enable_multiple_cursors": false, "enable_multiple_cursors": false,
"auto_reload_changed_files": true, "auto_reload_changed_files": true,
"search_for_selection": 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_comment": "IndentWidth, AccessModifierOffset and UseTab are set automatically. See http://clang.llvm.org/docs/ClangFormatStyleOptions.html",
"clang_format_style": "ColumnLimit: 0, NamespaceIndentation: All", "clang_format_style": "ColumnLimit: 0, NamespaceIndentation: All",
"clang_tidy_enable_comment": "Enable clang-tidy in new C/C++ buffers", "clang_tidy_enable_comment": "Enable clang-tidy in new C/C++ buffers",
"clang_tidy_enable": false, "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_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_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_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_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_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, "clang_detailed_preprocessing_record": false,
"debug_place_cursor_at_stop": false "debug_place_cursor_at_stop": false
}, },
"terminal": { "terminal": {
"history_size": 10000, "history_size": 10000,
"font_comment": "Use \"\" to use source.font with slightly smaller size", "font_comment": "Use \"\" to use source.font with slightly smaller size",
"font": "", "font": "",
"clear_on_compile": true, "clear_on_compile": true,
"clear_on_run_command": false, "clear_on_run_command": false,
"hide_entry_on_run_command": true "hide_entry_on_run_command": true
}, },
"project": { "project": {
"default_build_path_comment": "Use <project_directory_name> to insert the project top level directory name", "default_build_path_comment": "Use <project_directory_name> to insert the project top level directory name",
"default_build_path": "./build", "default_build_path": "./build",
"debug_build_path_comment": "Use <project_directory_name> to insert the project top level directory name, and <default_build_path> to insert your default_build_path setting.", "debug_build_path_comment": "Use <project_directory_name> to insert the project top level directory name, and <default_build_path> to insert your default_build_path setting.",
"debug_build_path": "<default_build_path>/debug", "debug_build_path": "<default_build_path>/debug",
"cmake": {)RAW" "cmake": {)RAW"
#ifdef _WIN32 #ifdef _WIN32
R"RAW( R"RAW(
"command": "cmake -G\"MSYS Makefiles\"",)RAW" "command": "cmake -G\"MSYS Makefiles\"",)RAW"
#else #else
R"RAW( R"RAW(
"command": "cmake",)RAW" "command": "cmake",)RAW"
#endif #endif
R"RAW( R"RAW(
"compile_command": "cmake --build ." "compile_command": "cmake --build ."
}, },
"meson": { "meson": {
"command": "meson", "command": "meson",
"compile_command": "ninja" "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_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", "default_build_management_system": "cmake",
"save_on_compile_or_run": true,)RAW" "save_on_compile_or_run": true,)RAW"
#ifdef JUCI_USE_UCTAGS #ifdef JUCI_USE_UCTAGS
R"RAW( R"RAW(
"ctags_command": "uctags",)RAW" "ctags_command": "uctags",)RAW"
#else #else
R"RAW( R"RAW(
"ctags_command": "ctags",)RAW" "ctags_command": "ctags",)RAW"
#endif #endif
R"RAW( R"RAW(
"grep_command": "grep", "grep_command": "grep",
"cargo_command": "cargo", "cargo_command": "cargo",
"python_command": "python -u", "python_command": "python -u",
"markdown_command": "grip -b" "markdown_command": "grip -b"
}, },
"keybindings": { "keybindings": {
"preferences": "<primary>comma", "preferences": "<primary>comma",
"snippets": "", "snippets": "",
"commands": "", "commands": "",
"quit": "<primary>q", "quit": "<primary>q",
"file_new_file": "<primary>n", "file_new_file": "<primary>n",
"file_new_folder": "<primary><shift>n", "file_new_folder": "<primary><shift>n",
"file_open_file": "<primary>o", "file_open_file": "<primary>o",
"file_open_folder": "<primary><shift>o", "file_open_folder": "<primary><shift>o",
"file_reload_file": "", "file_reload_file": "",
"file_save": "<primary>s", "file_save": "<primary>s",
"file_save_as": "<primary><shift>s", "file_save_as": "<primary><shift>s",
"file_close_file": "<primary>w", "file_close_file": "<primary>w",
"file_close_folder": "", "file_close_folder": "",
"file_close_project": "", "file_close_project": "",
"file_print": "", "file_print": "",
"edit_undo": "<primary>z", "edit_undo": "<primary>z",
"edit_redo": "<primary><shift>z", "edit_redo": "<primary><shift>z",
"edit_cut": "<primary>x", "edit_cut": "<primary>x",
"edit_cut_lines": "<primary><shift>x", "edit_cut_lines": "<primary><shift>x",
"edit_copy": "<primary>c", "edit_copy": "<primary>c",
"edit_copy_lines": "<primary><shift>c", "edit_copy_lines": "<primary><shift>c",
"edit_paste": "<primary>v", "edit_paste": "<primary>v",
"edit_extend_selection": "<primary><shift>a", "edit_extend_selection": "<primary><shift>a",
"edit_shrink_selection": "<primary><shift><alt>a", "edit_shrink_selection": "<primary><shift><alt>a",
"edit_show_or_hide": "", "edit_show_or_hide": "",
"edit_find": "<primary>f", "edit_find": "<primary>f",
"source_spellcheck": "", "source_spellcheck": "",
"source_spellcheck_clear": "", "source_spellcheck_clear": "",
"source_spellcheck_next_error": "<primary><shift>e", "source_spellcheck_next_error": "<primary><shift>e",
"source_git_next_diff": "<primary>k", "source_git_next_diff": "<primary>k",
"source_git_show_diff": "<alt>k", "source_git_show_diff": "<alt>k",
"source_indentation_set_buffer_tab": "", "source_indentation_set_buffer_tab": "",
"source_indentation_auto_indent_buffer": "<primary><shift>i", "source_indentation_auto_indent_buffer": "<primary><shift>i",
"source_goto_line": "<primary>g", "source_goto_line": "<primary>g",
"source_center_cursor": "<primary>l", "source_center_cursor": "<primary>l",
"source_cursor_history_back": "<alt>Left", "source_cursor_history_back": "<alt>Left",
"source_cursor_history_forward": "<alt>Right", "source_cursor_history_forward": "<alt>Right",
"source_show_completion_comment": "Add completion keybinding to disable interactive autocompletion", "source_show_completion_comment": "Add completion keybinding to disable interactive autocompletion",
"source_show_completion": "", "source_show_completion": "",
"source_find_file": "<primary>p", "source_find_file": "<primary>p",
"source_find_symbol": "<primary><shift>f", "source_find_symbol": "<primary><shift>f",
"source_find_pattern": "<alt><shift>f", "source_find_pattern": "<alt><shift>f",
"source_comments_toggle": "<primary>slash", "source_comments_toggle": "<primary>slash",
"source_comments_add_documentation": "<primary><alt>slash", "source_comments_add_documentation": "<primary><alt>slash",
"source_find_documentation": "<primary><shift>d", "source_find_documentation": "<primary><shift>d",
"source_goto_declaration": "<primary>d", "source_goto_declaration": "<primary>d",
"source_goto_type_declaration": "<alt><shift>d", "source_goto_type_declaration": "<alt><shift>d",
"source_goto_implementation": "<primary>i", "source_goto_implementation": "<primary>i",
"source_goto_usage": "<primary>u", "source_goto_usage": "<primary>u",
"source_goto_method": "<primary>m", "source_goto_method": "<primary>m",
"source_rename": "<primary>r", "source_rename": "<primary>r",
"source_implement_method": "<primary><shift>m", "source_implement_method": "<primary><shift>m",
"source_goto_next_diagnostic": "<primary>e", "source_goto_next_diagnostic": "<primary>e",
"source_apply_fix_its": "<control>space", "source_apply_fix_its": "<control>space",
"project_set_run_arguments": "", "project_set_run_arguments": "",
"project_compile_and_run": "<primary>Return", "project_compile_and_run": "<primary>Return",
"project_compile": "<primary><shift>Return", "project_compile": "<primary><shift>Return",
"project_run_command": "<alt>Return", "project_run_command": "<alt>Return",
"project_kill_last_running": "<primary>Escape", "project_kill_last_running": "<primary>Escape",
"project_force_kill_last_running": "<primary><shift>Escape", "project_force_kill_last_running": "<primary><shift>Escape",
"debug_set_run_arguments": "", "debug_set_run_arguments": "",
"debug_start_continue": "<primary>y", "debug_start_continue": "<primary>y",
"debug_stop": "<primary><shift>y", "debug_stop": "<primary><shift>y",
"debug_kill": "<primary><shift>k", "debug_kill": "<primary><shift>k",
"debug_step_over": "<primary>j", "debug_step_over": "<primary>j",
"debug_step_into": "<primary>t", "debug_step_into": "<primary>t",
"debug_step_out": "<primary><shift>t", "debug_step_out": "<primary><shift>t",
"debug_backtrace": "<primary><shift>j", "debug_backtrace": "<primary><shift>j",
"debug_show_variables": "<primary><shift>b", "debug_show_variables": "<primary><shift>b",
"debug_run_command": "<alt><shift>Return", "debug_run_command": "<alt><shift>Return",
"debug_toggle_breakpoint": "<primary>b", "debug_toggle_breakpoint": "<primary>b",
"debug_show_breakpoints": "<primary><shift><alt>b", "debug_show_breakpoints": "<primary><shift><alt>b",
"debug_goto_stop": "<primary><shift>l",)RAW" "debug_goto_stop": "<primary><shift>l",)RAW"
#ifdef __linux #ifdef __linux
R"RAW( R"RAW(
"window_next_tab": "<primary>Tab", "window_next_tab": "<primary>Tab",
"window_previous_tab": "<primary><shift>Tab",)RAW" "window_previous_tab": "<primary><shift>Tab",)RAW"
#else #else
R"RAW( R"RAW(
"window_next_tab": "<primary><alt>Right", "window_next_tab": "<primary><alt>Right",
"window_previous_tab": "<primary><alt>Left",)RAW" "window_previous_tab": "<primary><alt>Left",)RAW"
#endif #endif
R"RAW( R"RAW(
"window_goto_tab": "", "window_goto_tab": "",
"window_toggle_split": "", "window_toggle_split": "",
"window_split_source_buffer": "",)RAW" "window_split_source_buffer": "",)RAW"
#ifdef __APPLE__ #ifdef __APPLE__
R"RAW( R"RAW(
"window_toggle_full_screen": "<primary><control>f",)RAW" "window_toggle_full_screen": "<primary><control>f",)RAW"
#else #else
R"RAW( R"RAW(
"window_toggle_full_screen": "F11",)RAW" "window_toggle_full_screen": "F11",)RAW"
#endif #endif
R"RAW( R"RAW(
"window_toggle_directories": "", "window_toggle_directories": "",
"window_toggle_terminal": "", "window_toggle_terminal": "",
"window_toggle_menu": "", "window_toggle_menu": "",
"window_toggle_tabs": "", "window_toggle_tabs": "",
"window_toggle_zen_mode": "", "window_toggle_zen_mode": "",
"window_clear_terminal": "" "window_clear_terminal": ""
}, },
"documentation_searches": { "documentation_searches": {
"clang": { "clang": {
"separator": "::", "separator": "::",
"queries": { "queries": {
"@empty": "https://www.google.com/search?q=c%2B%2B+", "@empty": "https://www.google.com/search?q=c%2B%2B+",
"std": "https://www.google.com/search?q=site:http://www.cplusplus.com/reference/+", "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/+", "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/+", "Gtk": "https://www.google.com/search?q=site:https://developer.gnome.org/gtkmm/stable/+",
"@any": "https://www.google.com/search?q=" "@any": "https://www.google.com/search?q="
} }
}
},
"log": {
"libclang_comment": "Outputs diagnostics for new C/C++ buffers",
"libclang": false,
"language_server": false
} }
},
"log": {
"libclang_comment": "Outputs diagnostics for new C/C++ buffers",
"libclang": false,
"language_server": false
}
} }
)RAW"; )RAW";

4
src/filesystem.cpp

@ -9,7 +9,7 @@
//Only use on small files //Only use on small files
std::string filesystem::read(const std::string &path) { std::string filesystem::read(const std::string &path) {
std::string str; std::string str;
std::ifstream input(path, std::ofstream::binary); std::ifstream input(path, std::ios::binary);
if(input) { if(input) {
input.seekg(0, std::ios::end); input.seekg(0, std::ios::end);
auto size = input.tellg(); auto size = input.tellg();
@ -23,7 +23,7 @@ std::string filesystem::read(const std::string &path) {
//Only use on small files //Only use on small files
bool filesystem::write(const std::string &path, const std::string &new_content) { 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) if(output)
output << new_content; output << new_content;
else else

101
src/json.cpp

@ -0,0 +1,101 @@
#include "json.hpp"
#include <algorithm>
#include <fstream>
#include <iostream>
#include <sstream>
void JSON::write_json_internal(std::ostream &stream, const boost::property_tree::ptree &pt, bool pretty, const std::vector<std::string> &not_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<std::string>();
if(std::any_of(not_string_keys.begin(), not_string_keys.end(), [&key](const std::string &not_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<std::string> &not_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;
}

19
src/json.hpp

@ -0,0 +1,19 @@
#pragma once
#include <boost/filesystem.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <ostream>
class JSON {
static void write_json_internal(std::ostream &stream, const boost::property_tree::ptree &pt, bool pretty, const std::vector<std::string> &not_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<std::string> &not_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);
};

2
src/meson.cpp

@ -12,7 +12,7 @@
Meson::Meson(const boost::filesystem::path &path) { Meson::Meson(const boost::filesystem::path &path) {
const auto find_project = [](const boost::filesystem::path &file_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) { if(input) {
std::string line; std::string line;
while(std::getline(input, line)) { while(std::getline(input, line)) {

2
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(); auto sysroot = filesystem::get_rust_sysroot_path().string();
if(!sysroot.empty()) { if(!sysroot.empty()) {
std::string line; 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) { if(input) {
startup_commands.emplace_back("command script import \"" + sysroot + "/lib/rustlib/etc/lldb_lookup.py\""); startup_commands.emplace_back("command script import \"" + sysroot + "/lib/rustlib/etc/lldb_lookup.py\"");
while(std::getline(input, line)) while(std::getline(input, line))

2
src/project_build.cpp

@ -152,7 +152,7 @@ bool Project::CMakeBuild::is_valid() {
auto default_path = get_default_path(); auto default_path = get_default_path();
if(default_path.empty()) if(default_path.empty())
return true; 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) if(!input)
return true; return true;
std::string line; std::string line;

4
src/source_base.cpp

@ -409,7 +409,7 @@ bool Source::BaseView::load(bool not_undoable_action) {
} }
} }
catch(const Glib::Error &error) { 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; return false;
} }
} }
@ -479,7 +479,7 @@ void Source::BaseView::replace_text(const std::string &new_text) {
} }
} }
catch(...) { 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(); get_buffer()->end_user_action();

367
src/source_language_protocol.cpp

@ -9,6 +9,7 @@
#include "debug_lldb.hpp" #include "debug_lldb.hpp"
#endif #endif
#include "config.hpp" #include "config.hpp"
#include "json.hpp"
#include "menu.hpp" #include "menu.hpp"
#include "utility.hpp" #include "utility.hpp"
#include <future> #include <future>
@ -16,6 +17,7 @@
#include <regex> #include <regex>
#include <unordered_map> #include <unordered_map>
const std::vector<std::string> 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."; const std::string type_coverage_message = "Un-type checked code. Consider adding type annotations.";
LanguageProtocol::Offset::Offset(const boost::property_tree::ptree &pt) { 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_); file = std::move(file_);
} }
LanguageProtocol::Documentation::Documentation(const boost::property_tree::ptree &pt) {
value = pt.get<std::string>("documentation", "");
if(value.empty()) {
if(auto documentation_pt = pt.get_child_optional("documentation")) {
value = documentation_pt->get<std::string>("value", "");
kind = documentation_pt->get<std::string>("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<std::string>("message")), location(pt.get_child("location")) {} LanguageProtocol::Diagnostic::RelatedInformation::RelatedInformation(const boost::property_tree::ptree &pt) : message(pt.get<std::string>("message")), location(pt.get_child("location")) {}
LanguageProtocol::Diagnostic::Diagnostic(const boost::property_tree::ptree &pt) : message(pt.get<std::string>("message")), range(pt.get_child("range")), severity(pt.get<int>("severity", 0)), code(pt.get<std::string>("code", "")) { LanguageProtocol::Diagnostic::Diagnostic(const boost::property_tree::ptree &pt) : message(pt.get<std::string>("message")), range(pt.get_child("range")), severity(pt.get<int>("severity", 0)), code(pt.get<std::string>("code", "")), ptree(pt) {
auto related_information_it = pt.get_child("relatedInformation", boost::property_tree::ptree()); 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) for(auto it = related_information_it.begin(); it != related_information_it.end(); ++it)
related_informations.emplace_back(it->second); 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_)) { 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<TinyProcessLib::Process>( process = std::make_unique<TinyProcessLib::Process>(
filesystem::escape_argument(language_server), root_path.string(), filesystem::escape_argument(language_server), root_path.string(),
@ -218,7 +206,7 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang
process_id = process->get_id(); process_id = process->get_id();
} }
write_request( 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": { "workspace": {
"symbol": { "dynamicRegistration": false } "symbol": { "dynamicRegistration": false }
}, },
@ -286,6 +274,7 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang
} }
capabilities.hover = capabilities_pt->get<bool>("hoverProvider", false); capabilities.hover = capabilities_pt->get<bool>("hoverProvider", false);
capabilities.completion = static_cast<bool>(capabilities_pt->get_child_optional("completionProvider")); capabilities.completion = static_cast<bool>(capabilities_pt->get_child_optional("completionProvider"));
capabilities.completion_resolve = capabilities_pt->get<bool>("completionProvider.resolveProvider", false);
capabilities.signature_help = static_cast<bool>(capabilities_pt->get_child_optional("signatureHelpProvider")); capabilities.signature_help = static_cast<bool>(capabilities_pt->get_child_optional("signatureHelpProvider"));
capabilities.definition = capabilities_pt->get<bool>("definitionProvider", false); capabilities.definition = capabilities_pt->get<bool>("definitionProvider", false);
capabilities.type_definition = capabilities_pt->get<bool>("typeDefinitionProvider", false); capabilities.type_definition = capabilities_pt->get<bool>("typeDefinitionProvider", false);
@ -302,6 +291,7 @@ LanguageProtocol::Capabilities LanguageProtocol::Client::initialize(Source::Lang
capabilities.code_action = capabilities_pt->get<bool>("codeActionProvider", false); capabilities.code_action = capabilities_pt->get<bool>("codeActionProvider", false);
if(!capabilities.code_action) if(!capabilities.code_action)
capabilities.code_action = static_cast<bool>(capabilities_pt->get_child_optional("codeActionProvider.codeActionKinds")); capabilities.code_action = static_cast<bool>(capabilities_pt->get_child_optional("codeActionProvider.codeActionKinds"));
capabilities.code_action_resolve = capabilities_pt->get<bool>("codeActionProvider.resolveProvider", false);
capabilities.execute_command = static_cast<bool>(capabilities_pt->get_child_optional("executeCommandProvider")); capabilities.execute_command = static_cast<bool>(capabilities_pt->get_child_optional("executeCommandProvider"));
capabilities.type_coverage = capabilities_pt->get<bool>("typeCoverageProvider", false); capabilities.type_coverage = capabilities_pt->get<bool>("typeCoverageProvider", false);
} }
@ -383,7 +373,8 @@ void LanguageProtocol::Client::parse_server_message() {
if(Config::get().log.language_server) { if(Config::get().log.language_server) {
std::cout << "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<size_t>("id"); auto message_id = pt.get_optional<size_t>("id");
@ -402,8 +393,10 @@ void LanguageProtocol::Client::parse_server_message() {
} }
} }
else if(auto error = pt.get_child_optional("error")) { else if(auto error = pt.get_child_optional("error")) {
if(!Config::get().log.language_server) if(!Config::get().log.language_server) {
boost::property_tree::write_json(std::cerr, pt); JSON::write(std::cerr, pt);
std::cerr << '\n';
}
if(message_id) { if(message_id) {
auto id_it = handlers.find(*message_id); auto id_it = handlers.find(*message_id);
if(id_it != handlers.end()) { 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) if(Config::get().log.language_server)
std::cout << "Language client: " << content << std::endl; std::cout << "Language client: " << content << std::endl;
if(!process->write("Content-Length: " + std::to_string(content.size()) + "\r\n\r\n" + content)) { 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 &params) { void LanguageProtocol::Client::write_notification(const std::string &method, const std::string &params) {
LockGuard lock(read_write_mutex); 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) if(Config::get().log.language_server)
std::cout << "Language client: " << content << std::endl; std::cout << "Language client: " << content << std::endl;
process->write("Content-Length: " + std::to_string(content.size()) + "\r\n\r\n" + content); 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 &params) { void LanguageProtocol::Client::handle_server_request(size_t id, const std::string &method, const boost::property_tree::ptree &params) {
if(method == "workspace/applyEdit") { if(method == "workspace/applyEdit") {
std::promise<void> result_processed; std::promise<void> result_processed;
dispatcher->post([&result_processed, params] { bool applied = true;
dispatcher->post([&result_processed, &applied, params] {
ScopeGuard guard({[&result_processed] { ScopeGuard guard({[&result_processed] {
result_processed.set_value(); 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); workspace_edit = LanguageProtocol::WorkspaceEdit(params.get_child("edit"), current_view->file_path);
} }
catch(...) { catch(...) {
applied = false;
return; return;
} }
@ -559,8 +554,10 @@ void LanguageProtocol::Client::handle_server_request(size_t id, const std::strin
} }
} }
if(!view) { if(!view) {
if(!Notebook::get().open(document_edit.file)) if(!Notebook::get().open(document_edit.file)) {
applied = false;
return; return;
}
view = Notebook::get().get_current_view(); view = Notebook::get().get_current_view();
document_edits_and_views.emplace_back(DocumentEditAndView{&document_edit, 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(); buffer->end_user_action();
if(!view->save()) if(!view->save()) {
applied = false;
return; return;
}
} }
} }
}); });
result_processed.get_future().get(); 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<Gsv::Language> &language, std::string language_id_, std::string language_server_) Source::LanguageProtocolView::LanguageProtocolView(const boost::filesystem::path &file_path, const Glib::RefPtr<Gsv::Language> &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(); initialize();
} }
@ -746,7 +747,7 @@ void Source::LanguageProtocolView::write_notification(const std::string &method)
} }
void Source::LanguageProtocolView::write_did_open_notification() { 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<std::pair<std::string, std::string>> &params) { void Source::LanguageProtocolView::write_did_change_notification(const std::vector<std::pair<std::string, std::string>> &params) {
@ -759,7 +760,7 @@ void Source::LanguageProtocolView::rename(const boost::filesystem::path &path) {
dispatcher.reset(); dispatcher.reset();
Source::DiffView::rename(path); Source::DiffView::rename(path);
uri = filesystem::get_uri_from_path(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); client = LanguageProtocol::Client::get(file_path, language_id, language_server);
initialize(); initialize();
} }
@ -1277,111 +1278,105 @@ void Source::LanguageProtocolView::setup_navigation_and_refactoring() {
return; return;
auto edit_pt = results[index].second.get_child_optional("edit"); auto edit_pt = results[index].second.get_child_optional("edit");
if(!edit_pt) { if(capabilities.code_action_resolve && !edit_pt) {
std::promise<void> result_processed;
if(capabilities.execute_command) { std::stringstream ss;
auto command_pt = results[index].second.get_child_optional("command"); for(auto it = results[index].second.begin(); it != results[index].second.end(); ++it) {
if(command_pt) { ss << (it != results[index].second.begin() ? ",\"" : "\"") << JSON::escape_string(it->first) << "\":";
std::stringstream ss; JSON::write(ss, it->second, false, not_string_keys);
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;
}
} }
write_request("codeAction/resolve", ss.str(), [&result_processed, &edit_pt](const boost::property_tree::ptree &result, bool error) {
// TODO: disabled since rust-analyzer does not yet support numbers inside "" (boost::property_tree::write_json outputs all values with "") if(!error) {
// std::promise<void> result_processed; auto child = result.get_child_optional("edit");
// std::stringstream ss; if(child)
// boost::property_tree::write_json(ss, results[index].second, false); edit_pt = *child; // Make copy, since result will be destroyed at end of callback
// // ss.str() is enclosed in {}, and has ending newline }
// auto str = ss.str(); result_processed.set_value();
// if(str.size() <= 3) });
// return; result_processed.get_future().get();
// 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; if(edit_pt) {
try { LanguageProtocol::WorkspaceEdit workspace_edit;
workspace_edit = LanguageProtocol::WorkspaceEdit(*edit_pt, file_path); try {
} workspace_edit = LanguageProtocol::WorkspaceEdit(*edit_pt, file_path);
catch(...) { }
return; catch(...) {
} return;
}
auto current_view = Notebook::get().get_current_view(); auto current_view = Notebook::get().get_current_view();
struct DocumentEditAndView { struct DocumentEditAndView {
LanguageProtocol::TextDocumentEdit *document_edit; LanguageProtocol::TextDocumentEdit *document_edit;
Source::View *view; Source::View *view;
}; };
std::vector<DocumentEditAndView> document_edits_and_views; std::vector<DocumentEditAndView> document_edits_and_views;
for(auto &document_edit : workspace_edit.document_edits) { for(auto &document_edit : workspace_edit.document_edits) {
Source::View *view = nullptr; Source::View *view = nullptr;
for(auto it = views.begin(); it != views.end(); ++it) { for(auto it = views.begin(); it != views.end(); ++it) {
if((*it)->file_path == document_edit.file) { if((*it)->file_path == document_edit.file) {
view = *it; view = *it;
break; 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) if(current_view)
Notebook::get().open(current_view); Notebook::get().open(current_view);
for(auto &document_edit_and_view : document_edits_and_views) { for(auto &document_edit_and_view : document_edits_and_views) {
auto document_edit = document_edit_and_view.document_edit; auto document_edit = document_edit_and_view.document_edit;
auto view = document_edit_and_view.view; auto view = document_edit_and_view.view;
auto buffer = view->get_buffer(); auto buffer = view->get_buffer();
buffer->begin_user_action(); buffer->begin_user_action();
auto end_iter = buffer->end(); auto end_iter = buffer->end();
// If entire buffer is replaced // If entire buffer is replaced
if(document_edit->text_edits.size() == 1 && 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.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.line == end_iter.get_line() && document_edit->text_edits[0].range.end.character >= get_line_pos(end_iter)))) { (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); view->replace_text(document_edit->text_edits[0].new_text);
} }
else { else {
for(auto text_edit_it = document_edit->text_edits.rbegin(); text_edit_it != document_edit->text_edits.rend(); ++text_edit_it) { 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 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); 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) if(view != current_view)
view->get_buffer()->place_cursor(start_iter); view->get_buffer()->place_cursor(start_iter);
buffer->erase(start_iter, end_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); 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->insert(start_iter, text_edit_it->new_text);
}
} }
buffer->end_user_action();
if(!view->save())
return;
} }
}
buffer->end_user_action(); if(capabilities.execute_command) {
if(!view->save()) auto command_pt = results[index].second.get_child_optional("command");
return; 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(); hide_tooltips();
@ -1396,7 +1391,7 @@ void Source::LanguageProtocolView::setup_signals() {
get_buffer()->signal_insert().connect( get_buffer()->signal_insert().connect(
[this](const Gtk::TextIter &start, const Glib::ustring &text, int bytes) { [this](const Gtk::TextIter &start, const Glib::ustring &text, int bytes) {
std::pair<int, int> location = {start.get_line(), get_line_pos(start)}; std::pair<int, int> 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); false);
@ -1408,7 +1403,7 @@ void Source::LanguageProtocolView::setup_signals() {
} }
else if(capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::full) { else if(capabilities.text_document_sync == LanguageProtocol::Capabilities::TextDocumentSync::full) {
get_buffer()->signal_changed().connect([this]() { 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) { if(parameter_position == current_parameter_position || using_named_parameters) {
auto label = parameter_it->second.get<std::string>("label", ""); auto label = parameter_it->second.get<std::string>("label", "");
auto insert = label; auto insert = label;
auto documentation = parameter_it->second.get<std::string>("documentation", "");
std::string kind;
if(documentation.empty()) {
auto documentation_pt = parameter_it->second.get_child_optional("documentation");
if(documentation_pt) {
documentation = documentation_pt->get<std::string>("value", "");
kind = documentation_pt->get<std::string>("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()) { if(!using_named_parameters || used_named_parameters.find(insert) == used_named_parameters.end()) {
autocomplete->rows.emplace_back(std::move(label)); 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++; parameter_position++;
@ -1710,17 +1694,10 @@ void Source::LanguageProtocolView::setup_autocomplete() {
auto label = it->second.get<std::string>("label", ""); auto label = it->second.get<std::string>("label", "");
if(starts_with(label, prefix)) { if(starts_with(label, prefix)) {
auto detail = it->second.get<std::string>("detail", ""); auto detail = it->second.get<std::string>("detail", "");
auto documentation = it->second.get<std::string>("documentation", ""); LanguageProtocol::Documentation documentation(it->second);
std::string documentation_kind;
if(documentation.empty()) {
if(auto documentation_pt = it->second.get_child_optional("documentation")) {
documentation = documentation_pt->get<std::string>("value", "");
documentation_kind = documentation_pt->get<std::string>("kind", "");
}
}
boost::property_tree::ptree ptree; 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; ptree = it->second;
std::vector<LanguageProtocol::TextEdit> additional_text_edits; std::vector<LanguageProtocol::TextEdit> 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 if(kind >= 2 && kind <= 4 && insert.find('(') == std::string::npos) // If kind is method, function or constructor, but parentheses are missing
insert += "(${1:})"; insert += "(${1:})";
autocomplete->rows.emplace_back(std::move(label)); 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) { for(auto &snippet : *snippets) {
if(starts_with(snippet.prefix, prefix)) { if(starts_with(snippet.prefix, prefix)) {
autocomplete->rows.emplace_back(snippet.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<void(Tooltip & tooltip)> { autocomplete->set_tooltip_buffer = [this](unsigned int index) -> std::function<void(Tooltip & tooltip)> {
auto autocomplete = autocomplete_rows[index]; auto autocomplete = autocomplete_rows[index];
if(autocomplete.detail.empty() && autocomplete.documentation.empty() && !autocomplete.ptree.empty()) { if(capabilities.completion_resolve && autocomplete.detail.empty() && autocomplete.documentation.value.empty() && !autocomplete.ptree.empty()) {
try { std::stringstream ss;
std::stringstream ss; for(auto it = autocomplete.ptree.begin(); it != autocomplete.ptree.end(); ++it) {
boost::property_tree::write_json(ss, autocomplete.ptree, false); ss << (it != autocomplete.ptree.begin() ? ",\"" : "\"") << JSON::escape_string(it->first) << "\":";
// ss.str() is enclosed in {}, and has ending newline JSON::write(ss, it->second, false, not_string_keys);
auto str = ss.str();
if(str.size() <= 3)
return nullptr;
std::promise<void> 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<std::string>("detail", "");
autocomplete.documentation = result.get<std::string>("documentation", "");
if(autocomplete.documentation.empty()) {
if(auto documentation = result.get_child_optional("documentation")) {
autocomplete.kind = documentation->get<std::string>("kind", "");
autocomplete.documentation = documentation->get<std::string>("value", "");
}
}
}
result_processed.set_value();
});
result_processed.get_future().get();
}
catch(...) {
} }
std::promise<void> 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<std::string>("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 nullptr;
return [this, autocomplete = std::move(autocomplete)](Tooltip &tooltip) mutable { return [this, autocomplete = std::move(autocomplete)](Tooltip &tooltip) mutable {
if(language_id == "python") // Python might support markdown in the future if(language_id == "python") // Python might support markdown in the future
tooltip.insert_docstring(autocomplete.documentation); tooltip.insert_docstring(autocomplete.documentation.value);
else { else {
if(!autocomplete.detail.empty()) { if(!autocomplete.detail.empty()) {
tooltip.insert_code(autocomplete.detail, language); tooltip.insert_code(autocomplete.detail, language);
tooltip.remove_trailing_newlines(); tooltip.remove_trailing_newlines();
} }
if(!autocomplete.documentation.empty()) { if(!autocomplete.documentation.value.empty()) {
if(tooltip.buffer->size() > 0) if(tooltip.buffer->size() > 0)
tooltip.buffer->insert_at_cursor("\n\n"); tooltip.buffer->insert_at_cursor("\n\n");
if(autocomplete.kind == "plaintext" || autocomplete.kind.empty()) if(autocomplete.documentation.kind == "plaintext" || autocomplete.documentation.kind.empty())
tooltip.insert_with_links_tagged(autocomplete.documentation); tooltip.insert_with_links_tagged(autocomplete.documentation.value);
else if(autocomplete.kind == "markdown") else if(autocomplete.documentation.kind == "markdown")
tooltip.insert_markdown(autocomplete.documentation); tooltip.insert_markdown(autocomplete.documentation.value);
else 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<Language
dispatcher.post([this, diagnostics = std::move(diagnostics), last_count]() mutable { dispatcher.post([this, diagnostics = std::move(diagnostics), last_count]() mutable {
if(last_count != update_diagnostics_async_count) if(last_count != update_diagnostics_async_count)
return; return;
std::pair<std::string, std::string> range; std::stringstream diagnostics_ss;
std::string diagnostics_string; for(auto it = diagnostics.begin(); it != diagnostics.end(); ++it) {
for(auto &diagnostic : diagnostics) { if(it != diagnostics.begin())
range = make_range({diagnostic.range.start.line, diagnostic.range.start.character}, {diagnostic.range.end.line, diagnostic.range.end.character}); diagnostics_ss << ",";
std::vector<std::pair<std::string, std::string>> diagnostic_params = {range}; JSON::write(diagnostics_ss, it->ptree, false, not_string_keys);
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 std::pair<std::string, std::string> 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 start = get_buffer()->begin();
auto end = get_buffer()->end(); auto end = get_buffer()->end();
range = make_range({start.get_line(), get_line_pos(start)}, {end.get_line(), get_line_pos(end)}); range = make_range({start.get_line(), get_line_pos(start)}, {end.get_line(), get_line_pos(end)});
} }
std::vector<std::pair<std::string, std::string>> params = {range, {"context", '{' + to_string({{"diagnostics", '[' + diagnostics_string + ']'}, {"only", "[\"quickfix\"]"}}) + '}'}}; std::vector<std::pair<std::string, std::string>> 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 { thread_pool.push([this, diagnostics = std::move(diagnostics), params = std::move(params), last_count]() mutable {
if(last_count != update_diagnostics_async_count) if(last_count != update_diagnostics_async_count)
return; return;

16
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 { class Diagnostic {
public: public:
class RelatedInformation { class RelatedInformation {
@ -74,6 +82,7 @@ namespace LanguageProtocol {
std::string code; std::string code;
std::vector<RelatedInformation> related_informations; std::vector<RelatedInformation> related_informations;
std::map<std::string, std::set<Source::FixIt>> quickfixes; std::map<std::string, std::set<Source::FixIt>> quickfixes;
boost::property_tree::ptree ptree;
}; };
class TextEdit { class TextEdit {
@ -107,6 +116,7 @@ namespace LanguageProtocol {
TextDocumentSync text_document_sync = TextDocumentSync::none; TextDocumentSync text_document_sync = TextDocumentSync::none;
bool hover = false; bool hover = false;
bool completion = false; bool completion = false;
bool completion_resolve = false;
bool signature_help = false; bool signature_help = false;
bool definition = false; bool definition = false;
bool type_definition = false; bool type_definition = false;
@ -119,13 +129,12 @@ namespace LanguageProtocol {
bool document_range_formatting = false; bool document_range_formatting = false;
bool rename = false; bool rename = false;
bool code_action = false; bool code_action = false;
bool code_action_resolve = false;
bool execute_command = false; bool execute_command = false;
bool type_coverage = false; bool type_coverage = false;
bool use_line_index = false; bool use_line_index = false;
}; };
std::string escape_text(std::string text);
class Client { class Client {
Client(boost::filesystem::path root_path, std::string language_id, const std::string &language_server); Client(boost::filesystem::path root_path, std::string language_id, const std::string &language_server);
boost::filesystem::path root_path; boost::filesystem::path root_path;
@ -253,8 +262,7 @@ namespace Source {
struct AutocompleteRow { struct AutocompleteRow {
std::string insert; std::string insert;
std::string detail; std::string detail;
std::string documentation; LanguageProtocol::Documentation documentation;
std::string kind;
/// CompletionItem for completionItem/resolve /// CompletionItem for completionItem/resolve
boost::property_tree::ptree ptree; boost::property_tree::ptree ptree;
std::vector<LanguageProtocol::TextEdit> additional_text_edits; std::vector<LanguageProtocol::TextEdit> additional_text_edits;

2
src/usages_clang.cpp

@ -729,7 +729,7 @@ void Usages::Clang::write_cache(const boost::filesystem::path &path, const Clang
return; return;
tmp_file /= ("jucipp" + std::to_string(get_current_process_id()) + path_str); 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) { if(stream) {
try { try {
boost::archive::text_oarchive text_oarchive(stream); boost::archive::text_oarchive text_oarchive(stream);

20
src/window.cpp

@ -11,6 +11,7 @@
#include "filesystem.hpp" #include "filesystem.hpp"
#include "grep.hpp" #include "grep.hpp"
#include "info.hpp" #include "info.hpp"
#include "json.hpp"
#include "menu.hpp" #include "menu.hpp"
#include "notebook.hpp" #include "notebook.hpp"
#include "project.hpp" #include "project.hpp"
@ -253,7 +254,7 @@ void Window::configure() {
if(scheme) if(scheme)
style = scheme->get_style("def:note"); style = scheme->get_style("def:note");
else { 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(); 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"); Terminal::get().print(" \e[32mcreated\e[m\n");
} }
else 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", []() { menu.add_action("file_new_project_cpp", []() {
@ -442,7 +443,7 @@ void Window::set_menu_actions() {
Terminal::get().print(" \e[32mcreated\e[m\n"); Terminal::get().print(" \e[32mcreated\e[m\n");
} }
else 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", []() { menu.add_action("file_new_project_rust", []() {
@ -471,7 +472,7 @@ void Window::set_menu_actions() {
Terminal::get().print(" \e[32mcreated\e[m\n"); Terminal::get().print(" \e[32mcreated\e[m\n");
} }
else 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()) { if(auto view = Notebook::get().get_current_view()) {
auto path = Dialog::save_file_as(view->file_path); auto path = Dialog::save_file_as(view->file_path);
if(!path.empty()) { if(!path.empty()) {
std::ofstream file(path, std::ofstream::binary); std::ofstream file(path, std::ios::binary);
if(file) { if(file) {
file << view->get_buffer()->get_text().raw(); file << view->get_buffer()->get_text().raw();
file.close(); file.close();
@ -2318,7 +2319,14 @@ void Window::save_session() {
window_pt.put("height", height); window_pt.put("height", height);
root_pt.add_child("window", window_pt); 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(...) { catch(...) {
} }

6
tests/CMakeLists.txt

@ -90,7 +90,11 @@ if(BUILD_TESTING)
add_test(language_protocol_client_test language_protocol_client_test) add_test(language_protocol_client_test language_protocol_client_test)
add_executable(language_protocol_server_test language_protocol_server_test.cpp) 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_OBJECTS:test_stubs>)
target_link_libraries(json_test juci_shared)
add_test(json_test json_test)
endif() endif()
if(BUILD_FUZZING) if(BUILD_FUZZING)

58
tests/json_test.cpp

@ -0,0 +1,58 @@
#include "json.hpp"
#include <glib.h>
#include <iostream>
#include <sstream>
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);
}
}

32
tests/language_protocol_server_test.cpp

@ -1,5 +1,5 @@
#include "json.hpp"
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <iostream> #include <iostream>
#ifdef _WIN32 #ifdef _WIN32
@ -7,32 +7,6 @@
#include <io.h> #include <io.h>
#endif #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() { int main() {
#ifdef _WIN32 #ifdef _WIN32
_setmode(_fileno(stdout), _O_BINARY); _setmode(_fileno(stdout), _O_BINARY);
@ -472,7 +446,7 @@ int main() {
"id": "5", "id": "5",
"result": [ "result": [
{ {
"uri": "file://)" + escape_text(file_path.string()) + "uri": "file://)" + JSON::escape_string(file_path.string()) +
R"(", R"(",
"range": { "range": {
"start": { "start": {
@ -486,7 +460,7 @@ int main() {
} }
}, },
{ {
"uri": "file://)" + escape_text(file_path.string()) + "uri": "file://)" + JSON::escape_string(file_path.string()) +
R"(", R"(",
"range": { "range": {
"start": { "start": {

Loading…
Cancel
Save