Compare commits

...

397 Commits

Author SHA1 Message Date
Jørgen Lien Sellæg a2e4868893 Merge branch 'master' into python-refactor 7 months ago
Jørgen Lien Sellæg 1223a72908 Merge branch 'master' into python-refactor 7 months ago
eidheim 14b3317eee Select all now works in terminal as well 8 months ago
eidheim 1f94ae9a80 Added comment char for prolog 9 months ago
eidheim eb9fc4d9e6 Fixed isysroot settings on MacOS 9 months ago
eidheim e21a641d26 Fixed build issue on MacOS 10 months ago
eidheim bd2cb0a288 Escape key now closes tooltips 10 months ago
eidheim 1944bf78bb v1.8.1 11 months ago
eidheim 0984889b98 Fixed boost cmake warning 11 months ago
eidheim 332bf8b627 Updated cmake_minimum_required 11 months ago
eidheim d473c95af0 Fixed fontconfig cmake search and libclang isysroot issue on MacOS 11 months ago
eidheim 9b0e4555ab Updated libclangmm 11 months ago
eidheim 617e5d96cb Minor improvement to space drawing code 1 year ago
eidheim 3be4752a64 Fixed space drawing 1 year ago
eidheim 4dc64b8cce Added cmake check for gtksourceviewmm-4 1 year ago
eidheim 14f37e4544 Fixed gtk_source_search functions for all gtksourceview versions 1 year ago
eidheim 261660c715 Added support for new space drawer in gtksourceview 1 year ago
eidheim de56bb1111 Fixed control-click to run source files on Linux 1 year ago
eidheim f4a1498c99 Reversed control-click to run source files on Linux for the time being 1 year ago
eidheim 54400ccf83 Cleanup of clang language server commits 1 year ago
Shivang Gangadia dfd4ef7f83 Restored C++14, made C++ language server access through symbolic linnk. Updated documentation. 1 year ago
Shivang Gangadia ffdf01c1ab Fixed C++20 ambiguous operator overload warning 1 year ago
Shivang Gangadia aedd819fbe Added support for optionally using LSP for c-like languages. Also bumped c++ version to 20 for project 1 year ago
eidheim a8163f0d84 Cleanup of menu inconsistency fix 1 year ago
eidheim c7dbbcd962 Merge branch 'juci_menu_inconsistent' of https://gitlab.com/shivangsgangadia/jucipp-clangd-support 1 year ago
eidheim efc98b5b13 Space between lines cleanup 1 year ago
eidheim 27a4c47f5a Merge branch 'space_between_lines_option' of https://gitlab.com/shivangsgangadia/jucipp-clangd-support 1 year ago
Shivang Gangadia b4d6286460 Removed trailing app menu variable 1 year ago
Shivang Gangadia a5cf0529d5 Moved app menu to the menu bar to ensure consistent appearance across desktops. 1 year ago
Shivang Gangadia 69cec55f5a Added option to configure spacing between lines in editor 1 year ago
Shivang Gangadia 50adce1c4a Added support for configuration of space between lines in editor. Updated IsClangType to use a set. 1 year ago
eidheim 1289f45e59 Can now use control/command click to run source files directly from the directory view 1 year ago
eidheim 20e74bd468 Updated libclangmm 1 year ago
eidheim 9d2604aa4b Updated libclangmm 1 year ago
eidheim 37e5f049d9 Updated libclangmm 1 year ago
eidheim cc3ecab010 Added project.open_with_default_application to preferences 2 years ago
eidheim 5bc8dde6de Language protocol: added sorting for rename items in case server does not return sorted changes 2 years ago
eidheim 2bff28a844 Added possibility to label run commands in the commands.json 2 years ago
eidheim fe16979289 ctags: location.symbol and location.source should now correspond with respect to escaping 2 years ago
eidheim f6aeb314f0 Added workaround for Invalid cross-device link error 2 years ago
eidheim 9e080d0f77 Improved indentation when inserting snippets 2 years ago
eidheim fccfd34771 Fixed boost::archive exception 2 years ago
eidheim 86b77fcff1 Replaced deprecated boost::filesystem::copy_option 2 years ago
eidheim 469d8c8cb0 Replaced deprecated boost::filesystem::iterator::no_push 2 years ago
eidheim 0889806b52 Removed use of deprecated boost::property_tree::get_child 2 years ago
eidheim 284cc068ff No longer considers keys pressed with control/command active as keyvals 2 years ago
Sven Otto ee90cfe3df Update install.md to cover OpenSuSE Leap as well, when i tried to compile it was missing some dependencies, now it builds successfully. You would probably have to check as there is still a Warning shown when running the IDE: "Could not find a supported Build System" 2 years ago
eidheim 9305e35f7a Updated LICENSE 2 years ago
eidheim 91425d9c74 Cleanup of clang resource directory lookup 2 years ago
eidheim 546d5810be Merge branch 'clang_resource_dir_fix' of https://gitlab.com/doe300/jucipp 2 years ago
doe300 481a728a59 Fix Clang resource directory lookup for Fedora with LLVM 17+ 2 years ago
eidheim 2b7fc1e7d6 Slight improvement to paste indentation for Python buffers 2 years ago
eidheim 043a105dc3 Search entry histories are now updated correctly 2 years ago
eidheim de7763e555 Fixed CMake and Meson project detection for dos newlines 2 years ago
eidheim f8cb78897a Updated CMake version 2 years ago
eidheim 23ebfaa509 v1.8.0 2 years ago
eidheim ac3f17218a Updated cmake version when creating C/C++ projects 2 years ago
eidheim dd2f762924 Added support for prettier version 3 2 years ago
eidheim bdbcd22ca4 Updated submodules 2 years ago
eidheim 43f4e9b30b Improved version extraction from clang_getClangVersion() 2 years ago
eidheim b269119622 Minor addition to source.tooltip_top_offset setting 2 years ago
eidheim f6f2610098 Made it possible to adjust tooltip top offset in configuration item source.tooltip_top_offset 2 years ago
eidheim ba3ee99d11 Added another attempt to find libclang resource path 3 years ago
eidheim 66b98f18f4 Platform independent way to find clang resource path 3 years ago
eidheim 70b01ba867 Further improvements to libclang's resource path 3 years ago
eidheim ddd53d98fb Improved resource path detection on systems without clang/clang++ installed 3 years ago
eidheim e57ede474c Updated rust language server instructions 3 years ago
eidheim 8f4fafd23d Fixed process_test for systems without bash installed 3 years ago
eidheim a5858199df Fixed rust-analyzer installation 3 years ago
eidheim 96383e11f9 Updated libclangmm submodule 3 years ago
eidheim 6cbaf89802 Cleanup of state handling and parsing in order to limit while loops with sleeps, and removed standard c++ version from libclang arguments due to C header crashes 3 years ago
eidheim c2d31f20ff Fixed autocomplete on for instance '.' after newline 3 years ago
eidheim 5370e68479 Added support for compile_commands.json file in project root folder 3 years ago
eidheim 082cee92ca Cleanup of CompileCommands class 3 years ago
eidheim 6603f52be9 Minor cleanup of clang -resource-dir setting 3 years ago
eidheim 8913a4a485 Fixed clang -resource-dir setting for libclang 3 years ago
eidheim ef96031022 Fixed scrolling to selected element in selection dialogs 3 years ago
eidheim 1eb1209f4e Tooltip workaround no longer needed for gtk versions 3.24.38 and above. Also updated libclangmm submodule 3 years ago
eidheim ba9203959a Cleanup of GTK_VERSION_GT_MICRO replacement macro 3 years ago
eidheim cbda10a798 Fixed autocomplete initialization on text removal 3 years ago
eidheim 7e15a8a398 Fixed GTK_VERSION_GT_MICRO check 3 years ago
eidheim 7ae617d264 Cleanup and corrections to Window::set_title() and its usages: title is now correctly set when opening juCi++ for the first time, and when closing a folder 3 years ago
Jørgen Lien Sellæg 73977fa1ad set main window title based on open directory in tree view 3 years ago
eidheim 42c1103001 Added workaround MacOS missing resource-dir 3 years ago
eidheim 40536fefc8 Updated libclangmm submodule 3 years ago
eidheim 1c82115576 Removed unused variables 3 years ago
eidheim c43b5fff96 Added workaround for the crash described in https://gitlab.gnome.org/GNOME/gtk/-/issues/5593 3 years ago
eidheim 0e4fc91c48 Language client: corrected default workspace/configuration response 3 years ago
eidheim 18f2ebceff Merge https://gitlab.com/cppit/jucipp 3 years ago
eidheim 89efcacb4f Language client: improved support for autocomplete text edits 3 years ago
Jørgen Lien Sellæg fd836b0ecf skip very long lines as they probably arent the correct path and it fixes a crash when the regex match is to big 3 years ago
eidheim 9d902e2354 Made the entry boxes, for instance used in Find, larger 3 years ago
eidheim a7483981bf Directory view: unknown files are now made italic even when not showing a git repository 3 years ago
eidheim 85405c36bc Cleanup of Source::guess_language 3 years ago
eidheim 44e31bc0c4 Merge branch 'bak_in_detection' of https://gitlab.com/doe300/jucipp 3 years ago
eidheim 45c9f5b96d Merge branch 'xml_prologue' of https://gitlab.com/doe300/jucipp 3 years ago
doe300 4307048ed8 Implemented removal of .bak .in file extensions for language detection 3 years ago
doe300 c021fad812 Added detection of XML-derived file types by peeking the file contents 3 years ago
eidheim e9044e386b Language client: now adds diagnostics to codeAction requests, and supports adding configuration responses. Also now uses buffer completions when completion is not provided by server. 3 years ago
eidheim 1e8f4681c5 Updated tiny-process-library submodule 3 years ago
eidheim f212fa5936 Improved newline support for markdown annotated tooltips: two spaces followed by newline will now produce newline within a paragraph 3 years ago
eidheim e2201df1f7 Replaced unnecessary use of get_source_buffer() with get_buffer() 3 years ago
eidheim d11946c75c No longer copies indentation when copying within one line 3 years ago
eidheim 88f8934d32 Updated tiny-process-library submodule 3 years ago
eidheim e40ae4bfef Removed python specific paste() code 3 years ago
eidheim 50291c2fbe Slight cleanup of copy and cut text 3 years ago
eidheim c6d6cfec43 Improved paste indentation by coping or cutting with starting indentation if possible 3 years ago
eidheim c2b824e51e Force newline at end of file on prettier style format 3 years ago
eidheim e566cb8487 Temporary solution to enable prettier plugins: add a file, for instance .prettier-sql, to enable prettier for sql files in that directory and its subdirectories 3 years ago
eidheim d28dcf6f7a Slight improvement of Natural::compare, for instance: t1.txt now comes after t.txt 3 years ago
eidheim 6def62ad21 Added line comment for markdown 3 years ago
eidheim bfbb7a3ab1 Updated tiny-process-library submodule 3 years ago
eidheim d0c0107afc Merge branch 'flatpak' of https://gitlab.com/doe300/jucipp 3 years ago
eidheim 2fe8f1cb46 Fixed llvm 15 crash when trying to parse C header as C++ header in a C project 3 years ago
eidheim bab7e16a90 Temporary fix for llvm 15 where the TranslationUnit cursor kind was changed from 300 to 350 3 years ago
eidheim 20530fab7e Made it possible to enable dark MacOS titlebar through the C definition MACOS_DARK_TITLEBAR 3 years ago
eidheim 0ecf381ab5 Added code, although deactivated currently, to make title bar dark in MacOS 3 years ago
eidheim 1f2aef713d Fixed setting icon on MacOS 3 years ago
eidheim 5f26ec870e Corrected placement of tooltips 3 years ago
doe300 69a30f271d Flatpak, use host system headers and run external programs outside of sandbox 3 years ago
doe300 b9fcb0195b Add flatpak manifest 3 years ago
eidheim b8bda75048 Added instructions on how to use the pyright language server 3 years ago
eidheim 692f3de1f0 v1.7.2 3 years ago
eidheim bd930063c2 Fixed Project::CMakeBuild::is_valid for MSYS2 3 years ago
eidheim 69442af3e9 Added fstream include to filesystem.hpp as suggested in https://gitlab.com/cppit/jucipp/-/issues/453 to fix FreeBSD compilation 3 years ago
eidheim 5611ff5b21 Ignore arguments in compile commands after and including -- 3 years ago
eidheim 847dcbf424 Merge branch 'stop-ignoring-some-compile-commands' of https://gitlab.com/cppit/jucipp 3 years ago
eidheim 729c783d69 Updated libclangmm 3 years ago
eidheim d703454f5f C/C++: create build folder, if not already created, prior to getting run arguments in order to extract executable from build 3 years ago
Jørgen Lien Sellæg 99388cc472 fixes #452. I don't know why the compile commands insterts these? 3 years ago
eidheim de242f310b Minor cleanup of CMake::get_executable 3 years ago
eidheim 350da301ec Cleanup and slight improvement of cmake_file_api code 3 years ago
eidheim 9ee1d4dade Merge branch 'cmake_file_api' of https://gitlab.com/doe300/jucipp 3 years ago
doe300 d9fc483332 Add missing headers required for GCC 11 compilation 4 years ago
doe300 6c4de943ec Use CMake file API to find executables for source file 4 years ago
Jørgen Lien Sellæg 24ff43d654 add svelte files 4 years ago
eidheim d9db8cb2f8 Temporarily disable warnings as errors due to GCC bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105329 4 years ago
Jørgen Sverre Lien Sellæg e81b8f78b4 use space before comment 4 years ago
Jørgen Sverre Lien Sellæg 8f3761cb7a update license 4 years ago
eidheim a8021a1865 Minor cleanup of Source::guess_language 4 years ago
doe300 a07a4605ca Add support for Groovy language and Jenkins files 4 years ago
eidheim 90af1f14cf Slightly safer Natural::compare() 4 years ago
eidheim 103e5519bc Added natural sort to Find File 4 years ago
eidheim 625cecd305 Updated tiny-process-library submodule 4 years ago
eidheim 3e1eca11fc Improved build error messages 4 years ago
eidheim ec1d82e59b Fixed lldb_test on MacOS 4 years ago
eidheim 577c832a61 Update libclangmm 4 years ago
eidheim ae669bfcdf Slightly improved C++ include fixits 4 years ago
eidheim 3b0412e870 Fixed deadlock when inserting snippets when using autocomplete keys 4 years ago
eidheim 42b3f00980 Added extra checks for valid CMake builds, and fixed previous check for correct project path in CMakeCache.txt 4 years ago
eidheim 686649e67d Dollar sign now adds dollar signs around selected text in latex buffers 4 years ago
eidheim 77cab2ccbe Updated libclangmm 4 years ago
eidheim 485a7f5eb2 Fixed latexindent selection formatting 4 years ago
eidheim d3d54b48b6 Added -m switch to latexindent 4 years ago
eidheim fedb5c2c96 Added support for latexindent 4 years ago
vazub 58abd35e4e NetBSD build instructions 4 years ago
vazub 8d03762695 Build for NetBSD and include proper RPATH 4 years ago
eidheim 228056820e Language protocol: no longer show tooltips when no completion is selected 4 years ago
eidheim eb4b1eaf8c Language client: cleanup of client capabilities 4 years ago
eidheim 00a696d19c Language client: now supports multiple declarations and type declarations 4 years ago
eidheim 56a655f096 Changed preference item source.cleanup_whitespace_characters into two seperate items: add_missing_newline_at_end_of_file_on_save and remove_trailing_whitespace_characters_on_save 4 years ago
eidheim 0a3102f13e Language client: added pyright workarounds 4 years ago
eidheim cf695def6a Added yapf style format for python files when not using language server or when language server does not support style formatting, and no longer use ranged formatting on save 4 years ago
eidheim 1a1cffb039 Minor fix to toggle zen mode 4 years ago
eidheim 85f40a76d2 Language client: removed unecessary event flush 4 years ago
eidheim aed4e5723a Language client: added workaround for pyright, and cleanup of rename(). Also added log mutex for cleaner log output. 4 years ago
eidheim be5c8228d5 Language client: various cleanups 4 years ago
eidheim a99755b0e9 Language client: improved error message on JSON parse error 4 years ago
eidheim 06b9f63ad5 Language client: no longer save after code action, and saving after rename is now performed after all rename edits 4 years ago
eidheim f119f17341 Language client: accept requests from server with text ids 4 years ago
eidheim 46bc3de9d0 Language client: added support for RenameFile in textDocument/rename response 4 years ago
eidheim d66c8e16d6 No longer move cursor up/down paragraph when using control+alt+up/down 4 years ago
eidheim 53809dc93e Two or more menu actions can now have the same keybinding 4 years ago
eidheim 561ae473ba Simplified getting default Gtk::Application object 4 years ago
eidheim a8b2d1037b Updated LICENSE 4 years ago
eidheim b3b5182258 v1.7.1 4 years ago
eidheim 8b134a091a Added another doxygen test 4 years ago
eidheim d5001438c8 Fixes #446 : buffer overread in Tooltip::insert_doxygen(), and added _GLIBCXX_ASSERTIONS to tests 4 years ago
eidheim efb6a29e0c Can now unset keybindings in Preferences 4 years ago
eidheim 4525a596e5 Added Toggle Status to Window menu 4 years ago
eidheim 7d55b1f374 Removed dashed lines surrounding tooltips on some gtk themes 4 years ago
eidheim ba995b168a Tooltips: now adds scrolling to all tooltip windows that are placed partially outside of screen 4 years ago
eidheim d65eb9eaf2 Fixed potential compilation error on MSYS2 4 years ago
eidheim 1a2263f6db Preference item search_for_selection now also applies to Find Pattern 4 years ago
eidheim 5332fa624f Fixed indentation on enter after for instance {\n 1,\n 2,\n} 4 years ago
eidheim c3b62cb647 v1.7.0 4 years ago
eidheim 140e40b300 Reversed save modified dialog change: answering no now again closes buffer 4 years ago
eidheim c1525c0296 Store find_pattern options in last_session.json 4 years ago
eidheim 2035dba47e Added Close Other Files to File menu, and fixed save_modified_dialog when choosing No 4 years ago
eidheim c837f49092 Store search options in last_session.json 4 years ago
eidheim 56d77ef32d Added Switch File Type to File menu, and moved Find File from Source to File menu 4 years ago
eidheim 7f62bd6802 File path in status bar is now relative to opened folder if file is within this folder 4 years ago
eidheim 4d18245743 Language client: corrected workspace/workspaceFolders response 4 years ago
eidheim 709e2aeb18 Language client: additional support for workspaceFolders 4 years ago
eidheim 462534cb64 Language client: added support for workspaceFolders (needed by pyright) 4 years ago
eidheim bad362b248 Added preference instructions in python language server setup 4 years ago
eidheim 2ba8fea018 Slight modification of new C/C++ hello world source files 4 years ago
eidheim a8169628c2 Added compile flags -Wstring-conversion and -Wliteral-conversion for clang++ 4 years ago
eidheim 216c7e3a0a Added support for docstring :: code blocks 4 years ago
eidheim 24c795b41f Language client: added codeAction.resolveSupport to initialize request 4 years ago
eidheim 6cb1094ee5 Suggest using python-lsp-server instead of python-language-server, since the latter is no longer updated 4 years ago
eidheim 91efe29a52 Language client: added completionItem.resolveSupport to initialize request 4 years ago
eidheim 0a28c1822f Removed unnecessary mutex from GenericView 4 years ago
eidheim b9c2e2a019 Added spellcheck_all flag to html and xml 4 years ago
eidheim 584eb1e64e Added additional link to docs/language_servers.md in README 4 years ago
eidheim a4db3cc621 Autocomplete is now run without threads in GenericView 4 years ago
eidheim df51554ca0 Cleanup of spellcheck_all boolean used to activate spellchecking on for instance markdown text 4 years ago
eidheim 80cb893f0f Further cleanup of is_token_char 4 years ago
eidheim 91c1d2716d Cleanup of Source::BaseView::is_token_char 4 years ago
eidheim 821b5e845e Updated installation doc: universal-ctags is now an official homebrew package 4 years ago
eidheim d86355b88f Removed unnecessary line 4 years ago
eidheim 1f5973b5ff No longer shows prettier missing message in terminal when opening for instance preferences 4 years ago
eidheim 0a989504db Fixed complete insertion of dollar signs in generic views 4 years ago
eidheim ce99fe4f4b Added dialog icons and added rounded edges on message dialog 4 years ago
eidheim 2305dfb42b Reshow Rust and rust-analyzer dialogs if installation was cancelled 4 years ago
eidheim 9e534f053d Improved spellcheck inside '' 4 years ago
eidheim bc743b4a47 Language server on MSYS2: fixed URIs for Windows paths 4 years ago
eidheim dc57a8749c Added version_compare and adds parallel to default value in preference item project.cmake.compile_command 4 years ago
eidheim da8b1f7f98 Minor cleanup of rustup PATH modification 4 years ago
eidheim b67b919394 Additional utility tests 4 years ago
eidheim 8bf8148c63 Fixed filesystem_test for MSYS2 4 years ago
eidheim c3afe1009b Added filesystem::is_executable 4 years ago
eidheim af652278c3 Further improvements to MSYS2 Rust installer 4 years ago
eidheim b2aade877b Fixed Rust installation on MSYS2 4 years ago
eidheim d18e26ba80 Improved entry in selection dialog on MacOS 4 years ago
eidheim 1dd8cb3e36 Added Rust installer 4 years ago
eidheim a0a587b8fd Added cancellation dialog to rust-analyzer installation 4 years ago
eidheim 09d569873f Cleanup of rust-analyzer installation 4 years ago
eidheim e9b5548b57 Improved installer for rust-analyzer 4 years ago
eidheim 37341bf12e Added automatic installation of rust-analyzer through rustup if component is found 4 years ago
eidheim 7e56744972 Language protocol: client message logs are now formatted 4 years ago
eidheim 9fe68e4891 Improvement of error message when background prettier process exits prematurely 4 years ago
eidheim 6a5b74b87b Added error message if background prettier process exited prematurely 4 years ago
eidheim aa44fc0a50 Reset stdout_buffer and related variables if prettier background process has to be recreated due to failure 4 years ago
eidheim 5adfeaa1c1 Use stringstream reconstruction instead of using clear() 4 years ago
eidheim 257668730c Improvement to prettier library output parsing 4 years ago
eidheim d5eabfd4e7 Made use of prettier library when possible to speed up prettier formatting 4 years ago
eidheim 10a1ccba3c Reduced width of C/C++ completions slightly 4 years ago
eidheim 603835abdc Added style class to selection dialogs 4 years ago
eidheim 40905d54ed Made similar symbol tags more visibile when using dark themes 4 years ago
eidheim 8fb81c11d9 Improved placement of tooltips: now places tooltips below cursor if no space above or right 4 years ago
eidheim db63cedcd6 Improved positioning of tooltips through more exactly computing the size of the tooltip to be shown 4 years ago
eidheim 0cefe148fa Made use of the nlohmann/json library due to lacking JSON capabilities in Boost::PropertyTree 4 years ago
eidheim 3abc46c17b Language client: always add params object when sending notifications (gopls requires this) 5 years ago
eidheim c7340b709d Added JSON::write_json, and some various cleanup 5 years ago
eidheim 32b6436fe8 Minor cleanup of show_or_hide() 5 years ago
eidheim 6d46110907 Added support for Julia's end keywork in show_or_hide() 5 years ago
eidheim 30ce9ca3a4 Added file path escaping to language_protocol_server_test.cpp 5 years ago
eidheim 2b9491565f Language client: now supports CompletionItem.additionalTextEdits 5 years ago
eidheim 66abb09262 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. 5 years ago
eidheim 149e82a279 Additional language client tests 5 years ago
eidheim e6069f00bc Minor improvement to extend_selection(), and correction of cursor movement on functions with no arguments 5 years ago
eidheim c89238b9e5 Improvements to indentaion detection 5 years ago
eidheim 4e121ffd00 Improved C/C++ include fixits 5 years ago
eidheim a95cb94b79 Language client: move cursor forward if no arguments in completed function and if cursor is still inside () 5 years ago
eidheim b6e8e5d441 Language client: added completionItem/resolve request to fetch details and documentation of completion items 5 years ago
eidheim 70d9818772 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 5 years ago
eidheim 2e34b4ba6c Updated libclangmm 5 years ago
eidheim c51ad74811 Added link to documentation on Language Protocol extension offsetEncoding 5 years ago
eidheim 0c42dd0fa9 Language client: use proper utf-16 offsets when getting iters and offsetEncoding is not set to utf-8 5 years ago
eidheim be5e36627d Language client: improved support for both UTF-16 offsets and offsetEncoding set to utf-8 5 years ago
eidheim 5aeb065f4b Language client: cleanup of embolden_token 5 years ago
eidheim 5164f3f956 Another fix to MSYS2 test failure 5 years ago
eidheim 8acf82bb75 Removed unnecessary .raw() uses 5 years ago
eidheim 47c38ef227 Another fix to MSYS2 test failure 5 years ago
eidheim 300cac6589 Fixed MSYS2 test failure 5 years ago
eidheim aa91e38bfa Added language protocol tests 5 years ago
eidheim 31d86b22ba Minor cleanup of extend_selection() 5 years ago
eidheim bfff0bba2b Tab width set to 4 in terminal as well 5 years ago
eidheim 34eadaa2e4 Minor improvement to extend selection for non-bracket languages 5 years ago
eidheim 9a67a7557e Improved extend selection for non-bracket languages like Python and Julia 5 years ago
eidheim e63b4e974d Cleanup of source language checks 5 years ago
eidheim 2424113b9f Language client: made use of Source::is_js 5 years ago
eidheim 2b5d778284 Language client: further improvement to is_possible_jsx_property 5 years ago
eidheim 47caf05f94 Language client: improved is_possible_jsx_property 5 years ago
eidheim 20a046db69 Cleanup of autocompletion run checks 5 years ago
eidheim aa1019093d Fixed autocomplete at start of buffer 5 years ago
eidheim c8d9733385 Changed the timeouts in the language client 5 years ago
eidheim c6bf84bd90 Cleanup of on_motion_notify_event 5 years ago
eidheim 68202c648a Added warning messages on missing go or julia language server 5 years ago
eidheim 967ce0cf30 Fixed minor typo in Julia language server install instructions 5 years ago
eidheim 68fe6bab1a Added compile and run support for Julia 5 years ago
eidheim 9c8f105780 Added support for Go projects 5 years ago
eidheim de43494f25 MacOS: fixed selection marks when selecting text through mouse drag 5 years ago
eidheim a0dfe0a055 Cleanup of extend_selection() 5 years ago
eidheim 50299e7b08 Renamed SearchView to CommonView 5 years ago
eidheim cb2bc83044 Extend selection now works on HTML and JSX 5 years ago
eidheim 51c619f4bd Copying from diff buffer now omits + or - at start of lines 5 years ago
eidheim 3b4ef42bd5 Fixed compilation on MSYS2 5 years ago
eidheim c2eff0a40c Fixes #434 : added possibility to add custom commands in menu item juCi++, Commands 5 years ago
eidheim 11a5368ea4 Renamed instances in get() functions to instance 5 years ago
eidheim 81b3b670dc Improved instructions when rust installation is not found 5 years ago
eidheim 36a8515d05 Now also searches for rust-analyzer executable in $PATH 5 years ago
eidheim 4d1f725a0f New Project menu items now also add .gitignore files 5 years ago
eidheim 9579c16193 Added menu item File, New Project, Rust 5 years ago
eidheim 4cd3ee6de2 Added extra lookup for rust-analyzer 5 years ago
eidheim 17a3fb4e2f Fixed rust debug value formatter 5 years ago
eidheim 218f98f9c2 Now makes sure build directory is created before building and debugging rust programs 5 years ago
eidheim 485c68f6ea Fixed Open With Default Application menu item in directory view for file paths with special characters 5 years ago
eidheim b3b4cf71d9 Fixed potential crash on directory update when trying to colorize a deleted folder 5 years ago
eidheim 3b07c7b66b Updated libclangmm 5 years ago
eidheim 59aa74afb3 Fixed test on MSYS2 5 years ago
eidheim ddaca98476 Made filesystem methods thread safe 5 years ago
eidheim 955e1558f2 Let theme decide if tree lines should be enabled in directories view 5 years ago
eidheim 9a463dc9d7 Added cancel dialog if grep or ctags processes exceed 10 seconds 5 years ago
eidheim e59cb70467 v1.6.3 5 years ago
eidheim 7808ec6746 Updated libclangmm submodule 5 years ago
eidheim 18aca81cf6 Added undefined-sanitizer job to CI 5 years ago
eidheim dc9177e97e Fixed a thread sanitizer warning in LanguageProtocol::Client destructor 5 years ago
eidheim 0a76fab5b2 Added platform specific instructions for installing prettier 5 years ago
eidheim 38b92a54af Added Window menu items Toggle Directories, Toggle Terminal and Toggle Menu 5 years ago
eidheim a684e9995e Added .prettierrc and applied style format on markdown files 5 years ago
eidheim 3a8385a9c6 Added test for filesystem::get_current_path() 5 years ago
eidheim 69065d25c0 Added prettier installation instructions message when prettier executable is not found 5 years ago
eidheim 98354766e7 Fixed ctags_grep_test 5 years ago
eidheim eefe86f2b2 Cleanup of dialog source/header files 5 years ago
eidheim 076ee4d7cf No longer resolves symbolic links when finding current path 5 years ago
eidheim d2a5d81b59 Made project name terminal output bold on creating new C/C++ project 5 years ago
eidheim f709c31ec3 Language client, rename: now open, rename, save and close unopened buffers (as seems to be necessary for some language servers) 5 years ago
eidheim cd6af96e0e Find Symbol now always uses ctags 5 years ago
eidheim 61caf12e82 Improved and simplified folder exclusion for grep and ctags 5 years ago
eidheim 454fe03fb4 Cleanup for terminal messages 5 years ago
eidheim c7da9c4977 Corrected typo in language server installation instructions 5 years ago
eidheim cdf44cac9a Removed unnecessary line in python language server installation instructions 5 years ago
eidheim 77b60abf07 Added installation instruction outputs upon detecting missing language servers when opening some source file types 5 years ago
eidheim d94024f38d Added comment to Filesystem::find_executable, and slight changed message on created C/C++ project 5 years ago
eidheim 84804a89b7 Made "C/C++ project created" terminal message green 5 years ago
eidheim ab51495012 Now marks clickable URIs in terminal output 5 years ago
eidheim ccd84bd166 Made use of Thread Safety Analysis EXCLUDES to avoid deadlocks in the future 5 years ago
eidheim 92b7cbdb2b Callback of Terminal::get().async_process is now run in the main thread 5 years ago
eidheim cf9758b19f Related to #444: on executing command on for instance Linux, make sure symbolic links are not resolved 5 years ago
eidheim f03a223383 Relted to #444: added Linux specific line buffering on async processes 5 years ago
eidheim 979ae41520 Fixes #444: stdout is now line buffered when running commands 5 years ago
eidheim 1a93e3fdf6 LanguageClient: added support for CodeAction response with edit.documentChanges 5 years ago
eidheim 6dd97e41e6 Added LanguageProtocol::Diagnostic::code 5 years ago
eidheim 28ed5fef60 Slight correction of extend_selection() for markdown code blocks 5 years ago
eidheim ba6ebe8162 Removed redundant check 5 years ago
eidheim 98e89fccec Improved extend selection on markdown code blocks 5 years ago
eidheim 3d2f0810e1 Slight improvement to closing html element insertion 5 years ago
eidheim 1d89eba3e4 Cleanup of Project::LLDB::debug_backtrace() and Project::LLDB::debug_show_variables() 5 years ago
eidheim e08c39967f Debug::LLDB::get_variables no longer prefetch variable values 5 years ago
eidheim 08040b011b Selection dialog now also wraps arround navigation 5 years ago
Andreas Hammer d23a707458 Fixed completion dialog wrap arround navigation 5 years ago
eidheim 2b99c04dce Updated libclangmm submodule 5 years ago
eidheim a02e55e9e1 Added support for Apple M1 5 years ago
eidheim 3b26315623 Fixed debug stop status message for newer liblldb versions 5 years ago
eidheim 64205e2ee9 Improved smart insertions of '' and "" 5 years ago
eidheim 8a8c21543a Fixed potential crash when getting value from debug expression 5 years ago
Jørgen Lien Sellæg 46a5e5dcd8 disable dynamic registration of capabillities 5 years ago
Jørgen Lien Sellæg d4a1f165a8 use ccache if it is installed to speed up development 5 years ago
eidheim 7d1e49559f Fixes #443: added preference item for keeping entry shown after running command 5 years ago
eidheim f853ae9d08 Language client: workaround for buggy language servers that report double equal codeAction edits 5 years ago
eidheim 5d06b6fddd Language client: slightly improved codeAction message where range is set to diagnostic range if only one diagnostic exists 5 years ago
eidheim fa716a5d8c Updated cmake minimum required to 3.1 5 years ago
eidheim 404ac10afa Go to Usage now shows filenames for all rows unless usages are in current file only 5 years ago
eidheim 4f9b3c5940 Fixed grep command: always show filename, even when folder contains only one file 5 years ago
eidheim dae81957aa Updated tiny-process-library submodule 5 years ago
eidheim 6d245cc02a v1.6.2 5 years ago
eidheim dd9500da2a Updated libclangmm submodule 5 years ago
eidheim 4476df8b29 Updated minimum required cmake version 5 years ago
eidheim 1722d1288a Fixed prettier error messages with ansi colors, and now add link tag to prettier error links 5 years ago
eidheim e3f294f5c8 Slight improvement to find_close_symbol_forward and find_open_symbol_backward 5 years ago
eidheim 60e6730b78 Improved support for non-ascii symbol names 5 years ago
eidheim b59d4befe6 Fixed markup on ctags results 5 years ago
eidheim 3498b4bf82 Fixes LanguageManager::guess_language calls in certain circumstances: use filename as argument instead of entire path 5 years ago
eidheim 88b4900e35 Added menu item Open With Default Application to right click directory menus 5 years ago
eidheim d8902c25f3 Removed unnecessary newline 5 years ago
Jørgen Lien Sellæg f8301c2648 remove unnecessary space 5 years ago
Jørgen Lien Sellæg d96121fa5a update to multiline echo and set shebangs to /bin/sh 5 years ago
Jørgen Lien Sellæg 67289736f9 update docs to use sh instead of bash and add chmod for glsl 5 years ago
Jørgen Lien Sellæg a89a965a80 add support for glsl shaders closes #439 5 years ago
eidheim 04b5aace73 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. 5 years ago
eidheim 2074b03b7a Added Posix file link tagging to terminal 5 years ago
eidheim 9c726eaee3 Merge branch 'update-docs' of https://gitlab.com/cppit/jucipp 5 years ago
eidheim 25ce06b79c Added missing try around boost::property_tree::read_json call 5 years ago
Jørgen Lien Sellæg 5d1cd3282a add root hint to docs 5 years ago
eidheim 06d9854e15 Make terminal link tag higher priority than colored tags 5 years ago
eidheim b24113ae56 Improved terminal link tagging of Node.js error output 5 years ago
eidheim 9c0685cb01 No longer scroll terminal to botton when for instance running prettier or clang-format 5 years ago
eidheim 3c726f52da Formatting cleanup 5 years ago
Jørgen Lien Sellæg d518aab030 update lambdas to new format 5 years ago
Jørgen Lien Sellæg 6d40ecd8cc remove unnecessary else statement 5 years ago
Jørgen Lien Sellæg 007785a74d remove documentation about code style as it is no longer practise 5 years ago
Jørgen Lien Sellæg 889c80de70 add ci step to catch lint errors 5 years ago
Jørgen Lien Sellæg ead0a8c8b6 fix formatting in tooltips test 5 years ago
Jørgen Lien Sellæg 0a2f52f044 use if statement instead of ternary operator 5 years ago
Jørgen Lien Sellæg 946052c2a8 format raw strings 5 years ago
Jørgen Lien Sellæg bca93a62dc use correct format on certain parts 5 years ago
eidheim 4a76c6114f No longer adds semicolon after lambda expressions that are passed as arguments 5 years ago
eidheim f3d76e08ae Improved handling of enter after { at beginning of lambda expressions that are arguments 5 years ago
eidheim 19887dff27 Fixes #438: improved detection and handling of closing curly bracket in special circumstances 5 years ago
eidheim 64b67eedde Can now paste in terminal 5 years ago
eidheim 7e44c26615 Fix CI failure 5 years ago
eidheim 161b26aefa Cleanup of language client json parsing code 5 years ago
eidheim 85b19da51b Added include fixits for C warnings beginning with: implicitly declaring library function 5 years ago
eidheim 3124fa7fa3 Added suggestion to restart juCi++ on libclang completion failure 5 years ago
eidheim e67799eec3 Added and made use of gray color in terminal. Also made all error messages red. 5 years ago
  1. 24
      .gitlab-ci.yml
  2. 5
      .prettierrc
  3. 58
      CMakeLists.txt
  4. 3
      LICENSE
  5. 167
      README.md
  6. 14
      docs/api.md
  7. 6
      docs/custom_styling.md
  8. 98
      docs/install.md
  9. 189
      docs/language_servers.md
  10. 234
      jucipp.yaml
  11. 84
      lib/json/.clang-format
  12. 21
      lib/json/LICENSE.MIT
  13. 73
      lib/json/include/nlohmann/adl_serializer.hpp
  14. 166
      lib/json/include/nlohmann/byte_container_with_subtype.hpp
  15. 453
      lib/json/include/nlohmann/detail/conversions/from_json.hpp
  16. 1103
      lib/json/include/nlohmann/detail/conversions/to_chars.hpp
  17. 382
      lib/json/include/nlohmann/detail/conversions/to_json.hpp
  18. 421
      lib/json/include/nlohmann/detail/exceptions.hpp
  19. 121
      lib/json/include/nlohmann/detail/hash.hpp
  20. 2461
      lib/json/include/nlohmann/detail/input/binary_reader.hpp
  21. 476
      lib/json/include/nlohmann/detail/input/input_adapters.hpp
  22. 711
      lib/json/include/nlohmann/detail/input/json_sax.hpp
  23. 1623
      lib/json/include/nlohmann/detail/input/lexer.hpp
  24. 492
      lib/json/include/nlohmann/detail/input/parser.hpp
  25. 27
      lib/json/include/nlohmann/detail/input/position_t.hpp
  26. 25
      lib/json/include/nlohmann/detail/iterators/internal_iterator.hpp
  27. 646
      lib/json/include/nlohmann/detail/iterators/iter_impl.hpp
  28. 181
      lib/json/include/nlohmann/detail/iterators/iteration_proxy.hpp
  29. 51
      lib/json/include/nlohmann/detail/iterators/iterator_traits.hpp
  30. 119
      lib/json/include/nlohmann/detail/iterators/json_reverse_iterator.hpp
  31. 123
      lib/json/include/nlohmann/detail/iterators/primitive_iterator.hpp
  32. 934
      lib/json/include/nlohmann/detail/json_pointer.hpp
  33. 68
      lib/json/include/nlohmann/detail/json_ref.hpp
  34. 302
      lib/json/include/nlohmann/detail/macro_scope.hpp
  35. 23
      lib/json/include/nlohmann/detail/macro_unscope.hpp
  36. 154
      lib/json/include/nlohmann/detail/meta/cpp_future.hpp
  37. 58
      lib/json/include/nlohmann/detail/meta/detected.hpp
  38. 10
      lib/json/include/nlohmann/detail/meta/identity_tag.hpp
  39. 149
      lib/json/include/nlohmann/detail/meta/is_sax.hpp
  40. 436
      lib/json/include/nlohmann/detail/meta/type_traits.hpp
  41. 13
      lib/json/include/nlohmann/detail/meta/void_t.hpp
  42. 1594
      lib/json/include/nlohmann/detail/output/binary_writer.hpp
  43. 129
      lib/json/include/nlohmann/detail/output/output_adapters.hpp
  44. 954
      lib/json/include/nlohmann/detail/output/serializer.hpp
  45. 63
      lib/json/include/nlohmann/detail/string_escape.hpp
  46. 81
      lib/json/include/nlohmann/detail/value_t.hpp
  47. 8942
      lib/json/include/nlohmann/json.hpp
  48. 78
      lib/json/include/nlohmann/json_fwd.hpp
  49. 190
      lib/json/include/nlohmann/ordered_map.hpp
  50. 2044
      lib/json/include/nlohmann/thirdparty/hedley/hedley.hpp
  51. 150
      lib/json/include/nlohmann/thirdparty/hedley/hedley_undef.hpp
  52. 2
      lib/libclangmm
  53. 2
      lib/tiny-process-library
  54. 26
      share/set_icon.sh
  55. 4
      share/set_icon_macos.py
  56. 12
      src/CMakeLists.txt
  57. 71
      src/autocomplete.cpp
  58. 19
      src/autocomplete.hpp
  59. 129
      src/cmake.cpp
  60. 6
      src/cmake.hpp
  61. 55
      src/commands.cpp
  62. 30
      src/commands.hpp
  63. 270
      src/compile_commands.cpp
  64. 15
      src/compile_commands.hpp
  65. 755
      src/config.cpp
  66. 32
      src/config.hpp
  67. 84
      src/ctags.cpp
  68. 74
      src/debug_lldb.cpp
  69. 51
      src/debug_lldb.hpp
  70. 73
      src/dialog.cpp
  71. 15
      src/dialog.hpp
  72. 31
      src/dialogs_unix.cpp
  73. 163
      src/dialogs_win.cpp
  74. 398
      src/directories.cpp
  75. 18
      src/directories.hpp
  76. 23
      src/entrybox.cpp
  77. 9
      src/entrybox.hpp
  78. 241
      src/filesystem.cpp
  79. 41
      src/filesystem.hpp
  80. 125
      src/git.cpp
  81. 4
      src/git.hpp
  82. 58
      src/grep.cpp
  83. 19
      src/info.cpp
  84. 497
      src/json.cpp
  85. 128
      src/json.hpp
  86. 33
      src/juci.cpp
  87. 151
      src/menu.cpp
  88. 5
      src/menu.hpp
  89. 56
      src/meson.cpp
  90. 296
      src/notebook.cpp
  91. 16
      src/notebook.hpp
  92. 687
      src/project.cpp
  93. 43
      src/project.hpp
  94. 135
      src/project_build.cpp
  95. 36
      src/project_build.hpp
  96. 159
      src/selection_dialog.cpp
  97. 24
      src/selection_dialog.hpp
  98. 15
      src/snippets.cpp
  99. 4
      src/snippets.hpp
  100. 1547
      src/source.cpp
  101. Some files were not shown because too many files have changed in this diff Show More

24
.gitlab-ci.yml

@ -2,6 +2,7 @@ variables:
GIT_SUBMODULE_STRATEGY: recursive
stages:
- lint
- test
- chore
@ -9,7 +10,7 @@ stages:
stage: test
script:
- mkdir build && cd build
- CXXFLAGS=-Werror cmake -DBUILD_TESTING=1 ..
- CXXFLAGS="-D_GLIBCXX_ASSERTIONS" cmake -DBUILD_TESTING=1 ..
- make -j$(nproc)
- broadwayd & CTEST_OUTPUT_ON_FAILURE=1 make test
@ -54,6 +55,27 @@ 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
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

5
.prettierrc

@ -0,0 +1,5 @@
{
"printWidth": 100,
"proseWrap": "always",
"singleQuote": true
}

58
CMakeLists.txt

@ -1,7 +1,7 @@
cmake_minimum_required (VERSION 2.8.8)
cmake_minimum_required(VERSION 3.10)
project(juci)
set(JUCI_VERSION "1.7.0.0")
set(JUCI_VERSION "1.8.1")
set(CPACK_PACKAGE_NAME "jucipp")
set(CPACK_PACKAGE_CONTACT "Ole Christian Eidheim <eidheim@gmail.com>")
@ -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)
@ -29,24 +39,44 @@ 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 -Wstring-conversion -Wliteral-conversion)
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")
set(ENV{PKG_CONFIG_PATH} "/opt/homebrew/opt/cairo/lib/pkgconfig:/opt/homebrew/opt/fontconfig/lib/pkgconfig:/usr/local/opt/fontconfig/lib/pkgconfig:$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")
endif()
if(${CMAKE_SYSTEM_NAME} MATCHES FreeBSD)
link_directories(/usr/local/lib)
link_directories(${CMAKE_INSTALL_PREFIX}/lib)
endif()
if(${CMAKE_SYSTEM_NAME} MATCHES NetBSD)
link_directories(/usr/pkg/lib)
link_directories(${CMAKE_INSTALL_PREFIX}/lib)
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
endif()
option(BUILD_TESTING "Build tests")
option(BUILD_FUZZING "Build tests")
option(LIBCLANG_PATH "Use custom path for libclang")
option(LIBLLDB_PATH "Use custom path for liblldb")
option(FLATPAK_SANDBOX "Runs from within a flatpak sandbox")
if(${CMAKE_VERSION} VERSION_GREATER 3.11.4)
if (${CMAKE_HOST_WIN32})
@ -103,10 +133,19 @@ endif()
set(BUILD_TESTING_SAVED ${BUILD_TESTING})
set(BUILD_TESTING OFF CACHE BOOL "Disable sub-project tests" FORCE)
set(BUILD_TESTING ${BUILD_TESTING_SAVED} CACHE BOOL "Set to previous value" FORCE)
if(POLICY CMP0167)
cmake_policy(SET CMP0167 NEW)
endif()
find_package(Boost 1.54 COMPONENTS REQUIRED filesystem serialization)
find_package(ASPELL REQUIRED)
pkg_check_modules(GTKMM gtkmm-3.0 REQUIRED)
pkg_check_modules(GTKSVMM gtksourceviewmm-3.0 REQUIRED)
pkg_check_modules(GTKSVMM gtksourceviewmm-3.0)
if(NOT GTKSVMM_FOUND)
pkg_check_modules(GTKSVMM gtksourceviewmm-4.0 REQUIRED)
endif()
pkg_check_modules(LIBGIT2 libgit2 REQUIRED)
include(FindPackageHandleStandardArgs)
@ -124,6 +163,8 @@ else()
find_package(LibClang REQUIRED)
endif()
add_definitions(-DLIBCLANG_LIBRARY_DIR="${LIBCLANG_LIBRARY_DIR}")
set(BUILD_TESTING_SAVED ${BUILD_TESTING})
set(BUILD_TESTING OFF CACHE BOOL "Disable sub-project tests" FORCE)
add_subdirectory(lib/tiny-process-library)
@ -153,6 +194,10 @@ else()
message("liblldb not found. Building juCi++ without debugging support")
endif()
if(FLATPAK_SANDBOX)
add_definitions(-DJUCI_FLATPAK_SANDBOX)
endif()
if(CMAKE_SYSTEM_NAME MATCHES .*BSD|DragonFly)
add_definitions(-DJUCI_USE_UCTAGS) # See https://svnweb.freebsd.org/ports?view=revision&revision=452957
add_definitions(-DJUCI_USE_GREP_EXCLUDE) # --exclude-dir is not an argument in bsd grep
@ -169,6 +214,7 @@ include_directories(
${PYTHON_INCLUDE_DIRS}
${PYGOBJECT_INCLUDE_DIRS}
${CMAKE_SOURCE_DIR}/lib/pybind11/include
${CMAKE_SOURCE_DIR}/lib/json/include
)
add_subdirectory("src")

3
LICENSE

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2015-2020 cppit
Copyright (c) 2015-2024 cppit
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -19,4 +19,3 @@ 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.

167
README.md

@ -1,56 +1,76 @@
<img alt="juCi++" src="share/juci.png" />
## 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 (see
[setup of tested language servers](docs/language_servers.md)). 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.
## Installation
See [installation guide](docs/install.md).
## 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 JavaScript, Python, Rust, and Go 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
<table border="0">
<tr>
<td><img src="docs/images/screenshot1c.png" width="380"/></td>
@ -61,50 +81,27 @@ See [enhancements](https://gitlab.com/cppit/jucipp/issues?scope=all&state=opened
</tr>
</table>
## 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)
- [JSON for Modern C++](https://github.com/nlohmann/json) (included in repository, no need to
install)
## 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))
```
See [how to build the API doc](docs/api.md).

14
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

6
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%;
}
@ -38,6 +38,10 @@ was made with the following ~/.config/gtk-3.0/gtk.css:
background: transparent;
}
.juci_notebook {
background: transparent;
}
.juci_notebook :not(slider) {
background-color: rgba(255, 255, 255, 0.8);
}

98
docs/install.md

@ -1,22 +1,26 @@
# 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)
- [OpenSuSE Leap](#opensuse-leap)
- [GNU Guix/GuixSD](#gnu-guixguixsd)
- [FreeBSD](#freebsd)
- [NetBSD](#netbsd)
- 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 +28,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 +39,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 +58,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 +77,7 @@ sudo make install
```
## Mageia
**Mageia might not yet support LLDB, but you can compile without debug support.**
Install dependencies:
@ -77,11 +89,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
@ -91,13 +105,35 @@ make
sudo make install
```
## OpenSUSE Tumbleweed
## 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
cd jucipp/build
cmake -DCMAKE_CXX_COMPILER=g++ ..
make
sudo make install
```
## OpenSuSE Leap
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 libboost_filesystem1_66_0 libboost_filesystem1_66_0-devel libboost_serialization1_66_0 libboost_serialization1_66_0-devel aspell gtksourceviewmm3_0-devel
```
Get juCi++ source, compile and install:
```sh
git clone --recursive https://gitlab.com/cppit/jucipp
mkdir jucipp/build
@ -108,27 +144,53 @@ 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.
## NetBSD
Install dependencies:
```sh
pkgin -y install lldb ccache doxygen pkg-config aspell-en libgit2 gtksourceviewmm exctags boost
```
Get juCi++ source, compile and install:
```sh
git clone --recursive https://gitlab.com/cppit/jucipp
mkdir -p jucipp/build
cd jucipp/build
cmake -DCMAKE_INSTALL_PREFIX=/usr/pkg ..
make
su -m
make install
```
## 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
brew install cmake pkg-config boost gtksourceviewmm3 gnome-icon-theme aspell llvm clang-format libgit2 zlib libxml2 universal-ctags
```
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 +201,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 make mingw-w64-x86_64-{cmake,toolchain,clang,gtkmm3,gtksourceviewmm3,boost,aspell,aspell-en,libgit2,universal-ctags-git,pygobject-devel}
```
@ -149,6 +213,7 @@ pacman -S git make mingw-w64-x86_64-{cmake,toolchain,clang,gtkmm3,gtksourceviewm
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 +224,13 @@ make install
```
## Run
```sh
juci
```
Alternatively, you can also include directories and files:
```sh
juci [directory] [file1 file2 ...]
```

189
docs/language_servers.md

@ -1,76 +1,193 @@
# Setup of tested language servers
## JavaScript with Flow static type checker
* Prerequisites:
* Node.js
* Recommended:
* [Prettier](https://github.com/prettier/prettier)
- [JavaScript/TypeScript](#javascripttypescript)
- [Python3](#python3)
- [Rust](#rust)
- [Go](#go)
- [Julia](#julia)
- [GLSL](#glsl)
- [C/C++](#cc)
## JavaScript/TypeScript
### JavaScript with Flow static type checker
- 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
echo '#!/bin/bash
# Usually as root:
echo '#!/bin/sh
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
## 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
echo '#!/bin/bash
# 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
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
* In juCi++ preferences, set `project.python_command` to `PYTHONUNBUFFERED=1 python3`
- Prerequisites:
- Python3
- In juCi++ preferences, set `project.python_command` to `python3 -u`
### Python LSP Server
Install language server, and create symbolic link to enable server in juCi++:
```sh
pip3 install python-lsp-server[pycodestyle,yapf]
# Usually as root:
ln -s `which pylsp` /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
### Pyright
- Prerequisite:
- Node.js
- Recommended:
- [YAPF](https://github.com/google/yapf) for style format (`pip3 install yapf`)
Install language server, and create executable to enable server in juCi++:
```sh
pip3 install python-language-server[rope,pycodestyle,yapf]
npm install -g pyright
ln -s `which pyls` /usr/local/bin/python-language-server
# Usually as root:
echo '#!/bin/sh
pyright-langserver --stdio' > /usr/local/bin/python-language-server
chmod 755 /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 Pyright configuration file: `echo '{}' > pyrightconfig.json`
- Add style format file to enable format on save, for instance:
`printf '[style]\nCOLUMN_LIMIT = 120\n' > .style.yapf`
## Rust
* Prerequisites:
* Rust
Install language server, and create symbolic link to enable server in juCi++:
- Prerequisites:
- [Rust](https://www.rust-lang.org/tools/install)
Install language server:
```sh
rustup component add rust-analyzer
```
- 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
## Julia
- Prerequisites:
- [Julia](https://julialang.org/downloads/)
Install language server, and create executable to enable server in juCi++:
```sh
rustup component add rust-src
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
```
git clone https://github.com/rust-analyzer/rust-analyzer
cd rust-analyzer
cargo xtask install --server
- Additional setup within a Julia project:
- Add an empty `.julia-format` file to enable style format on save
ln -s ~/.cargo/bin/rust-analyzer /usr/local/bin/rust-language-server
## 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 '#!/bin/sh
/usr/local/bin/glslls --stdin' > /usr/local/bin/glsl-language-server
chmod 755 /usr/local/bin/glsl-language-server
```
* Additional setup within a Rust project:
* Add an empty `.rust-format` file to enable style format on save
## C/C++
**Note that we recommend using the builtin C/C++ support in juCi++ instead.**
- Prerequisites:
- A language server installed on your system, for example clangd or ccls
Create symbolic link to enable language server in juCi++. For example, if you have the clangd
installed and available on path:
```sh
# Usually as root:
ln -s `which clangd` /usr/local/bin/clang-language-server
```

234
jucipp.yaml

@ -0,0 +1,234 @@
id: com.gitlab.cppit.jucipp
command: juci
# Install via: flatpak install flathub org.gnome.Platform//42 org.gnome.Sdk//42
runtime: org.gnome.Platform
runtime-version: '42'
sdk: org.gnome.Sdk
sdk-extensions:
# Install via: flatpak install flathub org.freedesktop.Sdk.Extension.llvm14
- org.freedesktop.Sdk.Extension.llvm14
rename-desktop-file: juci.desktop
rename-icon: juci
finish-args:
- --socket=x11
- --share=ipc # somewhat required by X11
- --socket=wayland
- --share=network # Unix sockets are e.g. used by X11
- --filesystem=host:rw # access all files on the host
- --filesystem=home:rw # access all files on the host
- --talk-name=org.freedesktop.Flatpak # allows to run processes outside of flatpak sandbox
- --persist=.juci # persist configuration
modules:
- name: boost
buildsystem: simple
sources:
- type: archive
url: https://boostorg.jfrog.io/artifactory/main/release/1.79.0/source/boost_1_79_0.tar.bz2
sha256: 475d589d51a7f8b3ba2ba4eda022b170e562ca3b760ee922c146b6c65856ef39
build-commands:
- ./bootstrap.sh --prefix="${FLATPAK_DEST}" --with-libraries=filesystem,serialization
- ./b2 -j $FLATPAK_BUILDER_N_JOBS install
cleanup:
- /include
- '/lib/libboost_*.a'
- /lib/cmake
- /lib/debug
- /lib/pkgconfig
- name: mm-common # build-dependency of gtkmm
sources:
- type: archive
url: https://download.gnome.org/sources/mm-common/1.0/mm-common-1.0.4.tar.xz
sha256: e954c09b4309a7ef93e13b69260acdc5738c907477eb381b78bb1e414ee6dbd8
cleanup:
- '*'
- name: sigc++-2 # dependency of gtkmm
buildsystem: autotools
config-opts:
- --disable-documentation
sources:
- type: archive
url: https://download.gnome.org/sources/libsigc%2B%2B/2.10/libsigc%2B%2B-2.10.8.tar.xz
sha256: 235a40bec7346c7b82b6a8caae0456353dc06e71f14bc414bcc858af1838719a
cleanup:
- /include
- /lib/debug
- /lib/sigc++-2.0
- /lib/pkgconfig
- /lib/libsigc-2.0.la
- name: glibmm # dependency of gtkmm
buildsystem: meson
sources:
- type: archive
url: https://download.gnome.org/sources/glibmm/2.66/glibmm-2.66.2.tar.xz
sha256: b2a4cd7b9ae987794cbb5a1becc10cecb65182b9bb841868625d6bbb123edb1d
cleanup:
- /include
- /lib/debug
- /lib/giomm-2.4
- /lib/glibmm-2.4
- /lib/pkgconfig
- name: cairomm # dependency of gtkmm
buildsystem: autotools
config-opts:
- --disable-documentation
sources:
- type: archive
url: https://www.cairographics.org/releases/cairomm-1.14.3.tar.xz
sha256: 0d37e067c5c4ca7808b7ceddabfe1932c5bd2a750ad64fb321e1213536297e78
cleanup:
- /include
- /lib/cairomm-1.0
- /lib/debug
- /lib/pkgconfig
- /lib/libcairomm-1.0.la
- name: pangomm # dependency of gtkmm
buildsystem: meson
sources:
- type: archive
url: https://download.gnome.org/sources/pangomm/2.46/pangomm-2.46.2.tar.xz
sha256: 57442ab4dc043877bfe3839915731ab2d693fc6634a71614422fb530c9eaa6f4
cleanup:
- /include
- /lib/debug
- /lib/pangomm-1.4
- /lib/pkgconfig
- name: atkmm # dependency of gtkmm
buildsystem: meson
sources:
- type: archive
url: https://download.gnome.org/sources/atkmm/2.28/atkmm-2.28.2.tar.xz
sha256: a0bb49765ceccc293ab2c6735ba100431807d384ffa14c2ebd30e07993fd2fa4
cleanup:
- /include
- /lib/atkmm-1.6
- /lib/debug
- /lib/pkgconfig
- name: gtkmm
buildsystem: meson
sources:
- type: archive
url: https://download.gnome.org/sources/gtkmm/3.24/gtkmm-3.24.5.tar.xz
sha256: 856333de86689f6a81c123f2db15d85db9addc438bc3574c36f15736aeae22e6
cleanup:
- /include
- /lib/debug
- /lib/gdkmm-3.0
- /lib/gtkmm-3.0
- /lib/pkgconfig
- name: gtksourceview # dependency of gtksourceviewmm
buildsystem: autotools
config-opts:
- --disable-documentation
sources:
- type: archive
url: https://download.gnome.org/sources/gtksourceview/3.24/gtksourceview-3.24.11.tar.xz
sha256: 691b074a37b2a307f7f48edc5b8c7afa7301709be56378ccf9cc9735909077fd
cleanup:
- /include
- /lib/debug
- /lib/pkgconfig
- /lib/libgtksourceview-3.0.la
- /share/gtk-doc
- /share/vala
- name: gtksourceviewmm
buildsystem: autotools
config-opts:
- --disable-documentation
sources:
- type: archive
url: https://download.gnome.org/sources/gtksourceviewmm/3.21/gtksourceviewmm-3.21.3.tar.xz
sha256: dbb00b1c28e0407cc27d8b07a2ed0b4ea22f92e4b3e3006431cbd6726b6256b5
cleanup:
- /include
- /lib/debug
- /lib/gtksourceviewmm-3.0
- /lib/pkgconfig
- /lib/libgtksourceviewmm-3.0.la
- name: ncurses # build-depencendy of aspell (required to build aspell executable)
buildsystem: autotools
sources:
- type: archive
url: https://ftp.gnu.org/gnu/ncurses/ncurses-6.3.tar.gz
sha256: 97fc51ac2b085d4cde31ef4d2c3122c21abc217e9090a43a30fc5ec21684e059
cleanup:
- '*'
- name: aspell
buildsystem: autotools
sources:
- type: archive
url: https://ftp.gnu.org/gnu/aspell/aspell-0.60.8.tar.gz
sha256: f9b77e515334a751b2e60daab5db23499e26c9209f5e7b7443b05235ad0226f2
cleanup:
- /bin
- /include
- /lib/debug
- /lib/pkgconfig
- /lib/libaspell.la
- '/lib/libpspell.*'
- /share/info
- /share/man
- name: aspell-en
buildsystem: simple
sources:
- type: archive
url: https://ftp.gnu.org/gnu/aspell/dict/en/aspell6-en-2020.12.07-0.tar.bz2
sha256: 4c8f734a28a088b88bb6481fcf972d0b2c3dc8da944f7673283ce487eac49fb3
build-commands:
- ./configure
- make -j $FLATPAK_BUILDER_N_JOBS
- make install
- name: libgit2
buildsystem: cmake
config-opts:
- -DBUILD_TESTS=OFF
sources:
- type: archive
url: https://github.com/libgit2/libgit2/archive/refs/tags/v1.4.3.tar.gz
sha256: f48b961e463a9e4e7e7e58b21a0fb5a9b2a1d24d9ba4d15870a0c9b8ad965163
cleanup:
- /include
- /lib/debug
- /lib/pkgconfig
- name: jucipp
buildsystem: simple
build-options:
append-path: /usr/lib/sdk/llvm14/bin
prepend-ld-library-path: /usr/lib/sdk/llvm14/lib
sources:
- type: git
branch: master
url: https://gitlab.com/cppit/jucipp.git
build-commands:
- cp -r /usr/lib/sdk/llvm14/lib/* /app/lib/
- cp -r /usr/lib/sdk/llvm14/include/* /app/include/
- cmake -DCMAKE_INSTALL_PREFIX=${FLATPAK_DEST} -DFLATPAK_SANDBOX=ON .
- cmake --build . -- -j $FLATPAK_BUILDER_N_JOBS
- make install
cleanup:
- /include
- /lib/clang
- /lib/cmake
- /lib/debug
- /lib/libear
- /lib/libscanbuild
- /lib/python3.9
- '/lib/libLTO.*'
- '/lib/libRemarks.*'
- /lib/LLVMgold.so

84
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

21
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.

73
lib/json/include/nlohmann/adl_serializer.hpp

@ -0,0 +1,73 @@
#pragma once
#include <type_traits>
#include <utility>
#include <nlohmann/detail/conversions/from_json.hpp>
#include <nlohmann/detail/conversions/to_json.hpp>
#include <nlohmann/detail/meta/identity_tag.hpp>
#include <nlohmann/detail/meta/type_traits.hpp>
namespace nlohmann
{
template<typename ValueType, typename>
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<typename BasicJsonType, typename TargetType = ValueType>
static auto from_json(BasicJsonType && j, TargetType& val) noexcept(
noexcept(::nlohmann::from_json(std::forward<BasicJsonType>(j), val)))
-> decltype(::nlohmann::from_json(std::forward<BasicJsonType>(j), val), void())
{
::nlohmann::from_json(std::forward<BasicJsonType>(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<typename BasicJsonType, typename TargetType = ValueType>
static auto from_json(BasicJsonType && j) noexcept(
noexcept(::nlohmann::from_json(std::forward<BasicJsonType>(j), detail::identity_tag<TargetType> {})))
-> decltype(::nlohmann::from_json(std::forward<BasicJsonType>(j), detail::identity_tag<TargetType> {}))
{
return ::nlohmann::from_json(std::forward<BasicJsonType>(j), detail::identity_tag<TargetType> {});
}
/*!
@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<typename BasicJsonType, typename TargetType = ValueType>
static auto to_json(BasicJsonType& j, TargetType && val) noexcept(
noexcept(::nlohmann::to_json(j, std::forward<TargetType>(val))))
-> decltype(::nlohmann::to_json(j, std::forward<TargetType>(val)), void())
{
::nlohmann::to_json(j, std::forward<TargetType>(val));
}
};
} // namespace nlohmann

166
lib/json/include/nlohmann/byte_container_with_subtype.hpp

@ -0,0 +1,166 @@
#pragma once
#include <cstdint> // uint8_t
#include <tuple> // tie
#include <utility> // 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<std::uint8_t>` by
default)
@since version 3.8.0
*/
template<typename BinaryType>
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<const BinaryType&>(*this), m_subtype, m_has_subtype) ==
std::tie(static_cast<const BinaryType&>(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

453
lib/json/include/nlohmann/detail/conversions/from_json.hpp

@ -0,0 +1,453 @@
#pragma once
#include <algorithm> // transform
#include <array> // array
#include <forward_list> // forward_list
#include <iterator> // inserter, front_inserter, end
#include <map> // map
#include <string> // string
#include <tuple> // tuple, make_tuple
#include <type_traits> // is_arithmetic, is_same, is_enum, underlying_type, is_convertible
#include <unordered_map> // unordered_map
#include <utility> // pair, declval
#include <valarray> // valarray
#include <nlohmann/detail/exceptions.hpp>
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/meta/cpp_future.hpp>
#include <nlohmann/detail/meta/identity_tag.hpp>
#include <nlohmann/detail/meta/type_traits.hpp>
#include <nlohmann/detail/value_t.hpp>
namespace nlohmann
{
namespace detail
{
template<typename BasicJsonType>
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<ArithmeticType>::value&&
!std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,
int > = 0 >
void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
{
switch (static_cast<value_t>(j))
{
case value_t::number_unsigned:
{
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());
break;
}
case value_t::number_integer:
{
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());
break;
}
case value_t::number_float:
{
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());
break;
}
default:
JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()), j));
}
}
template<typename BasicJsonType>
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<const typename BasicJsonType::boolean_t*>();
}
template<typename BasicJsonType>
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<const typename BasicJsonType::string_t*>();
}
template <
typename BasicJsonType, typename ConstructibleStringType,
enable_if_t <
is_constructible_string_type<BasicJsonType, ConstructibleStringType>::value&&
!std::is_same<typename BasicJsonType::string_t,
ConstructibleStringType>::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<const typename BasicJsonType::string_t*>();
}
template<typename BasicJsonType>
void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val)
{
get_arithmetic_value(j, val);
}
template<typename BasicJsonType>
void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val)
{
get_arithmetic_value(j, val);
}
template<typename BasicJsonType>
void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val)
{
get_arithmetic_value(j, val);
}
template<typename BasicJsonType, typename EnumType,
enable_if_t<std::is_enum<EnumType>::value, int> = 0>
void from_json(const BasicJsonType& j, EnumType& e)
{
typename std::underlying_type<EnumType>::type val;
get_arithmetic_value(j, val);
e = static_cast<EnumType>(val);
}
// forward_list doesn't have an insert method
template<typename BasicJsonType, typename T, typename Allocator,
enable_if_t<is_getable<BasicJsonType, T>::value, int> = 0>
void from_json(const BasicJsonType& j, std::forward_list<T, Allocator>& 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<T>();
});
}
// valarray doesn't have an insert method
template<typename BasicJsonType, typename T,
enable_if_t<is_getable<BasicJsonType, T>::value, int> = 0>
void from_json(const BasicJsonType& j, std::valarray<T>& 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<T>();
});
}
template<typename BasicJsonType, typename T, std::size_t N>
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<T>(), void())
{
for (std::size_t i = 0; i < N; ++i)
{
arr[i] = j.at(i).template get<T>();
}
}
template<typename BasicJsonType>
void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/)
{
arr = *j.template get_ptr<const typename BasicJsonType::array_t*>();
}
template<typename BasicJsonType, typename T, std::size_t N>
auto from_json_array_impl(const BasicJsonType& j, std::array<T, N>& arr,
priority_tag<2> /*unused*/)
-> decltype(j.template get<T>(), void())
{
for (std::size_t i = 0; i < N; ++i)
{
arr[i] = j.at(i).template get<T>();
}
}
template<typename BasicJsonType, typename ConstructibleArrayType,
enable_if_t<
std::is_assignable<ConstructibleArrayType&, ConstructibleArrayType>::value,
int> = 0>
auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<1> /*unused*/)
-> decltype(
arr.reserve(std::declval<typename ConstructibleArrayType::size_type>()),
j.template get<typename ConstructibleArrayType::value_type>(),
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<BasicJsonType>() returns *this, this won't call a from_json
// method when value_type is BasicJsonType
return i.template get<typename ConstructibleArrayType::value_type>();
});
arr = std::move(ret);
}
template<typename BasicJsonType, typename ConstructibleArrayType,
enable_if_t<
std::is_assignable<ConstructibleArrayType&, ConstructibleArrayType>::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<BasicJsonType>() returns *this, this won't call a from_json
// method when value_type is BasicJsonType
return i.template get<typename ConstructibleArrayType::value_type>();
});
arr = std::move(ret);
}
template < typename BasicJsonType, typename ConstructibleArrayType,
enable_if_t <
is_constructible_array_type<BasicJsonType, ConstructibleArrayType>::value&&
!is_constructible_object_type<BasicJsonType, ConstructibleArrayType>::value&&
!is_constructible_string_type<BasicJsonType, ConstructibleArrayType>::value&&
!std::is_same<ConstructibleArrayType, typename BasicJsonType::binary_t>::value&&
!is_basic_json<ConstructibleArrayType>::value,
int > = 0 >
auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr)
-> decltype(from_json_array_impl(j, arr, priority_tag<3> {}),
j.template get<typename ConstructibleArrayType::value_type>(),
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<T, sizeof...(Idx)> from_json_inplace_array_impl(BasicJsonType&& j,
identity_tag<std::array<T, sizeof...(Idx)>> /*unused*/, index_sequence<Idx...> /*unused*/)
{
return { { std::forward<BasicJsonType>(j).at(Idx).template get<T>()... } };
}
template < typename BasicJsonType, typename T, std::size_t N >
auto from_json(BasicJsonType&& j, identity_tag<std::array<T, N>> tag)
-> decltype(from_json_inplace_array_impl(std::forward<BasicJsonType>(j), tag, make_index_sequence<N> {}))
{
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<BasicJsonType>(j), tag, make_index_sequence<N> {});
}
template<typename BasicJsonType>
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<const typename BasicJsonType::binary_t*>();
}
template<typename BasicJsonType, typename ConstructibleObjectType,
enable_if_t<is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::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<const typename BasicJsonType::object_t*>();
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<typename ConstructibleObjectType::mapped_type>());
});
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<ArithmeticType>::value&&
!std::is_same<ArithmeticType, typename BasicJsonType::number_unsigned_t>::value&&
!std::is_same<ArithmeticType, typename BasicJsonType::number_integer_t>::value&&
!std::is_same<ArithmeticType, typename BasicJsonType::number_float_t>::value&&
!std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,
int > = 0 >
void from_json(const BasicJsonType& j, ArithmeticType& val)
{
switch (static_cast<value_t>(j))
{
case value_t::number_unsigned:
{
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());
break;
}
case value_t::number_integer:
{
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());
break;
}
case value_t::number_float:
{
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());
break;
}
case value_t::boolean:
{
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::boolean_t*>());
break;
}
default:
JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()), j));
}
}
template<typename BasicJsonType, typename... Args, std::size_t... Idx>
std::tuple<Args...> from_json_tuple_impl_base(BasicJsonType&& j, index_sequence<Idx...> /*unused*/)
{
return std::make_tuple(std::forward<BasicJsonType>(j).at(Idx).template get<Args>()...);
}
template < typename BasicJsonType, class A1, class A2 >
std::pair<A1, A2> from_json_tuple_impl(BasicJsonType&& j, identity_tag<std::pair<A1, A2>> /*unused*/, priority_tag<0> /*unused*/)
{
return {std::forward<BasicJsonType>(j).at(0).template get<A1>(),
std::forward<BasicJsonType>(j).at(1).template get<A2>()};
}
template<typename BasicJsonType, typename A1, typename A2>
void from_json_tuple_impl(BasicJsonType&& j, std::pair<A1, A2>& p, priority_tag<1> /*unused*/)
{
p = from_json_tuple_impl(std::forward<BasicJsonType>(j), identity_tag<std::pair<A1, A2>> {}, priority_tag<0> {});
}
template<typename BasicJsonType, typename... Args>
std::tuple<Args...> from_json_tuple_impl(BasicJsonType&& j, identity_tag<std::tuple<Args...>> /*unused*/, priority_tag<2> /*unused*/)
{
return from_json_tuple_impl_base<BasicJsonType, Args...>(std::forward<BasicJsonType>(j), index_sequence_for<Args...> {});
}
template<typename BasicJsonType, typename... Args>
void from_json_tuple_impl(BasicJsonType&& j, std::tuple<Args...>& t, priority_tag<3> /*unused*/)
{
t = from_json_tuple_impl_base<BasicJsonType, Args...>(std::forward<BasicJsonType>(j), index_sequence_for<Args...> {});
}
template<typename BasicJsonType, typename TupleRelated>
auto from_json(BasicJsonType&& j, TupleRelated&& t)
-> decltype(from_json_tuple_impl(std::forward<BasicJsonType>(j), std::forward<TupleRelated>(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<BasicJsonType>(j), std::forward<TupleRelated>(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<Key, Value, Compare, Allocator>& 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<Key>(), p.at(1).template get<Value>());
}
}
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<Key, Value, Hash, KeyEqual, Allocator>& 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<Key>(), p.at(1).template get<Value>());
}
}
struct from_json_fn
{
template<typename BasicJsonType, typename T>
auto operator()(const BasicJsonType& j, T&& val) const
noexcept(noexcept(from_json(j, std::forward<T>(val))))
-> decltype(from_json(j, std::forward<T>(val)))
{
return from_json(j, std::forward<T>(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<detail::from_json_fn>::value; // NOLINT(misc-definitions-in-headers)
} // namespace
} // namespace nlohmann

1103
lib/json/include/nlohmann/detail/conversions/to_chars.hpp

File diff suppressed because it is too large Load Diff

382
lib/json/include/nlohmann/detail/conversions/to_json.hpp

@ -0,0 +1,382 @@
#pragma once
#include <algorithm> // copy
#include <iterator> // begin, end
#include <string> // string
#include <tuple> // tuple, get
#include <type_traits> // is_same, is_constructible, is_floating_point, is_enum, underlying_type
#include <utility> // move, forward, declval, pair
#include <valarray> // valarray
#include <vector> // vector
#include <nlohmann/detail/iterators/iteration_proxy.hpp>
#include <nlohmann/detail/meta/cpp_future.hpp>
#include <nlohmann/detail/meta/type_traits.hpp>
#include <nlohmann/detail/value_t.hpp>
namespace nlohmann
{
namespace detail
{
//////////////////
// constructors //
//////////////////
template<value_t> struct external_constructor;
template<>
struct external_constructor<value_t::boolean>
{
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept
{
j.m_type = value_t::boolean;
j.m_value = b;
j.assert_invariant();
}
};
template<>
struct external_constructor<value_t::string>
{
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s)
{
j.m_type = value_t::string;
j.m_value = s;
j.assert_invariant();
}
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s)
{
j.m_type = value_t::string;
j.m_value = std::move(s);
j.assert_invariant();
}
template < typename BasicJsonType, typename CompatibleStringType,
enable_if_t < !std::is_same<CompatibleStringType, typename BasicJsonType::string_t>::value,
int > = 0 >
static void construct(BasicJsonType& j, const CompatibleStringType& str)
{
j.m_type = value_t::string;
j.m_value.string = j.template create<typename BasicJsonType::string_t>(str);
j.assert_invariant();
}
};
template<>
struct external_constructor<value_t::binary>
{
template<typename BasicJsonType>
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<typename BasicJsonType>
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<value_t::number_float>
{
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept
{
j.m_type = value_t::number_float;
j.m_value = val;
j.assert_invariant();
}
};
template<>
struct external_constructor<value_t::number_unsigned>
{
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept
{
j.m_type = value_t::number_unsigned;
j.m_value = val;
j.assert_invariant();
}
};
template<>
struct external_constructor<value_t::number_integer>
{
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept
{
j.m_type = value_t::number_integer;
j.m_value = val;
j.assert_invariant();
}
};
template<>
struct external_constructor<value_t::array>
{
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr)
{
j.m_type = value_t::array;
j.m_value = arr;
j.set_parents();
j.assert_invariant();
}
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr)
{
j.m_type = value_t::array;
j.m_value = std::move(arr);
j.set_parents();
j.assert_invariant();
}
template < typename BasicJsonType, typename CompatibleArrayType,
enable_if_t < !std::is_same<CompatibleArrayType, typename BasicJsonType::array_t>::value,
int > = 0 >
static void construct(BasicJsonType& j, const CompatibleArrayType& arr)
{
using std::begin;
using std::end;
j.m_type = value_t::array;
j.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr));
j.set_parents();
j.assert_invariant();
}
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const std::vector<bool>& arr)
{
j.m_type = value_t::array;
j.m_value = value_t::array;
j.m_value.array->reserve(arr.size());
for (const bool x : arr)
{
j.m_value.array->push_back(x);
j.set_parent(j.m_value.array->back());
}
j.assert_invariant();
}
template<typename BasicJsonType, typename T,
enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
static void construct(BasicJsonType& j, const std::valarray<T>& arr)
{
j.m_type = value_t::array;
j.m_value = value_t::array;
j.m_value.array->resize(arr.size());
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<value_t::object>
{
template<typename BasicJsonType>
static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj)
{
j.m_type = value_t::object;
j.m_value = obj;
j.set_parents();
j.assert_invariant();
}
template<typename BasicJsonType>
static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
{
j.m_type = value_t::object;
j.m_value = std::move(obj);
j.set_parents();
j.assert_invariant();
}
template < typename BasicJsonType, typename CompatibleObjectType,
enable_if_t < !std::is_same<CompatibleObjectType, typename BasicJsonType::object_t>::value, int > = 0 >
static void construct(BasicJsonType& j, const CompatibleObjectType& obj)
{
using std::begin;
using std::end;
j.m_type = value_t::object;
j.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj));
j.set_parents();
j.assert_invariant();
}
};
/////////////
// to_json //
/////////////
template<typename BasicJsonType, typename T,
enable_if_t<std::is_same<T, typename BasicJsonType::boolean_t>::value, int> = 0>
void to_json(BasicJsonType& j, T b) noexcept
{
external_constructor<value_t::boolean>::construct(j, b);
}
template<typename BasicJsonType, typename CompatibleString,
enable_if_t<std::is_constructible<typename BasicJsonType::string_t, CompatibleString>::value, int> = 0>
void to_json(BasicJsonType& j, const CompatibleString& s)
{
external_constructor<value_t::string>::construct(j, s);
}
template<typename BasicJsonType>
void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s)
{
external_constructor<value_t::string>::construct(j, std::move(s));
}
template<typename BasicJsonType, typename FloatType,
enable_if_t<std::is_floating_point<FloatType>::value, int> = 0>
void to_json(BasicJsonType& j, FloatType val) noexcept
{
external_constructor<value_t::number_float>::construct(j, static_cast<typename BasicJsonType::number_float_t>(val));
}
template<typename BasicJsonType, typename CompatibleNumberUnsignedType,
enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_unsigned_t, CompatibleNumberUnsignedType>::value, int> = 0>
void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept
{
external_constructor<value_t::number_unsigned>::construct(j, static_cast<typename BasicJsonType::number_unsigned_t>(val));
}
template<typename BasicJsonType, typename CompatibleNumberIntegerType,
enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType>::value, int> = 0>
void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept
{
external_constructor<value_t::number_integer>::construct(j, static_cast<typename BasicJsonType::number_integer_t>(val));
}
template<typename BasicJsonType, typename EnumType,
enable_if_t<std::is_enum<EnumType>::value, int> = 0>
void to_json(BasicJsonType& j, EnumType e) noexcept
{
using underlying_type = typename std::underlying_type<EnumType>::type;
external_constructor<value_t::number_integer>::construct(j, static_cast<underlying_type>(e));
}
template<typename BasicJsonType>
void to_json(BasicJsonType& j, const std::vector<bool>& e)
{
external_constructor<value_t::array>::construct(j, e);
}
template < typename BasicJsonType, typename CompatibleArrayType,
enable_if_t < is_compatible_array_type<BasicJsonType,
CompatibleArrayType>::value&&
!is_compatible_object_type<BasicJsonType, CompatibleArrayType>::value&&
!is_compatible_string_type<BasicJsonType, CompatibleArrayType>::value&&
!std::is_same<typename BasicJsonType::binary_t, CompatibleArrayType>::value&&
!is_basic_json<CompatibleArrayType>::value,
int > = 0 >
void to_json(BasicJsonType& j, const CompatibleArrayType& arr)
{
external_constructor<value_t::array>::construct(j, arr);
}
template<typename BasicJsonType>
void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin)
{
external_constructor<value_t::binary>::construct(j, bin);
}
template<typename BasicJsonType, typename T,
enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
void to_json(BasicJsonType& j, const std::valarray<T>& arr)
{
external_constructor<value_t::array>::construct(j, std::move(arr));
}
template<typename BasicJsonType>
void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr)
{
external_constructor<value_t::array>::construct(j, std::move(arr));
}
template < typename BasicJsonType, typename CompatibleObjectType,
enable_if_t < is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value&& !is_basic_json<CompatibleObjectType>::value, int > = 0 >
void to_json(BasicJsonType& j, const CompatibleObjectType& obj)
{
external_constructor<value_t::object>::construct(j, obj);
}
template<typename BasicJsonType>
void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
{
external_constructor<value_t::object>::construct(j, std::move(obj));
}
template <
typename BasicJsonType, typename T, std::size_t N,
enable_if_t < !std::is_constructible<typename BasicJsonType::string_t,
const T(&)[N]>::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<value_t::array>::construct(j, arr);
}
template < typename BasicJsonType, typename T1, typename T2, enable_if_t < std::is_constructible<BasicJsonType, T1>::value&& std::is_constructible<BasicJsonType, T2>::value, int > = 0 >
void to_json(BasicJsonType& j, const std::pair<T1, T2>& p)
{
j = { p.first, p.second };
}
// for https://github.com/nlohmann/json/pull/1134
template<typename BasicJsonType, typename T,
enable_if_t<std::is_same<T, iteration_proxy_value<typename BasicJsonType::iterator>>::value, int> = 0>
void to_json(BasicJsonType& j, const T& b)
{
j = { {b.key(), b.value()} };
}
template<typename BasicJsonType, typename Tuple, std::size_t... Idx>
void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<Idx...> /*unused*/)
{
j = { std::get<Idx>(t)... };
}
template<typename BasicJsonType, typename T, enable_if_t<is_constructible_tuple<BasicJsonType, T>::value, int > = 0>
void to_json(BasicJsonType& j, const T& t)
{
to_json_tuple_impl(j, t, make_index_sequence<std::tuple_size<T>::value> {});
}
struct to_json_fn
{
template<typename BasicJsonType, typename T>
auto operator()(BasicJsonType& j, T&& val) const noexcept(noexcept(to_json(j, std::forward<T>(val))))
-> decltype(to_json(j, std::forward<T>(val)), void())
{
return to_json(j, std::forward<T>(val));
}
};
} // 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<detail::to_json_fn>::value; // NOLINT(misc-definitions-in-headers)
} // namespace
} // namespace nlohmann

421
lib/json/include/nlohmann/detail/exceptions.hpp

@ -0,0 +1,421 @@
#pragma once
#include <exception> // exception
#include <stdexcept> // runtime_error
#include <string> // to_string
#include <vector> // vector
#include <nlohmann/detail/value_t.hpp>
#include <nlohmann/detail/string_escape.hpp>
#include <nlohmann/detail/input/position_t.hpp>
#include <nlohmann/detail/macro_scope.hpp>
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<typename BasicJsonType>
static std::string diagnostics(const BasicJsonType& leaf_element)
{
#if JSON_DIAGNOSTICS
std::vector<std::string> 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 (&current->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<void>(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<typename BasicJsonType>
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<typename BasicJsonType>
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<typename BasicJsonType>
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<typename BasicJsonType>
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<typename BasicJsonType>
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<typename BasicJsonType>
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

121
lib/json/include/nlohmann/detail/hash.hpp

@ -0,0 +1,121 @@
#pragma once
#include <cstdint> // uint8_t
#include <cstddef> // size_t
#include <functional> // hash
#include <nlohmann/detail/macro_scope.hpp>
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<typename BasicJsonType>
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<std::size_t>(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<string_t> {}(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<string_t> {}(j.template get_ref<const string_t&>());
return combine(type, h);
}
case BasicJsonType::value_t::boolean:
{
const auto h = std::hash<bool> {}(j.template get<bool>());
return combine(type, h);
}
case BasicJsonType::value_t::number_integer:
{
const auto h = std::hash<number_integer_t> {}(j.template get<number_integer_t>());
return combine(type, h);
}
case BasicJsonType::value_t::number_unsigned:
{
const auto h = std::hash<number_unsigned_t> {}(j.template get<number_unsigned_t>());
return combine(type, h);
}
case BasicJsonType::value_t::number_float:
{
const auto h = std::hash<number_float_t> {}(j.template get<number_float_t>());
return combine(type, h);
}
case BasicJsonType::value_t::binary:
{
auto seed = combine(type, j.get_binary().size());
const auto h = std::hash<bool> {}(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<std::uint8_t> {}(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

2461
lib/json/include/nlohmann/detail/input/binary_reader.hpp

File diff suppressed because it is too large Load Diff

476
lib/json/include/nlohmann/detail/input/input_adapters.hpp

@ -0,0 +1,476 @@
#pragma once
#include <array> // array
#include <cstddef> // size_t
#include <cstdio> //FILE *
#include <cstring> // strlen
#include <istream> // istream
#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next
#include <memory> // shared_ptr, make_shared, addressof
#include <numeric> // accumulate
#include <string> // string, char_traits
#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
#include <utility> // pair, declval
#include <nlohmann/detail/iterators/iterator_traits.hpp>
#include <nlohmann/detail/macro_scope.hpp>
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<char>::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<char>::to_int_type, to
// ensure that std::char_traits<char>::eof() and the character 0xFF do not
// end up as the same value, eg. 0xFFFFFFFF.
std::char_traits<char>::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<char>::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<typename IteratorType>
class iterator_input_adapter
{
public:
using char_type = typename std::iterator_traits<IteratorType>::value_type;
iterator_input_adapter(IteratorType first, IteratorType last)
: current(std::move(first)), end(std::move(last))
{}
typename std::char_traits<char_type>::int_type get_character()
{
if (JSON_HEDLEY_LIKELY(current != end))
{
auto result = std::char_traits<char_type>::to_int_type(*current);
std::advance(current, 1);
return result;
}
return std::char_traits<char_type>::eof();
}
private:
IteratorType current;
IteratorType end;
template<typename BaseInputAdapter, size_t T>
friend struct wide_string_input_helper;
bool empty() const
{
return current == end;
}
};
template<typename BaseInputAdapter, size_t T>
struct wide_string_input_helper;
template<typename BaseInputAdapter>
struct wide_string_input_helper<BaseInputAdapter, 4>
{
// UTF-32
static void fill_buffer(BaseInputAdapter& input,
std::array<std::char_traits<char>::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<char>::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<std::char_traits<char>::int_type>(wc);
utf8_bytes_filled = 1;
}
else if (wc <= 0x7FF)
{
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xC0u | ((static_cast<unsigned int>(wc) >> 6u) & 0x1Fu));
utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | (static_cast<unsigned int>(wc) & 0x3Fu));
utf8_bytes_filled = 2;
}
else if (wc <= 0xFFFF)
{
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xE0u | ((static_cast<unsigned int>(wc) >> 12u) & 0x0Fu));
utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((static_cast<unsigned int>(wc) >> 6u) & 0x3Fu));
utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | (static_cast<unsigned int>(wc) & 0x3Fu));
utf8_bytes_filled = 3;
}
else if (wc <= 0x10FFFF)
{
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xF0u | ((static_cast<unsigned int>(wc) >> 18u) & 0x07u));
utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((static_cast<unsigned int>(wc) >> 12u) & 0x3Fu));
utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | ((static_cast<unsigned int>(wc) >> 6u) & 0x3Fu));
utf8_bytes[3] = static_cast<std::char_traits<char>::int_type>(0x80u | (static_cast<unsigned int>(wc) & 0x3Fu));
utf8_bytes_filled = 4;
}
else
{
// unknown character
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);
utf8_bytes_filled = 1;
}
}
}
};
template<typename BaseInputAdapter>
struct wide_string_input_helper<BaseInputAdapter, 2>
{
// UTF-16
static void fill_buffer(BaseInputAdapter& input,
std::array<std::char_traits<char>::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<char>::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<std::char_traits<char>::int_type>(wc);
utf8_bytes_filled = 1;
}
else if (wc <= 0x7FF)
{
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xC0u | ((static_cast<unsigned int>(wc) >> 6u)));
utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | (static_cast<unsigned int>(wc) & 0x3Fu));
utf8_bytes_filled = 2;
}
else if (0xD800 > wc || wc >= 0xE000)
{
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xE0u | ((static_cast<unsigned int>(wc) >> 12u)));
utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((static_cast<unsigned int>(wc) >> 6u) & 0x3Fu));
utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | (static_cast<unsigned int>(wc) & 0x3Fu));
utf8_bytes_filled = 3;
}
else
{
if (JSON_HEDLEY_UNLIKELY(!input.empty()))
{
const auto wc2 = static_cast<unsigned int>(input.get_character());
const auto charcode = 0x10000u + (((static_cast<unsigned int>(wc) & 0x3FFu) << 10u) | (wc2 & 0x3FFu));
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xF0u | (charcode >> 18u));
utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 12u) & 0x3Fu));
utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 6u) & 0x3Fu));
utf8_bytes[3] = static_cast<std::char_traits<char>::int_type>(0x80u | (charcode & 0x3Fu));
utf8_bytes_filled = 4;
}
else
{
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);
utf8_bytes_filled = 1;
}
}
}
}
};
// Wraps another input apdater to convert wide character types into individual bytes.
template<typename BaseInputAdapter, typename WideCharType>
class wide_string_input_adapter
{
public:
using char_type = char;
wide_string_input_adapter(BaseInputAdapter base)
: base_adapter(base) {}
typename std::char_traits<char>::int_type get_character() noexcept
{
// check if buffer needs to be filled
if (utf8_bytes_index == utf8_bytes_filled)
{
fill_buffer<sizeof(WideCharType)>();
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<size_t T>
void fill_buffer()
{
wide_string_input_helper<BaseInputAdapter, T>::fill_buffer(base_adapter, utf8_bytes, utf8_bytes_index, utf8_bytes_filled);
}
/// a buffer for UTF-8 bytes
std::array<std::char_traits<char>::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<typename IteratorType, typename Enable = void>
struct iterator_input_adapter_factory
{
using iterator_type = IteratorType;
using char_type = typename std::iterator_traits<iterator_type>::value_type;
using adapter_type = iterator_input_adapter<iterator_type>;
static adapter_type create(IteratorType first, IteratorType last)
{
return adapter_type(std::move(first), std::move(last));
}
};
template<typename T>
struct is_iterator_of_multibyte
{
using value_type = typename std::iterator_traits<T>::value_type;
enum
{
value = sizeof(value_type) > 1
};
};
template<typename IteratorType>
struct iterator_input_adapter_factory<IteratorType, enable_if_t<is_iterator_of_multibyte<IteratorType>::value>>
{
using iterator_type = IteratorType;
using char_type = typename std::iterator_traits<iterator_type>::value_type;
using base_adapter_type = iterator_input_adapter<iterator_type>;
using adapter_type = wide_string_input_adapter<base_adapter_type, char_type>;
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 IteratorType>
typename iterator_input_adapter_factory<IteratorType>::adapter_type input_adapter(IteratorType first, IteratorType last)
{
using factory_type = iterator_input_adapter_factory<IteratorType>;
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<typename ContainerType, typename Enable = void>
struct container_input_adapter_factory {};
template<typename ContainerType>
struct container_input_adapter_factory< ContainerType,
void_t<decltype(begin(std::declval<ContainerType>()), end(std::declval<ContainerType>()))>>
{
using adapter_type = decltype(input_adapter(begin(std::declval<ContainerType>()), end(std::declval<ContainerType>())));
static adapter_type create(const ContainerType& container)
{
return input_adapter(begin(container), end(container));
}
};
} // namespace container_input_adapter_factory_impl
template<typename ContainerType>
typename container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::adapter_type input_adapter(const ContainerType& container)
{
return container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::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<const char*>(), std::declval<const char*>()));
// Null-delimited strings, and the like.
template < typename CharT,
typename std::enable_if <
std::is_pointer<CharT>::value&&
!std::is_array<CharT>::value&&
std::is_integral<typename std::remove_pointer<CharT>::type>::value&&
sizeof(typename std::remove_pointer<CharT>::type) == 1,
int >::type = 0 >
contiguous_bytes_input_adapter input_adapter(CharT b)
{
auto length = std::strlen(reinterpret_cast<const char*>(b));
const auto* ptr = reinterpret_cast<const char*>(b);
return input_adapter(ptr, ptr + length);
}
template<typename T, std::size_t N>
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<CharT>::value&&
std::is_integral<typename std::remove_pointer<CharT>::type>::value&&
sizeof(typename std::remove_pointer<CharT>::type) == 1,
int >::type = 0 >
span_input_adapter(CharT b, std::size_t l)
: ia(reinterpret_cast<const char*>(b), reinterpret_cast<const char*>(b) + l) {}
template<class IteratorType,
typename std::enable_if<
std::is_same<typename iterator_traits<IteratorType>::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

711
lib/json/include/nlohmann/detail/input/json_sax.hpp

@ -0,0 +1,711 @@
#pragma once
#include <cstddef>
#include <string> // string
#include <utility> // move
#include <vector> // vector
#include <nlohmann/detail/exceptions.hpp>
#include <nlohmann/detail/macro_scope.hpp>
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<typename BasicJsonType>
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<typename BasicJsonType>
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<class Exception>
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/,
const Exception& ex)
{
errored = true;
static_cast<void>(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<typename Value>
JSON_HEDLEY_RETURNS_NON_NULL
BasicJsonType* handle_value(Value&& v)
{
if (ref_stack.empty())
{
root = BasicJsonType(std::forward<Value>(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<Value>(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<Value>(v));
return object_element;
}
/// the parsed JSON value
BasicJsonType& root;
/// stack to model hierarchy of values
std::vector<BasicJsonType*> 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<typename BasicJsonType>
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<int>(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<int>(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<int>(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<int>(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<int>(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<class Exception>
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/,
const Exception& ex)
{
errored = true;
static_cast<void>(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<typename Value>
std::pair<bool, BasicJsonType*> 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<Value>(v));
// check callback
const bool keep = skip_callback || callback(static_cast<int>(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<BasicJsonType*> ref_stack {};
/// stack to manage which values to keep
std::vector<bool> keep_stack {};
/// stack to manage which object keys to keep
std::vector<bool> 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<typename BasicJsonType>
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

1623
lib/json/include/nlohmann/detail/input/lexer.hpp

File diff suppressed because it is too large Load Diff

492
lib/json/include/nlohmann/detail/input/parser.hpp

@ -0,0 +1,492 @@
#pragma once
#include <cmath> // isfinite
#include <cstdint> // uint8_t
#include <functional> // function
#include <string> // string
#include <utility> // move
#include <vector> // vector
#include <nlohmann/detail/exceptions.hpp>
#include <nlohmann/detail/input/input_adapters.hpp>
#include <nlohmann/detail/input/json_sax.hpp>
#include <nlohmann/detail/input/lexer.hpp>
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/meta/is_sax.hpp>
#include <nlohmann/detail/value_t.hpp>
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<typename BasicJsonType>
using parser_callback_t =
std::function<bool(int /*depth*/, parse_event_t /*event*/, BasicJsonType& /*parsed*/)>;
/*!
@brief syntax analysis
This class implements a recursive descent parser.
*/
template<typename BasicJsonType, typename InputAdapterType>
class parser
{
using number_integer_t = typename BasicJsonType::number_integer_t;
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
using number_float_t = typename BasicJsonType::number_float_t;
using string_t = typename BasicJsonType::string_t;
using lexer_t = lexer<BasicJsonType, InputAdapterType>;
using token_type = typename lexer_t::token_type;
public:
/// a parser reading from an input adapter
explicit parser(InputAdapterType&& adapter,
const parser_callback_t<BasicJsonType> 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<BasicJsonType> 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<BasicJsonType> 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<BasicJsonType> sax_acceptor;
return sax_parse(&sax_acceptor, strict);
}
template<typename SAX>
JSON_HEDLEY_NON_NULL(2)
bool sax_parse(SAX* sax, const bool strict = true)
{
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
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<typename SAX>
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<bool> 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<BasicJsonType> 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

27
lib/json/include/nlohmann/detail/input/position_t.hpp

@ -0,0 +1,27 @@
#pragma once
#include <cstddef> // 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

25
lib/json/include/nlohmann/detail/iterators/internal_iterator.hpp

@ -0,0 +1,25 @@
#pragma once
#include <nlohmann/detail/iterators/primitive_iterator.hpp>
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<typename BasicJsonType> 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

646
lib/json/include/nlohmann/detail/iterators/iter_impl.hpp

@ -0,0 +1,646 @@
#pragma once
#include <iterator> // iterator, random_access_iterator_tag, bidirectional_iterator_tag, advance, next
#include <type_traits> // conditional, is_const, remove_const
#include <nlohmann/detail/exceptions.hpp>
#include <nlohmann/detail/iterators/internal_iterator.hpp>
#include <nlohmann/detail/iterators/primitive_iterator.hpp>
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/meta/cpp_future.hpp>
#include <nlohmann/detail/meta/type_traits.hpp>
#include <nlohmann/detail/value_t.hpp>
namespace nlohmann
{
namespace detail
{
// forward declare, to be able to friend it later on
template<typename IteratorType> class iteration_proxy;
template<typename IteratorType> 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<typename BasicJsonType>
class iter_impl
{
/// the iterator with BasicJsonType of different const-ness
using other_iter_impl = iter_impl<typename std::conditional<std::is_const<BasicJsonType>::value, typename std::remove_const<BasicJsonType>::type, const BasicJsonType>::type>;
/// allow basic_json to access private members
friend other_iter_impl;
friend BasicJsonType;
friend iteration_proxy<iter_impl>;
friend iteration_proxy_value<iter_impl>;
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<typename std::remove_const<BasicJsonType>::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<std::is_const<BasicJsonType>::value,
typename BasicJsonType::const_pointer,
typename BasicJsonType::pointer>::type;
/// defines a reference to the type iterated over (value_type)
using reference =
typename std::conditional<std::is_const<BasicJsonType>::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<const BasicJsonType>& 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<const BasicJsonType>& 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<typename std::remove_const<BasicJsonType>::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<typename std::remove_const<BasicJsonType>::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<IterImpl, iter_impl>::value || std::is_same<IterImpl, other_iter_impl>::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<IterImpl, iter_impl>::value || std::is_same<IterImpl, other_iter_impl>::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<typename std::remove_const<BasicJsonType>::type> m_it {};
};
} // namespace detail
} // namespace nlohmann

181
lib/json/include/nlohmann/detail/iterators/iteration_proxy.hpp

@ -0,0 +1,181 @@
#pragma once
#include <cstddef> // size_t
#include <iterator> // input_iterator_tag
#include <string> // string, to_string
#include <tuple> // tuple_size, get, tuple_element
#include <utility> // move
#include <nlohmann/detail/meta/type_traits.hpp>
#include <nlohmann/detail/value_t.hpp>
namespace nlohmann
{
namespace detail
{
template<typename string_type>
void int_to_string( string_type& target, std::size_t value )
{
// For ADL
using std::to_string;
target = to_string(value);
}
template<typename IteratorType> 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<decltype( std::declval<IteratorType>().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<typename IteratorType> 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<IteratorType> begin() noexcept
{
return iteration_proxy_value<IteratorType>(container.begin());
}
/// return iterator end (needed for range-based for)
iteration_proxy_value<IteratorType> end() noexcept
{
return iteration_proxy_value<IteratorType>(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<std::size_t N, typename IteratorType, enable_if_t<N == 0, int> = 0>
auto get(const nlohmann::detail::iteration_proxy_value<IteratorType>& 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<std::size_t N, typename IteratorType, enable_if_t<N == 1, int> = 0>
auto get(const nlohmann::detail::iteration_proxy_value<IteratorType>& 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<typename IteratorType>
class tuple_size<::nlohmann::detail::iteration_proxy_value<IteratorType>>
: public std::integral_constant<std::size_t, 2> {};
template<std::size_t N, typename IteratorType>
class tuple_element<N, ::nlohmann::detail::iteration_proxy_value<IteratorType >>
{
public:
using type = decltype(
get<N>(std::declval <
::nlohmann::detail::iteration_proxy_value<IteratorType >> ()));
};
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
} // namespace std

51
lib/json/include/nlohmann/detail/iterators/iterator_traits.hpp

@ -0,0 +1,51 @@
#pragma once
#include <iterator> // random_access_iterator_tag
#include <nlohmann/detail/meta/void_t.hpp>
#include <nlohmann/detail/meta/cpp_future.hpp>
namespace nlohmann
{
namespace detail
{
template<typename It, typename = void>
struct iterator_types {};
template<typename It>
struct iterator_types <
It,
void_t<typename It::difference_type, typename It::value_type, typename It::pointer,
typename It::reference, typename It::iterator_category >>
{
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<typename T, typename = void>
struct iterator_traits
{
};
template<typename T>
struct iterator_traits < T, enable_if_t < !std::is_pointer<T>::value >>
: iterator_types<T>
{
};
template<typename T>
struct iterator_traits<T*, enable_if_t<std::is_object<T>::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

119
lib/json/include/nlohmann/detail/iterators/json_reverse_iterator.hpp

@ -0,0 +1,119 @@
#pragma once
#include <cstddef> // ptrdiff_t
#include <iterator> // reverse_iterator
#include <utility> // 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<typename Base>
class json_reverse_iterator : public std::reverse_iterator<Base>
{
public:
using difference_type = std::ptrdiff_t;
/// shortcut to the reverse iterator adapter
using base_iterator = std::reverse_iterator<Base>;
/// 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<json_reverse_iterator>(base_iterator::operator++(1));
}
/// pre-increment (++it)
json_reverse_iterator& operator++()
{
return static_cast<json_reverse_iterator&>(base_iterator::operator++());
}
/// post-decrement (it--)
json_reverse_iterator const operator--(int) // NOLINT(readability-const-return-type)
{
return static_cast<json_reverse_iterator>(base_iterator::operator--(1));
}
/// pre-decrement (--it)
json_reverse_iterator& operator--()
{
return static_cast<json_reverse_iterator&>(base_iterator::operator--());
}
/// add to iterator
json_reverse_iterator& operator+=(difference_type i)
{
return static_cast<json_reverse_iterator&>(base_iterator::operator+=(i));
}
/// add to iterator
json_reverse_iterator operator+(difference_type i) const
{
return static_cast<json_reverse_iterator>(base_iterator::operator+(i));
}
/// subtract from iterator
json_reverse_iterator operator-(difference_type i) const
{
return static_cast<json_reverse_iterator>(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<Base>().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

123
lib/json/include/nlohmann/detail/iterators/primitive_iterator.hpp

@ -0,0 +1,123 @@
#pragma once
#include <cstddef> // ptrdiff_t
#include <limits> // numeric_limits
#include <nlohmann/detail/macro_scope.hpp>
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<std::ptrdiff_t>::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

934
lib/json/include/nlohmann/detail/json_pointer.hpp

@ -0,0 +1,934 @@
#pragma once
#include <algorithm> // all_of
#include <cctype> // isdigit
#include <limits> // max
#include <numeric> // accumulate
#include <string> // string
#include <utility> // move
#include <vector> // vector
#include <nlohmann/detail/exceptions.hpp>
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/string_escape.hpp>
#include <nlohmann/detail/value_t.hpp>
namespace nlohmann
{
template<typename BasicJsonType>
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<unsigned long long>((std::numeric_limits<size_type>::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<size_type>(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<std::string> split(const std::string& reference_string)
{
std::vector<std::string> 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<std::string> reference_tokens;
};
} // namespace nlohmann

68
lib/json/include/nlohmann/detail/json_ref.hpp

@ -0,0 +1,68 @@
#pragma once
#include <initializer_list>
#include <utility>
#include <nlohmann/detail/meta/type_traits.hpp>
namespace nlohmann
{
namespace detail
{
template<typename BasicJsonType>
class json_ref
{
public:
using value_type = BasicJsonType;
json_ref(value_type&& value)
: owned_value(std::move(value))
{}
json_ref(const value_type& value)
: value_ref(&value)
{}
json_ref(std::initializer_list<json_ref> init)
: owned_value(init)
{}
template <
class... Args,
enable_if_t<std::is_constructible<value_type, Args...>::value, int> = 0 >
json_ref(Args && ... args)
: owned_value(std::forward<Args>(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

302
lib/json/include/nlohmann/detail/macro_scope.hpp

@ -0,0 +1,302 @@
#pragma once
#include <utility> // pair
#include <nlohmann/thirdparty/hedley/hedley.hpp>
// 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 <cstdlib>
#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 <cassert> // 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<typename BasicJsonType> \
inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \
{ \
static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!"); \
static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__; \
auto it = std::find_if(std::begin(m), std::end(m), \
[e](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \
{ \
return ej_pair.first == e; \
}); \
j = ((it != std::end(m)) ? it : std::begin(m))->second; \
} \
template<typename BasicJsonType> \
inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \
{ \
static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!"); \
static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__; \
auto it = std::find_if(std::begin(m), std::end(m), \
[&j](const std::pair<ENUM_TYPE, BasicJsonType>& 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<template<typename, typename, typename...> class ObjectType, \
template<typename, typename...> class ArrayType, \
class StringType, class BooleanType, class NumberIntegerType, \
class NumberUnsignedType, class NumberFloatType, \
template<typename> class AllocatorType, \
template<typename, typename = void> class JSONSerializer, \
class BinaryType>
#define NLOHMANN_BASIC_JSON_TPL \
basic_json<ObjectType, ArrayType, StringType, BooleanType, \
NumberIntegerType, NumberUnsignedType, NumberFloatType, \
AllocatorType, JSONSerializer, BinaryType>
// 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

23
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 <nlohmann/thirdparty/hedley/hedley_undef.hpp>

154
lib/json/include/nlohmann/detail/meta/cpp_future.hpp

@ -0,0 +1,154 @@
#pragma once
#include <cstddef> // size_t
#include <type_traits> // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type
#include <utility> // index_sequence, make_index_sequence, index_sequence_for
#include <nlohmann/detail/macro_scope.hpp>
namespace nlohmann
{
namespace detail
{
template<typename T>
using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::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<bool B, typename T = void>
using enable_if_t = typename std::enable_if<B, T>::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<T, Ints...>` 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<T, Ints...>);
//
// 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<int, 5>());
// }
template <typename T, T... Ints>
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 <size_t... Ints>
using index_sequence = integer_sequence<size_t, Ints...>;
namespace utility_internal
{
template <typename Seq, size_t SeqSize, size_t Rem>
struct Extend;
// Note that SeqSize == sizeof...(Ints). It's passed explicitly for efficiency.
template <typename T, T... Ints, size_t SeqSize>
struct Extend<integer_sequence<T, Ints...>, SeqSize, 0>
{
using type = integer_sequence < T, Ints..., (Ints + SeqSize)... >;
};
template <typename T, T... Ints, size_t SeqSize>
struct Extend<integer_sequence<T, Ints...>, SeqSize, 1>
{
using type = integer_sequence < T, Ints..., (Ints + SeqSize)..., 2 * SeqSize >;
};
// Recursion helper for 'make_integer_sequence<T, N>'.
// 'Gen<T, N>::type' is an alias for 'integer_sequence<T, 0, 1, ... N-1>'.
template <typename T, size_t N>
struct Gen
{
using type =
typename Extend < typename Gen < T, N / 2 >::type, N / 2, N % 2 >::type;
};
template <typename T>
struct Gen<T, 0>
{
using type = integer_sequence<T>;
};
} // namespace utility_internal
// Compile-time sequences of integers
// make_integer_sequence
//
// This template alias is equivalent to
// `integer_sequence<int, 0, 1, ..., N-1>`, and is designed to be a drop-in
// replacement for C++14's `std::make_integer_sequence`.
template <typename T, T N>
using make_integer_sequence = typename utility_internal::Gen<T, N>::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 <size_t N>
using make_index_sequence = make_integer_sequence<size_t, N>;
// 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 <typename... Ts>
using index_sequence_for = make_index_sequence<sizeof...(Ts)>;
//// END OF CODE FROM GOOGLE ABSEIL
#endif
// dispatch utility (taken from ranges-v3)
template<unsigned N> struct priority_tag : priority_tag < N - 1 > {};
template<> struct priority_tag<0> {};
// taken from ranges-v3
template<typename T>
struct static_const
{
static constexpr T value{};
};
template<typename T>
constexpr T static_const<T>::value;
} // namespace detail
} // namespace nlohmann

58
lib/json/include/nlohmann/detail/meta/detected.hpp

@ -0,0 +1,58 @@
#pragma once
#include <type_traits>
#include <nlohmann/detail/meta/void_t.hpp>
// 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 Default,
class AlwaysVoid,
template<class...> class Op,
class... Args>
struct detector
{
using value_t = std::false_type;
using type = Default;
};
template<class Default, template<class...> class Op, class... Args>
struct detector<Default, void_t<Op<Args...>>, Op, Args...>
{
using value_t = std::true_type;
using type = Op<Args...>;
};
template<template<class...> class Op, class... Args>
using is_detected = typename detector<nonesuch, void, Op, Args...>::value_t;
template<template<class...> class Op, class... Args>
using detected_t = typename detector<nonesuch, void, Op, Args...>::type;
template<class Default, template<class...> class Op, class... Args>
using detected_or = detector<Default, void, Op, Args...>;
template<class Default, template<class...> class Op, class... Args>
using detected_or_t = typename detected_or<Default, Op, Args...>::type;
template<class Expected, template<class...> class Op, class... Args>
using is_detected_exact = std::is_same<Expected, detected_t<Op, Args...>>;
template<class To, template<class...> class Op, class... Args>
using is_detected_convertible =
std::is_convertible<detected_t<Op, Args...>, To>;
} // namespace detail
} // namespace nlohmann

10
lib/json/include/nlohmann/detail/meta/identity_tag.hpp

@ -0,0 +1,10 @@
#pragma once
namespace nlohmann
{
namespace detail
{
// dispatching helper struct
template <class T> struct identity_tag {};
} // namespace detail
} // namespace nlohmann

149
lib/json/include/nlohmann/detail/meta/is_sax.hpp

@ -0,0 +1,149 @@
#pragma once
#include <cstdint> // size_t
#include <utility> // declval
#include <string> // string
#include <nlohmann/detail/meta/detected.hpp>
#include <nlohmann/detail/meta/type_traits.hpp>
namespace nlohmann
{
namespace detail
{
template<typename T>
using null_function_t = decltype(std::declval<T&>().null());
template<typename T>
using boolean_function_t =
decltype(std::declval<T&>().boolean(std::declval<bool>()));
template<typename T, typename Integer>
using number_integer_function_t =
decltype(std::declval<T&>().number_integer(std::declval<Integer>()));
template<typename T, typename Unsigned>
using number_unsigned_function_t =
decltype(std::declval<T&>().number_unsigned(std::declval<Unsigned>()));
template<typename T, typename Float, typename String>
using number_float_function_t = decltype(std::declval<T&>().number_float(
std::declval<Float>(), std::declval<const String&>()));
template<typename T, typename String>
using string_function_t =
decltype(std::declval<T&>().string(std::declval<String&>()));
template<typename T, typename Binary>
using binary_function_t =
decltype(std::declval<T&>().binary(std::declval<Binary&>()));
template<typename T>
using start_object_function_t =
decltype(std::declval<T&>().start_object(std::declval<std::size_t>()));
template<typename T, typename String>
using key_function_t =
decltype(std::declval<T&>().key(std::declval<String&>()));
template<typename T>
using end_object_function_t = decltype(std::declval<T&>().end_object());
template<typename T>
using start_array_function_t =
decltype(std::declval<T&>().start_array(std::declval<std::size_t>()));
template<typename T>
using end_array_function_t = decltype(std::declval<T&>().end_array());
template<typename T, typename Exception>
using parse_error_function_t = decltype(std::declval<T&>().parse_error(
std::declval<std::size_t>(), std::declval<const std::string&>(),
std::declval<const Exception&>()));
template<typename SAX, typename BasicJsonType>
struct is_sax
{
private:
static_assert(is_basic_json<BasicJsonType>::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<bool, null_function_t, SAX>::value &&
is_detected_exact<bool, boolean_function_t, SAX>::value &&
is_detected_exact<bool, number_integer_function_t, SAX, number_integer_t>::value &&
is_detected_exact<bool, number_unsigned_function_t, SAX, number_unsigned_t>::value &&
is_detected_exact<bool, number_float_function_t, SAX, number_float_t, string_t>::value &&
is_detected_exact<bool, string_function_t, SAX, string_t>::value &&
is_detected_exact<bool, binary_function_t, SAX, binary_t>::value &&
is_detected_exact<bool, start_object_function_t, SAX>::value &&
is_detected_exact<bool, key_function_t, SAX, string_t>::value &&
is_detected_exact<bool, end_object_function_t, SAX>::value &&
is_detected_exact<bool, start_array_function_t, SAX>::value &&
is_detected_exact<bool, end_array_function_t, SAX>::value &&
is_detected_exact<bool, parse_error_function_t, SAX, exception_t>::value;
};
template<typename SAX, typename BasicJsonType>
struct is_sax_static_asserts
{
private:
static_assert(is_basic_json<BasicJsonType>::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<bool, null_function_t, SAX>::value,
"Missing/invalid function: bool null()");
static_assert(is_detected_exact<bool, boolean_function_t, SAX>::value,
"Missing/invalid function: bool boolean(bool)");
static_assert(is_detected_exact<bool, boolean_function_t, SAX>::value,
"Missing/invalid function: bool boolean(bool)");
static_assert(
is_detected_exact<bool, number_integer_function_t, SAX,
number_integer_t>::value,
"Missing/invalid function: bool number_integer(number_integer_t)");
static_assert(
is_detected_exact<bool, number_unsigned_function_t, SAX,
number_unsigned_t>::value,
"Missing/invalid function: bool number_unsigned(number_unsigned_t)");
static_assert(is_detected_exact<bool, number_float_function_t, SAX,
number_float_t, string_t>::value,
"Missing/invalid function: bool number_float(number_float_t, const string_t&)");
static_assert(
is_detected_exact<bool, string_function_t, SAX, string_t>::value,
"Missing/invalid function: bool string(string_t&)");
static_assert(
is_detected_exact<bool, binary_function_t, SAX, binary_t>::value,
"Missing/invalid function: bool binary(binary_t&)");
static_assert(is_detected_exact<bool, start_object_function_t, SAX>::value,
"Missing/invalid function: bool start_object(std::size_t)");
static_assert(is_detected_exact<bool, key_function_t, SAX, string_t>::value,
"Missing/invalid function: bool key(string_t&)");
static_assert(is_detected_exact<bool, end_object_function_t, SAX>::value,
"Missing/invalid function: bool end_object()");
static_assert(is_detected_exact<bool, start_array_function_t, SAX>::value,
"Missing/invalid function: bool start_array(std::size_t)");
static_assert(is_detected_exact<bool, end_array_function_t, SAX>::value,
"Missing/invalid function: bool end_array()");
static_assert(
is_detected_exact<bool, parse_error_function_t, SAX, exception_t>::value,
"Missing/invalid function: bool parse_error(std::size_t, const "
"std::string&, const exception&)");
};
} // namespace detail
} // namespace nlohmann

436
lib/json/include/nlohmann/detail/meta/type_traits.hpp

@ -0,0 +1,436 @@
#pragma once
#include <limits> // numeric_limits
#include <type_traits> // false_type, is_constructible, is_integral, is_same, true_type
#include <utility> // declval
#include <tuple> // tuple
#include <nlohmann/detail/iterators/iterator_traits.hpp>
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/meta/cpp_future.hpp>
#include <nlohmann/detail/meta/detected.hpp>
#include <nlohmann/json_fwd.hpp>
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<T>())))
//
// In this case, T has to be properly CV-qualified to constraint the function arguments
// (e.g. to_json(BasicJsonType&, const T&))
template<typename> struct is_basic_json : std::false_type {};
NLOHMANN_BASIC_JSON_TPL_DECLARATION
struct is_basic_json<NLOHMANN_BASIC_JSON_TPL> : std::true_type {};
//////////////////////
// json_ref helpers //
//////////////////////
template<typename>
class json_ref;
template<typename>
struct is_json_ref : std::false_type {};
template<typename T>
struct is_json_ref<json_ref<T>> : std::true_type {};
//////////////////////////
// aliases for detected //
//////////////////////////
template<typename T>
using mapped_type_t = typename T::mapped_type;
template<typename T>
using key_type_t = typename T::key_type;
template<typename T>
using value_type_t = typename T::value_type;
template<typename T>
using difference_type_t = typename T::difference_type;
template<typename T>
using pointer_t = typename T::pointer;
template<typename T>
using reference_t = typename T::reference;
template<typename T>
using iterator_category_t = typename T::iterator_category;
template<typename T>
using iterator_t = typename T::iterator;
template<typename T, typename... Args>
using to_json_function = decltype(T::to_json(std::declval<Args>()...));
template<typename T, typename... Args>
using from_json_function = decltype(T::from_json(std::declval<Args>()...));
template<typename T, typename U>
using get_template_function = decltype(std::declval<T>().template get<U>());
// trait checking if JSONSerializer<T>::from_json(json const&, udt&) exists
template<typename BasicJsonType, typename T, typename = void>
struct has_from_json : std::false_type {};
// trait checking if j.get<T> 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 <typename BasicJsonType, typename T>
struct is_getable
{
static constexpr bool value = is_detected<get_template_function, const BasicJsonType&, T>::value;
};
template<typename BasicJsonType, typename T>
struct has_from_json < BasicJsonType, T, enable_if_t < !is_basic_json<T>::value >>
{
using serializer = typename BasicJsonType::template json_serializer<T, void>;
static constexpr bool value =
is_detected_exact<void, from_json_function, serializer,
const BasicJsonType&, T&>::value;
};
// This trait checks if JSONSerializer<T>::from_json(json const&) exists
// this overload is used for non-default-constructible user-defined-types
template<typename BasicJsonType, typename T, typename = void>
struct has_non_default_from_json : std::false_type {};
template<typename BasicJsonType, typename T>
struct has_non_default_from_json < BasicJsonType, T, enable_if_t < !is_basic_json<T>::value >>
{
using serializer = typename BasicJsonType::template json_serializer<T, void>;
static constexpr bool value =
is_detected_exact<T, from_json_function, serializer,
const BasicJsonType&>::value;
};
// This trait checks if BasicJsonType::json_serializer<T>::to_json exists
// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion.
template<typename BasicJsonType, typename T, typename = void>
struct has_to_json : std::false_type {};
template<typename BasicJsonType, typename T>
struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json<T>::value >>
{
using serializer = typename BasicJsonType::template json_serializer<T, void>;
static constexpr bool value =
is_detected_exact<void, to_json_function, serializer, BasicJsonType&,
T>::value;
};
///////////////////
// is_ functions //
///////////////////
// https://en.cppreference.com/w/cpp/types/conjunction
template<class...> struct conjunction : std::true_type { };
template<class B1> struct conjunction<B1> : B1 { };
template<class B1, class... Bn>
struct conjunction<B1, Bn...>
: std::conditional<bool(B1::value), conjunction<Bn...>, B1>::type {};
// 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 <typename T>
struct is_default_constructible : std::is_default_constructible<T> {};
template <typename T1, typename T2>
struct is_default_constructible<std::pair<T1, T2>>
: conjunction<is_default_constructible<T1>, is_default_constructible<T2>> {};
template <typename T1, typename T2>
struct is_default_constructible<const std::pair<T1, T2>>
: conjunction<is_default_constructible<T1>, is_default_constructible<T2>> {};
template <typename... Ts>
struct is_default_constructible<std::tuple<Ts...>>
: conjunction<is_default_constructible<Ts>...> {};
template <typename... Ts>
struct is_default_constructible<const std::tuple<Ts...>>
: conjunction<is_default_constructible<Ts>...> {};
template <typename T, typename... Args>
struct is_constructible : std::is_constructible<T, Args...> {};
template <typename T1, typename T2>
struct is_constructible<std::pair<T1, T2>> : is_default_constructible<std::pair<T1, T2>> {};
template <typename T1, typename T2>
struct is_constructible<const std::pair<T1, T2>> : is_default_constructible<const std::pair<T1, T2>> {};
template <typename... Ts>
struct is_constructible<std::tuple<Ts...>> : is_default_constructible<std::tuple<Ts...>> {};
template <typename... Ts>
struct is_constructible<const std::tuple<Ts...>> : is_default_constructible<const std::tuple<Ts...>> {};
template<typename T, typename = void>
struct is_iterator_traits : std::false_type {};
template<typename T>
struct is_iterator_traits<iterator_traits<T>>
{
private:
using traits = iterator_traits<T>;
public:
static constexpr auto value =
is_detected<value_type_t, traits>::value &&
is_detected<difference_type_t, traits>::value &&
is_detected<pointer_t, traits>::value &&
is_detected<iterator_category_t, traits>::value &&
is_detected<reference_t, traits>::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<typename T, typename = void>
struct is_complete_type : std::false_type {};
template<typename T>
struct is_complete_type<T, decltype(void(sizeof(T)))> : std::true_type {};
template<typename BasicJsonType, typename CompatibleObjectType,
typename = void>
struct is_compatible_object_type_impl : std::false_type {};
template<typename BasicJsonType, typename CompatibleObjectType>
struct is_compatible_object_type_impl <
BasicJsonType, CompatibleObjectType,
enable_if_t < is_detected<mapped_type_t, CompatibleObjectType>::value&&
is_detected<key_type_t, CompatibleObjectType>::value >>
{
using object_t = typename BasicJsonType::object_t;
// macOS's is_constructible does not play well with nonesuch...
static constexpr bool value =
is_constructible<typename object_t::key_type,
typename CompatibleObjectType::key_type>::value &&
is_constructible<typename object_t::mapped_type,
typename CompatibleObjectType::mapped_type>::value;
};
template<typename BasicJsonType, typename CompatibleObjectType>
struct is_compatible_object_type
: is_compatible_object_type_impl<BasicJsonType, CompatibleObjectType> {};
template<typename BasicJsonType, typename ConstructibleObjectType,
typename = void>
struct is_constructible_object_type_impl : std::false_type {};
template<typename BasicJsonType, typename ConstructibleObjectType>
struct is_constructible_object_type_impl <
BasicJsonType, ConstructibleObjectType,
enable_if_t < is_detected<mapped_type_t, ConstructibleObjectType>::value&&
is_detected<key_type_t, ConstructibleObjectType>::value >>
{
using object_t = typename BasicJsonType::object_t;
static constexpr bool value =
(is_default_constructible<ConstructibleObjectType>::value &&
(std::is_move_assignable<ConstructibleObjectType>::value ||
std::is_copy_assignable<ConstructibleObjectType>::value) &&
(is_constructible<typename ConstructibleObjectType::key_type,
typename object_t::key_type>::value &&
std::is_same <
typename object_t::mapped_type,
typename ConstructibleObjectType::mapped_type >::value)) ||
(has_from_json<BasicJsonType,
typename ConstructibleObjectType::mapped_type>::value ||
has_non_default_from_json <
BasicJsonType,
typename ConstructibleObjectType::mapped_type >::value);
};
template<typename BasicJsonType, typename ConstructibleObjectType>
struct is_constructible_object_type
: is_constructible_object_type_impl<BasicJsonType,
ConstructibleObjectType> {};
template<typename BasicJsonType, typename CompatibleStringType,
typename = void>
struct is_compatible_string_type_impl : std::false_type {};
template<typename BasicJsonType, typename CompatibleStringType>
struct is_compatible_string_type_impl <
BasicJsonType, CompatibleStringType,
enable_if_t<is_detected_exact<typename BasicJsonType::string_t::value_type,
value_type_t, CompatibleStringType>::value >>
{
static constexpr auto value =
is_constructible<typename BasicJsonType::string_t, CompatibleStringType>::value;
};
template<typename BasicJsonType, typename ConstructibleStringType>
struct is_compatible_string_type
: is_compatible_string_type_impl<BasicJsonType, ConstructibleStringType> {};
template<typename BasicJsonType, typename ConstructibleStringType,
typename = void>
struct is_constructible_string_type_impl : std::false_type {};
template<typename BasicJsonType, typename ConstructibleStringType>
struct is_constructible_string_type_impl <
BasicJsonType, ConstructibleStringType,
enable_if_t<is_detected_exact<typename BasicJsonType::string_t::value_type,
value_type_t, ConstructibleStringType>::value >>
{
static constexpr auto value =
is_constructible<ConstructibleStringType,
typename BasicJsonType::string_t>::value;
};
template<typename BasicJsonType, typename ConstructibleStringType>
struct is_constructible_string_type
: is_constructible_string_type_impl<BasicJsonType, ConstructibleStringType> {};
template<typename BasicJsonType, typename CompatibleArrayType, typename = void>
struct is_compatible_array_type_impl : std::false_type {};
template<typename BasicJsonType, typename CompatibleArrayType>
struct is_compatible_array_type_impl <
BasicJsonType, CompatibleArrayType,
enable_if_t < is_detected<value_type_t, CompatibleArrayType>::value&&
is_detected<iterator_t, CompatibleArrayType>::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<CompatibleArrayType >>::value >>
{
static constexpr bool value =
is_constructible<BasicJsonType,
typename CompatibleArrayType::value_type>::value;
};
template<typename BasicJsonType, typename CompatibleArrayType>
struct is_compatible_array_type
: is_compatible_array_type_impl<BasicJsonType, CompatibleArrayType> {};
template<typename BasicJsonType, typename ConstructibleArrayType, typename = void>
struct is_constructible_array_type_impl : std::false_type {};
template<typename BasicJsonType, typename ConstructibleArrayType>
struct is_constructible_array_type_impl <
BasicJsonType, ConstructibleArrayType,
enable_if_t<std::is_same<ConstructibleArrayType,
typename BasicJsonType::value_type>::value >>
: std::true_type {};
template<typename BasicJsonType, typename ConstructibleArrayType>
struct is_constructible_array_type_impl <
BasicJsonType, ConstructibleArrayType,
enable_if_t < !std::is_same<ConstructibleArrayType,
typename BasicJsonType::value_type>::value&&
is_default_constructible<ConstructibleArrayType>::value&&
(std::is_move_assignable<ConstructibleArrayType>::value ||
std::is_copy_assignable<ConstructibleArrayType>::value)&&
is_detected<value_type_t, ConstructibleArrayType>::value&&
is_detected<iterator_t, ConstructibleArrayType>::value&&
is_complete_type <
detected_t<value_type_t, ConstructibleArrayType >>::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<iterator_traits<ConstructibleArrayType>>::value &&
(std::is_same<typename ConstructibleArrayType::value_type,
typename BasicJsonType::array_t::value_type>::value ||
has_from_json<BasicJsonType,
typename ConstructibleArrayType::value_type>::value ||
has_non_default_from_json <
BasicJsonType, typename ConstructibleArrayType::value_type >::value);
};
template<typename BasicJsonType, typename ConstructibleArrayType>
struct is_constructible_array_type
: is_constructible_array_type_impl<BasicJsonType, ConstructibleArrayType> {};
template<typename RealIntegerType, typename CompatibleNumberIntegerType,
typename = void>
struct is_compatible_integer_type_impl : std::false_type {};
template<typename RealIntegerType, typename CompatibleNumberIntegerType>
struct is_compatible_integer_type_impl <
RealIntegerType, CompatibleNumberIntegerType,
enable_if_t < std::is_integral<RealIntegerType>::value&&
std::is_integral<CompatibleNumberIntegerType>::value&&
!std::is_same<bool, CompatibleNumberIntegerType>::value >>
{
// is there an assert somewhere on overflows?
using RealLimits = std::numeric_limits<RealIntegerType>;
using CompatibleLimits = std::numeric_limits<CompatibleNumberIntegerType>;
static constexpr auto value =
is_constructible<RealIntegerType,
CompatibleNumberIntegerType>::value &&
CompatibleLimits::is_integer &&
RealLimits::is_signed == CompatibleLimits::is_signed;
};
template<typename RealIntegerType, typename CompatibleNumberIntegerType>
struct is_compatible_integer_type
: is_compatible_integer_type_impl<RealIntegerType,
CompatibleNumberIntegerType> {};
template<typename BasicJsonType, typename CompatibleType, typename = void>
struct is_compatible_type_impl: std::false_type {};
template<typename BasicJsonType, typename CompatibleType>
struct is_compatible_type_impl <
BasicJsonType, CompatibleType,
enable_if_t<is_complete_type<CompatibleType>::value >>
{
static constexpr bool value =
has_to_json<BasicJsonType, CompatibleType>::value;
};
template<typename BasicJsonType, typename CompatibleType>
struct is_compatible_type
: is_compatible_type_impl<BasicJsonType, CompatibleType> {};
template<typename T1, typename T2>
struct is_constructible_tuple : std::false_type {};
template<typename T1, typename... Args>
struct is_constructible_tuple<T1, std::tuple<Args...>> : conjunction<is_constructible<T1, Args>...> {};
} // namespace detail
} // namespace nlohmann

13
lib/json/include/nlohmann/detail/meta/void_t.hpp

@ -0,0 +1,13 @@
#pragma once
namespace nlohmann
{
namespace detail
{
template<typename ...Ts> struct make_void
{
using type = void;
};
template<typename ...Ts> using void_t = typename make_void<Ts...>::type;
} // namespace detail
} // namespace nlohmann

1594
lib/json/include/nlohmann/detail/output/binary_writer.hpp

File diff suppressed because it is too large Load Diff

129
lib/json/include/nlohmann/detail/output/output_adapters.hpp

@ -0,0 +1,129 @@
#pragma once
#include <algorithm> // copy
#include <cstddef> // size_t
#include <ios> // streamsize
#include <iterator> // back_inserter
#include <memory> // shared_ptr, make_shared
#include <ostream> // basic_ostream
#include <string> // basic_string
#include <vector> // vector
#include <nlohmann/detail/macro_scope.hpp>
namespace nlohmann
{
namespace detail
{
/// abstract output adapter interface
template<typename CharType> struct output_adapter_protocol
{
virtual void write_character(CharType c) = 0;
virtual void write_characters(const CharType* s, std::size_t length) = 0;
virtual ~output_adapter_protocol() = default;
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<typename CharType>
using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;
/// output adapter for byte vectors
template<typename CharType>
class output_vector_adapter : public output_adapter_protocol<CharType>
{
public:
explicit output_vector_adapter(std::vector<CharType>& 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<CharType>& v;
};
/// output adapter for output streams
template<typename CharType>
class output_stream_adapter : public output_adapter_protocol<CharType>
{
public:
explicit output_stream_adapter(std::basic_ostream<CharType>& 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<std::streamsize>(length));
}
private:
std::basic_ostream<CharType>& stream;
};
/// output adapter for basic_string
template<typename CharType, typename StringType = std::basic_string<CharType>>
class output_string_adapter : public output_adapter_protocol<CharType>
{
public:
explicit output_string_adapter(StringType& s) 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<typename CharType, typename StringType = std::basic_string<CharType>>
class output_adapter
{
public:
output_adapter(std::vector<CharType>& vec)
: oa(std::make_shared<output_vector_adapter<CharType>>(vec)) {}
output_adapter(std::basic_ostream<CharType>& s)
: oa(std::make_shared<output_stream_adapter<CharType>>(s)) {}
output_adapter(StringType& s)
: oa(std::make_shared<output_string_adapter<CharType, StringType>>(s)) {}
operator output_adapter_t<CharType>()
{
return oa;
}
private:
output_adapter_t<CharType> oa = nullptr;
};
} // namespace detail
} // namespace nlohmann

954
lib/json/include/nlohmann/detail/output/serializer.hpp

@ -0,0 +1,954 @@
#pragma once
#include <algorithm> // reverse, remove, fill, find, none_of
#include <array> // array
#include <clocale> // localeconv, lconv
#include <cmath> // labs, isfinite, isnan, signbit
#include <cstddef> // size_t, ptrdiff_t
#include <cstdint> // uint8_t
#include <cstdio> // snprintf
#include <limits> // numeric_limits
#include <string> // string, char_traits
#include <type_traits> // is_same
#include <utility> // move
#include <nlohmann/detail/conversions/to_chars.hpp>
#include <nlohmann/detail/exceptions.hpp>
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/meta/cpp_future.hpp>
#include <nlohmann/detail/output/binary_writer.hpp>
#include <nlohmann/detail/output/output_adapters.hpp>
#include <nlohmann/detail/value_t.hpp>
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<typename BasicJsonType>
class serializer
{
using string_t = typename BasicJsonType::string_t;
using number_float_t = typename BasicJsonType::number_float_t;
using number_integer_t = typename BasicJsonType::number_integer_t;
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
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<char> 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<char>::to_char_type(* (loc->thousands_sep)))
, decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits<char>::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("<discarded>", 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<uint8_t>(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<std::uint16_t>(codepoint));
bytes += 6;
}
else
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
(std::snprintf)(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x",
static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)),
static_cast<std::uint16_t>(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<BasicJsonType, char>::to_char_type('\xEF');
string_buffer[bytes++] = detail::binary_writer<BasicJsonType, char>::to_char_type('\xBF');
string_buffer[bytes++] = detail::binary_writer<BasicJsonType, char>::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<std::uint8_t>(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<NumberType, number_unsigned_t>::value ||
std::is_same<NumberType, number_integer_t>::value ||
std::is_same<NumberType, binary_char_t>::value,
int > = 0 >
void dump_integer(NumberType x)
{
static constexpr std::array<std::array<char, 2>, 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<NumberType, number_integer_t>::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<number_integer_t>(x));
// account one more byte for the minus sign
n_chars = 1 + count_digits(abs_value);
}
else
{
abs_value = static_cast<number_unsigned_t>(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<unsigned>((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<unsigned>(abs_value);
*(--buffer_ptr) = digits_to_99[digits_index][1];
*(--buffer_ptr) = digits_to_99[digits_index][0];
}
else
{
*(--buffer_ptr) = static_cast<char>('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 <long double> == <double>.
static constexpr bool is_ieee_single_or_double
= (std::numeric_limits<number_float_t>::is_iec559 && std::numeric_limits<number_float_t>::digits == 24 && std::numeric_limits<number_float_t>::max_exponent == 128) ||
(std::numeric_limits<number_float_t>::is_iec559 && std::numeric_limits<number_float_t>::digits == 53 && std::numeric_limits<number_float_t>::max_exponent == 1024);
dump_float(x, std::integral_constant<bool, is_ieee_single_or_double>());
}
void dump_float(number_float_t x, std::true_type /*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<size_t>(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<number_float_t>::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<std::size_t>(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<std::size_t>(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 <bjoern@hoehrmann.de>
@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<std::uint8_t, 400> utf8d =
{
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 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<size_t>(state) * 16u + static_cast<size_t>(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<number_integer_t>::max)()); // NOLINT(misc-redundant-expression)
return static_cast<number_unsigned_t>(-(x + 1)) + 1;
}
private:
/// the output of the serializer
output_adapter_t<char> o = nullptr;
/// a (hopefully) large enough character buffer
std::array<char, 64> 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<char, 512> 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

63
lib/json/include/nlohmann/detail/string_escape.hpp

@ -0,0 +1,63 @@
#pragma once
#include <string>
#include <nlohmann/detail/macro_scope.hpp>
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

81
lib/json/include/nlohmann/detail/value_t.hpp

@ -0,0 +1,81 @@
#pragma once
#include <array> // array
#include <cstddef> // size_t
#include <cstdint> // uint8_t
#include <string> // 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<std::uint8_t, 9> 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<std::size_t>(lhs);
const auto r_index = static_cast<std::size_t>(rhs);
return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index];
}
} // namespace detail
} // namespace nlohmann

8942
lib/json/include/nlohmann/json.hpp

File diff suppressed because it is too large Load Diff

78
lib/json/include/nlohmann/json_fwd.hpp

@ -0,0 +1,78 @@
#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_
#define INCLUDE_NLOHMANN_JSON_FWD_HPP_
#include <cstdint> // int64_t, uint64_t
#include <map> // map
#include <memory> // allocator
#include <string> // string
#include <vector> // 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<typename T = void, typename SFINAE = void>
struct adl_serializer;
template<template<typename U, typename V, typename... Args> class ObjectType =
std::map,
template<typename U, typename... Args> class ArrayType = std::vector,
class StringType = std::string, class BooleanType = bool,
class NumberIntegerType = std::int64_t,
class NumberUnsignedType = std::uint64_t,
class NumberFloatType = double,
template<typename U> class AllocatorType = std::allocator,
template<typename T, typename SFINAE = void> class JSONSerializer =
adl_serializer,
class BinaryType = std::vector<std::uint8_t>>
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<typename BasicJsonType>
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<class Key, class T, class IgnoredLess, class Allocator>
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<nlohmann::ordered_map>;
} // namespace nlohmann
#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_

190
lib/json/include/nlohmann/ordered_map.hpp

@ -0,0 +1,190 @@
#pragma once
#include <functional> // less
#include <initializer_list> // initializer_list
#include <iterator> // input_iterator_tag, iterator_traits
#include <memory> // allocator
#include <stdexcept> // for out_of_range
#include <type_traits> // enable_if, is_convertible
#include <utility> // pair
#include <vector> // vector
#include <nlohmann/detail/macro_scope.hpp>
namespace nlohmann
{
/// ordered_map: a minimal map-like container that preserves insertion order
/// for use within nlohmann::basic_json<ordered_map>
template <class Key, class T, class IgnoredLess = std::less<Key>,
class Allocator = std::allocator<std::pair<const Key, T>>>
struct ordered_map : std::vector<std::pair<const Key, T>, Allocator>
{
using key_type = Key;
using mapped_type = T;
using Container = std::vector<std::pair<const Key, T>, 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 <class It>
ordered_map(It first, It last, const Allocator& alloc = Allocator())
: Container{first, last, alloc} {}
ordered_map(std::initializer_list<T> init, const Allocator& alloc = Allocator() )
: Container{init, alloc} {}
std::pair<iterator, bool> 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<iterator, bool> insert( value_type&& value )
{
return emplace(value.first, std::move(value.second));
}
std::pair<iterator, bool> 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<typename InputIt>
using require_input_iter = typename std::enable_if<std::is_convertible<typename std::iterator_traits<InputIt>::iterator_category,
std::input_iterator_tag>::value>::type;
template<typename InputIt, typename = require_input_iter<InputIt>>
void insert(InputIt first, InputIt last)
{
for (auto it = first; it != last; ++it)
{
insert(*it);
}
}
};
} // namespace nlohmann

2044
lib/json/include/nlohmann/thirdparty/hedley/hedley.hpp vendored

File diff suppressed because it is too large Load Diff

150
lib/json/include/nlohmann/thirdparty/hedley/hedley_undef.hpp vendored

@ -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

2
lib/libclangmm

@ -1 +1 @@
Subproject commit c807211edcd3894f0920fcc1c01476d898f93f8f
Subproject commit 8dca5d81af05d5184da92a2ca2ce175b5577de25

2
lib/tiny-process-library

@ -1 +1 @@
Subproject commit c5b0028fde831c1f5155aa2fe06a87e7c60ca124
Subproject commit 8bbb5a211c5c9df8ee69301da9d22fb977b27dc1

26
share/set_icon.sh

@ -0,0 +1,26 @@
#!/bin/sh
# Based on https://stackoverflow.com/a/8375093
# TODO: Rez, DeRez and SetFile are deprecated, but I have not yet found replacement tools
# Sets an icon on file or directory
# Usage setIcon.sh iconimage.jpg /path/to/[file|folder]
iconSource=$1
iconDestination=$2
icon=/tmp/`basename $iconSource`
rsrc=/tmp/juci.rsrc
# Create icon from the iconSource
cp $iconSource $icon
# Add icon to image file, meaning use itself as the icon
sips -i $icon > /dev/null
# Take that icon and put it into a rsrc file
DeRez -only icns $icon > $rsrc
# Apply the rsrc file to
SetFile -a C $iconDestination
# Append resource to the file you want to icon-ize.
Rez -append $rsrc -o $iconDestination
rm $rsrc $icon

4
share/set_icon_macos.py

@ -1,4 +0,0 @@
import Cocoa
import sys
Cocoa.NSWorkspace.sharedWorkspace().setIcon_forFile_options_(Cocoa.NSImage.alloc().initWithContentsOfFile_(sys.argv[1].decode('utf-8')), sys.argv[2].decode('utf-8'), 0) or sys.exit("Unable to set file icon")

12
src/CMakeLists.txt

@ -2,6 +2,8 @@
set(JUCI_SHARED_FILES
autocomplete.cpp
cmake.cpp
commands.cpp
config.cpp
compile_commands.cpp
ctags.cpp
dispatcher.cpp
@ -9,6 +11,7 @@ set(JUCI_SHARED_FILES
filesystem.cpp
git.cpp
grep.cpp
json.cpp
menu.cpp
meson.cpp
project_build.cpp
@ -28,6 +31,7 @@ set(JUCI_SHARED_FILES
utility.cpp
python_module.cc
config_module.cc
workers.cpp
)
if(LIBLLDB_FOUND)
list(APPEND JUCI_SHARED_FILES debug_lldb.cpp)
@ -45,10 +49,8 @@ target_link_libraries(juci_shared
tiny-process-library
)
set(JUCI_SOURCES
config.cpp
dialogs.cpp
dialogs_unix.cpp
set(JUCI_FILES
dialog.cpp
directories.cpp
entrybox.cpp
info.cpp
@ -87,7 +89,7 @@ if(${CMAKE_SYSTEM_NAME} MATCHES Linux|.*BSD|DragonFly)
install(FILES "${CMAKE_SOURCE_DIR}/share/juci.svg"
DESTINATION "${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/scalable/apps")
elseif(APPLE)
install(CODE "execute_process(COMMAND /usr/bin/python ${CMAKE_SOURCE_DIR}/share/set_icon_macos.py ${CMAKE_SOURCE_DIR}/share/juci.png ${CMAKE_INSTALL_PREFIX}/bin/juci)")
install(CODE "execute_process(COMMAND ${CMAKE_SOURCE_DIR}/share/set_icon.sh ${CMAKE_SOURCE_DIR}/share/juci.png ${CMAKE_INSTALL_PREFIX}/bin/juci)")
endif()
# add a target to generate API documentation with Doxygen

71
src/autocomplete.cpp

@ -1,9 +1,9 @@
#include "autocomplete.hpp"
#include "selection_dialog.hpp"
Autocomplete::Autocomplete(Gsv::View *view, bool &interactive_completion, guint &last_keyval, bool pass_buffer_and_strip_word)
: view(view), interactive_completion(interactive_completion), pass_buffer_and_strip_word(pass_buffer_and_strip_word) {
view->get_buffer()->signal_changed().connect([this, &last_keyval] {
Autocomplete::Autocomplete(Source::BaseView *view, bool &interactive_completion, guint &last_keyval, bool pass_buffer_and_strip_word, bool use_thread)
: view(view), interactive_completion(interactive_completion), pass_buffer_and_strip_word(pass_buffer_and_strip_word), use_thread(use_thread) {
view->get_buffer()->signal_insert().connect([this, &last_keyval](const Gtk::TextIter & /*iter*/, const Glib::ustring & /*text*/, int /*bytes*/) {
if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) {
cancel_reparse();
return;
@ -19,19 +19,30 @@ Autocomplete::Autocomplete(Gsv::View *view, bool &interactive_completion, guint
run();
}
});
view->get_buffer()->signal_erase().connect([this](const Gtk::TextIter & /*start*/, const Gtk::TextIter & /*end*/) {
if(CompletionDialog::get() && CompletionDialog::get()->is_visible()) {
cancel_reparse();
return;
}
if(!this->view->has_focus())
return;
stop();
});
view->get_buffer()->signal_mark_set().connect([this](const Gtk::TextIter &iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark> &mark) {
if(mark->get_name() == "insert")
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();
@ -54,23 +65,23 @@ void Autocomplete::run() {
before_add_rows();
if(thread.joinable())
if(use_thread && 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 && ((buffer[pos] >= 'a' && buffer[pos] <= 'z') || (buffer[pos] >= 'A' && buffer[pos] <= 'Z') ||
(buffer[pos] >= '0' && buffer[pos] <= '9') || buffer[pos] == '_')) {
while(pos >= 0 && view->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)] {
auto func = [this, line, line_index, buffer = std::move(buffer)] {
auto lock = get_parse_lock();
if(!is_processing())
return;
@ -78,12 +89,12 @@ void Autocomplete::run() {
rows.clear();
auto &buffer_raw = const_cast<std::string &>(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;
if(success) {
dispatcher.post([this]() {
auto func = [this]() {
after_add_rows();
if(state == State::restarting) {
state = State::idle;
@ -117,15 +128,27 @@ void Autocomplete::run() {
view->get_buffer()->begin_user_action();
CompletionDialog::get()->show();
}
});
};
if(use_thread)
dispatcher.post(std::move(func));
else
func();
}
else {
dispatcher.post([this] {
auto func = [this] {
state = State::canceled;
on_add_rows_error();
});
};
if(use_thread)
dispatcher.post(std::move(func));
else
func();
}
});
};
if(use_thread)
thread = std::thread(std::move(func));
else
func();
}
if(state != State::idle)
@ -155,7 +178,7 @@ void Autocomplete::setup_dialog() {
on_change(index, text);
if(!index) {
tooltips.hide();
clear_tooltips();
return;
}

19
src/autocomplete.hpp

@ -7,7 +7,7 @@
#include <thread>
class Autocomplete {
Gsv::View *view;
Source::BaseView *view;
bool &interactive_completion;
/// If view buffer should be passed to add_rows. Empty buffer is passed if not.
/// Also, some utilities, like libclang, require that autocomplete is started at the beginning of a word.
@ -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);
@ -34,7 +39,7 @@ public:
std::function<std::unique_ptr<LockGuard>()> get_parse_lock = [] { return nullptr; };
std::function<void()> stop_parse = [] {};
std::function<bool(guint last_keyval)> 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<bool(guint last_keyval)> is_continue_key = [this](guint keyval) { return view->is_token_char(gdk_keyval_to_unicode(keyval)); };
std::function<bool(guint last_keyval)> is_restart_key = [](guint) { return false; };
std::function<bool()> run_check = [] { return false; };
@ -42,8 +47,8 @@ public:
std::function<void()> after_add_rows = [] {};
std::function<void()> on_add_rows_error = [] {};
/// The handler is not run in the main loop. Should return false on error.
std::function<bool(std::string &buffer, int line_number, int column)> add_rows = [](std::string &, int, int) { return true; };
/// The handler is not run in the main loop if use_thread is true. Should return false on error.
std::function<bool(std::string &buffer, int line, int line_index)> add_rows = [](std::string &, int, int) { return true; };
std::function<void()> on_show = [] {};
std::function<void()> on_hide = [] {};
@ -51,12 +56,14 @@ public:
std::function<void(unsigned int, const std::string &, bool)> on_select;
std::function<std::function<void(Tooltip &tooltip)>(unsigned int)> set_tooltip_buffer = [](unsigned int index) { return nullptr; };
std::function<void()> clear_tooltips = [this] { tooltips.hide(); };
Autocomplete(Gsv::View *view, bool &interactive_completion, guint &last_keyval, bool pass_buffer_and_strip_word);
Autocomplete(Source::BaseView *view, bool &interactive_completion, guint &last_keyval, bool pass_buffer_and_strip_word, bool use_thread);
void run();
void stop();
private:
void setup_dialog();
bool use_thread;
};

129
src/cmake.cpp

@ -1,22 +1,24 @@
#include "cmake.hpp"
#include "compile_commands.hpp"
#include "config.hpp"
#include "dialogs.hpp"
#include "dialog.hpp"
#include "filesystem.hpp"
#include "json.hpp"
#include "terminal.hpp"
#include "utility.hpp"
#include <boost/algorithm/string.hpp>
#include <boost/optional.hpp>
#include <fstream>
#include <future>
#include <regex>
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)) {
const static std::regex project_regex("^ *project *\\(.*$", std::regex::icase | std::regex::optimize);
const static std::regex project_regex("^ *project *\\(.*\r?$", std::regex::icase | std::regex::optimize);
std::smatch sm;
if(std::regex_match(line, sm, project_regex))
return true;
@ -51,12 +53,12 @@ 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;
}
}
if(!force && boost::filesystem::exists(default_build_path / "compile_commands.json", ec))
if(!create_file_api_query(default_build_path) && !force && boost::filesystem::exists(default_build_path / "compile_commands.json", ec))
return true;
auto compile_commands_path = default_build_path / "compile_commands.json";
@ -64,25 +66,25 @@ bool CMake::update_default_build(const boost::filesystem::path &default_build_pa
Dialog::Message message("Creating/updating default build", [&canceled] {
canceled = true;
});
std::promise<int> promise;
boost::optional<int> 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) {
#ifdef _WIN32 //Temporary fix to MSYS2's libclang
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 &param) {
size_t pos = 0;
@ -112,7 +114,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;
}
}
@ -124,27 +126,32 @@ bool CMake::update_debug_build(const boost::filesystem::path &debug_build_path,
Dialog::Message message("Creating/updating debug build", [&canceled] {
canceled = true;
});
std::promise<int> promise;
boost::optional<int> 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) {
// Prefer the CMake file API (if available) which gives exact information about the targets a file belongs to
if(auto executable = get_executable_from_file_api(build_path, file_path))
return *executable;
// CMake does not store in compile_commands.json if an object is part of an executable or not.
// Therefore, executables are first attempted found in the cmake files. These executables
// are then used to identify if a file in compile_commands.json is part of an executable or not
@ -153,7 +160,7 @@ boost::filesystem::path CMake::get_executable(const boost::filesystem::path &bui
std::vector<std::pair<boost::filesystem::path, boost::filesystem::path>> source_files_and_maybe_executables;
for(auto &command : compile_commands.commands) {
auto source_file = filesystem::get_normal_path(command.file);
auto values = command.parameter_values("-o");
auto values = command.get_argument_values("-o");
if(!values.empty()) {
size_t pos;
if((pos = values[0].find("CMakeFiles/")) != std::string::npos)
@ -366,3 +373,85 @@ void CMake::init_module(pybind11::module &api) {
;
}
bool CMake::create_file_api_query(const boost::filesystem::path &build_path) {
auto query_directory = build_path / ".cmake" / "api" / "v1" / "query" / "client-jucipp";
auto query_file = query_directory / "query.json";
boost::system::error_code ec;
if(boost::filesystem::exists(query_file, ec))
return false;
boost::filesystem::create_directories(query_directory, ec);
if(ec) {
Terminal::get().print("\e[31mError\e[m: could not create cmake file api query directory " + filesystem::get_short_path(query_directory).string() + ": " + ec.message() + "\n", true);
return false;
}
if(!filesystem::write(query_file, R"({
"requests": [
{ "kind": "codemodel" , "version": 2 }
]
})")) {
Terminal::get().print("\e[31mError\e[m: could not create cmake file api query file " + filesystem::get_short_path(query_file).string() + ": " + ec.message() + "\n", true);
return false;
}
return true;
}
boost::optional<boost::filesystem::path> CMake::get_executable_from_file_api(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) {
auto reply_directory = build_path / ".cmake" / "api" / "v1" / "reply";
boost::system::error_code ec;
if(!boost::filesystem::is_directory(reply_directory, ec)) {
// If the reply directory does not exist, either CMake was not run or the file API is not yet supported
return {};
}
// Check all target-*.json files and filter for the given file path
std::vector<boost::filesystem::path> target_files;
for(const auto &reply_file : boost::filesystem::directory_iterator(reply_directory, ec)) {
auto reply_file_path = reply_file.path();
if(starts_with(reply_file_path.stem().string(), "target-"))
target_files.emplace_back(reply_file_path);
}
std::sort(target_files.begin(), target_files.end(), [](const boost::filesystem::path &path1, const boost::filesystem::path &path2) {
return Natural::compare(path1.string(), path2.string()) < 0;
});
boost::optional<boost::filesystem::path> executable;
ssize_t best_match_size = -1;
for(const auto &target_file : target_files) {
try {
JSON json(target_file);
if(json.string("type") != "EXECUTABLE")
continue;
auto artifacts = json.array("artifacts");
if(artifacts.empty())
continue;
auto relative_path = artifacts.front().string("path");
for(auto &source : json.array("sources")) {
auto source_file = project_path / source.string("path");
if(source_file == file_path)
return {build_path / relative_path};
auto source_directory = source_file.parent_path();
if(filesystem::file_in_path(file_path, source_directory)) {
auto size = std::distance(source_directory.begin(), source_directory.end());
if(size > best_match_size) {
best_match_size = size;
executable = build_path / relative_path;
}
}
}
}
catch(...) {
}
}
return executable;
}

6
src/cmake.hpp

@ -1,5 +1,6 @@
#pragma once
#include <boost/filesystem.hpp>
#include <boost/optional.hpp>
#include <list>
#include <map>
#include <vector>
@ -22,4 +23,9 @@ private:
std::list<std::string> parameters;
};
static void parse_file(const std::string &src, std::map<std::string, std::list<std::string>> &variables, std::function<void(Function &&)> &&on_function);
// Cmake file API functions
/// Returns true if file api query file was created, false otherwise including if the file already exists
bool create_file_api_query(const boost::filesystem::path &build_path);
boost::optional<boost::filesystem::path> get_executable_from_file_api(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path);
};

55
src/commands.cpp

@ -0,0 +1,55 @@
#include "commands.hpp"
#include "config.hpp"
#include "filesystem.hpp"
#include "json.hpp"
#include "terminal.hpp"
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": "<primary><shift>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. <path_match> is set to the matching file or directory, and <working_directory> is set to the project directory if found or the matching file's directory.",
"compile": "",
"run_comment": "<path_match> is set to the matching file or directory, and <working_directory> is set to the project directory if found or the matching file's directory",
"run": "echo <path_match> && echo <working_directory>",
"debug_comment": "Whether or not this command should run through debugger",
"debug": false,
"debug_remote_host": "",
"label_comment": "Output to the terminal instead of the run command",
"label": ""
}
]
)");
commands.clear();
try {
JSON commands_json(commands_file);
for(auto &command : commands_json.array()) {
auto key_string = command.string("key");
guint key = 0;
GdkModifierType modifier = static_cast<GdkModifierType>(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.string_or("path", "");
boost::optional<std::regex> regex;
if(!path.empty())
regex = std::regex(path, std::regex::optimize);
commands.emplace_back(Command{key, modifier, std::move(regex),
command.string_or("compile", ""), command.string("run"),
command.boolean_or("debug", false), command.string_or("debug_remote_host", ""),
command.string_or("label", "")});
}
}
catch(const std::exception &e) {
Terminal::get().async_print(std::string("\e[31mError\e[m: ") + e.what() + "\n", true);
}
}

30
src/commands.hpp

@ -0,0 +1,30 @@
#pragma once
#include <boost/optional.hpp>
#include <gdk/gdk.h>
#include <regex>
#include <string>
#include <vector>
class Commands {
public:
class Command {
public:
guint key;
GdkModifierType modifier;
boost::optional<std::regex> path;
std::string compile;
std::string run;
bool debug;
std::string debug_remote_host;
std::string label;
};
static Commands &get() {
static Commands instance;
return instance;
}
std::vector<Command> commands;
void load();
};

270
src/compile_commands.cpp

@ -2,213 +2,145 @@
#include "clangmm.hpp"
#include "config.hpp"
#include "python_type_casters.h"
#include <pybind11/stl.h>
#include "terminal.hpp"
#include "filesystem.hpp"
#include "json.hpp"
#include "utility.hpp"
#include <algorithm>
#include <boost/property_tree/json_parser.hpp>
#include <pybind11/stl.h>
#include <regex>
CompileCommands::FindSystemIncludePaths::FindSystemIncludePaths() {
std::stringstream stdin_stream, stdout_stream;
stdin_stream << "int main() {}";
exit_status = Terminal::get().process(stdin_stream, stdout_stream, "clang++ -v -x c++ -E 2>&1 -");
if(exit_status != 0)
return;
std::string line;
while(std::getline(stdout_stream, line)) {
if(starts_with(line, "#include <...> search starts here:")) {
while(std::getline(stdout_stream, line)) {
if(!line.empty() && line[0] == ' ') {
#ifdef _WIN32
if(line.back() == '\r')
line.pop_back();
#endif
auto end = line.find(" (framework directory)", 1);
if(end == std::string::npos)
include_paths.emplace_back(line.substr(1, end));
else
framework_paths.emplace_back(line.substr(1, end));
}
else
return;
}
return;
}
}
}
std::vector<std::string> CompileCommands::Command::parameter_values(const std::string &parameter_name) const {
std::vector<std::string> parameter_values;
std::vector<std::string> CompileCommands::Command::get_argument_values(const std::string &argument_name) const {
std::vector<std::string> argument_values;
bool found_argument = false;
for(auto &parameter : parameters) {
for(auto &argument : arguments) {
if(found_argument) {
parameter_values.emplace_back(parameter);
argument_values.emplace_back(argument);
found_argument = false;
}
else if(parameter == parameter_name)
else if(argument == argument_name)
found_argument = true;
}
return parameter_values;
return argument_values;
}
CompileCommands::CompileCommands(const boost::filesystem::path &build_path) {
try {
boost::property_tree::ptree root_pt;
boost::property_tree::json_parser::read_json((build_path / "compile_commands.json").string(), root_pt);
auto commands_pt = root_pt.get_child("");
for(auto &command : commands_pt) {
boost::filesystem::path directory = command.second.get<std::string>("directory");
auto parameters_str = command.second.get<std::string>("command");
boost::filesystem::path file = command.second.get<std::string>("file");
std::vector<std::string> parameters;
bool backslash = false;
bool single_quote = false;
bool double_quote = false;
size_t parameter_start_pos = std::string::npos;
size_t parameter_size = 0;
auto add_parameter = [&parameters, &parameters_str, &parameter_start_pos, &parameter_size] {
auto parameter = parameters_str.substr(parameter_start_pos, parameter_size);
// Remove escaping
for(size_t c = 0; c < parameter.size() - 1; ++c) {
if(parameter[c] == '\\')
parameter.replace(c, 2, std::string() + parameter[c + 1]);
}
parameters.emplace_back(parameter);
};
for(size_t c = 0; c < parameters_str.size(); ++c) {
if(backslash)
backslash = false;
else if(parameters_str[c] == '\\')
backslash = true;
else if((parameters_str[c] == ' ' || parameters_str[c] == '\t') && !backslash && !single_quote && !double_quote) {
if(parameter_start_pos != std::string::npos) {
add_parameter();
parameter_start_pos = std::string::npos;
parameter_size = 0;
}
continue;
}
else if(parameters_str[c] == '\'' && !backslash && !double_quote) {
single_quote = !single_quote;
continue;
}
else if(parameters_str[c] == '\"' && !backslash && !single_quote) {
double_quote = !double_quote;
continue;
}
if(parameter_start_pos == std::string::npos)
parameter_start_pos = c;
++parameter_size;
}
if(parameter_start_pos != std::string::npos)
add_parameter();
commands.emplace_back(Command{directory.make_preferred(), parameters, boost::filesystem::absolute(file, build_path).make_preferred()});
}
}
catch(...) {
clangmm::CompilationDatabase db(build_path.string());
if(db) {
clangmm::CompileCommands compile_commands({}, db);
for(auto &command : compile_commands.get_commands())
commands.emplace_back(Command{clangmm::to_string(clang_CompileCommand_getDirectory(command.cx_command)),
command.get_arguments(),
filesystem::get_absolute_path(clangmm::to_string(clang_CompileCommand_getFilename(command.cx_command)), build_path)});
}
}
std::vector<std::string> CompileCommands::get_arguments(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) {
std::string default_std_argument = "-std=c++1y";
auto extension = file_path.extension().string();
bool is_header = CompileCommands::is_header(file_path) || extension.empty(); // Include std C++ headers that are without extensions
// If header file, use source file flags if they are in the same folder
std::vector<boost::filesystem::path> file_paths;
if(is_header && !extension.empty()) {
auto parent_path = file_path.parent_path();
CompileCommands compile_commands(build_path);
for(auto &command : compile_commands.commands) {
if(command.file.parent_path() == parent_path)
file_paths.emplace_back(command.file);
}
}
if(file_paths.empty())
file_paths.emplace_back(file_path);
std::vector<std::string> arguments;
if(!build_path.empty()) {
clangmm::CompilationDatabase db(build_path.string());
if(db) {
for(auto &file_path : file_paths) {
std::vector<std::vector<std::string>> compile_commands_arguments;
// If header file, use source file flags if they are in the same folder
if(is_header && !extension.empty()) {
auto parent_path = file_path.parent_path();
clangmm::CompileCommands compile_commands({}, db);
for(auto &command : compile_commands.get_commands()) {
boost::filesystem::path file = filesystem::get_absolute_path(clangmm::to_string(clang_CompileCommand_getFilename(command.cx_command)), build_path);
if(file.parent_path() == parent_path)
compile_commands_arguments.emplace_back(command.get_arguments());
}
}
if(compile_commands_arguments.empty()) {
clangmm::CompileCommands compile_commands(file_path.string(), db);
auto commands = compile_commands.get_commands();
for(auto &command : commands) {
auto cmd_arguments = command.get_arguments();
bool ignore_next = false;
for(size_t c = 1; c + 1 < cmd_arguments.size(); c++) { // Exclude first and last argument
if(ignore_next)
ignore_next = false;
else if(cmd_arguments[c] == "-o" ||
cmd_arguments[c] == "-x" || // Remove language arguments since some tools add languages not understood by clang
(is_header && cmd_arguments[c] == "-include-pch") || // Header files should not use precompiled headers
cmd_arguments[c] == "-MF") { // Exclude dependency file generation
ignore_next = true;
}
else if(cmd_arguments[c] == "-c") {
}
else
arguments.emplace_back(cmd_arguments[c]);
for(auto &command : compile_commands.get_commands())
compile_commands_arguments.emplace_back(command.get_arguments());
}
for(auto &command_arguments : compile_commands_arguments) {
bool ignore_next = false;
for(size_t i = 1; i + 1 < command_arguments.size(); i++) { // Exclude first and last argument
if(ignore_next)
ignore_next = false;
else if(command_arguments[i] == "-o" ||
command_arguments[i] == "-x" || // Remove language arguments since some tools add languages not understood by clang
(is_header && command_arguments[i] == "-include-pch") || // Header files should not use precompiled headers
command_arguments[i] == "-MF") { // Exclude dependency file generation
ignore_next = true;
}
else if(command_arguments[i] == "-c") {
}
else if(command_arguments[i] == "--") // End of command options
break;
else
arguments.emplace_back(command_arguments[i]);
}
}
}
else
arguments.emplace_back(default_std_argument);
}
else
arguments.emplace_back(default_std_argument);
static FindSystemIncludePaths system_include_paths;
if(system_include_paths) {
for(auto &path : system_include_paths.include_paths)
arguments.emplace_back("-I" + path);
for(auto &path : system_include_paths.framework_paths)
arguments.emplace_back("-F" + path);
#ifdef _WIN32
auto clang_version_string = clangmm::to_string(clang_getClangVersion());
const static std::regex clang_version_regex(R"(^[A-Za-z ]+([0-9.]+).*$)", std::regex::optimize);
static std::string resource_path = []() -> std::string {
auto find_resource_path = [](const boost::filesystem::path &base_path, std::string version) -> std::string {
while(!version.empty() && version.front() != '.' && version.back() != '.') {
auto path = base_path / "clang" / version;
boost::system::error_code ec;
if(boost::filesystem::is_directory(path / "include", ec))
return path.string();
auto pos = version.rfind('.');
if(pos == std::string::npos)
break;
version.erase(pos);
}
return "";
};
// Try based on clang_getClangVersion first, even though its format is not guaranteed to be stable.
auto clang_version = clangmm::to_string(clang_getClangVersion());
std::regex clang_version_regex("([0-9]+\\.[0-9]+\\.[0-9]+)");
std::smatch sm;
if(std::regex_match(clang_version_string, sm, clang_version_regex)) {
auto clang_version = sm[1].str();
auto env_msystem_prefix = std::getenv("MSYSTEM_PREFIX");
if(env_msystem_prefix)
arguments.emplace_back("-I" + (boost::filesystem::path(env_msystem_prefix) / "lib/clang" / clang_version / "include").string());
if(std::regex_search(clang_version, sm, clang_version_regex)) {
auto version = sm[1].str();
auto path = find_resource_path(boost::filesystem::path(LIBCLANG_LIBRARY_DIR), version);
if(path.empty()) // For e.g. Fedora the LibClang is located in /usr/lib64/libclang.so while the resource dir is in /usr/lib/clang/<version>
path = find_resource_path(boost::filesystem::path(LIBCLANG_LIBRARY_DIR) / ".." / "lib", version);
if(!path.empty())
return path;
}
#endif
boost::system::error_code ec;
for(boost::filesystem::directory_iterator it(boost::filesystem::path(LIBCLANG_LIBRARY_DIR) / "clang", ec), end; it != end; ++it) {
if(boost::filesystem::is_directory(it->path() / "include", ec))
return it->path().string();
}
return {};
}();
if(!resource_path.empty()) {
arguments.emplace_back("-resource-dir");
arguments.emplace_back(resource_path);
}
else {
auto clang_version_string = clangmm::to_string(clang_getClangVersion());
const static std::regex clang_version_regex(R"(^[A-Za-z ]+([0-9.]+).*$)", std::regex::optimize);
std::smatch sm;
if(std::regex_match(clang_version_string, sm, clang_version_regex)) {
auto clang_version = sm[1].str();
arguments.emplace_back("-I/usr/lib/clang/" + clang_version + "/include");
arguments.emplace_back("-I/usr/lib64/clang/" + clang_version + "/include"); // For Fedora
#if defined(__APPLE__)
// Missing include and framework folders for MacOS:
arguments.emplace_back("-I/usr/local/opt/llvm/include/c++/v1");
arguments.emplace_back("-I/usr/local/opt/llvm/lib/clang/" + clang_version + "/include");
arguments.emplace_back("-I/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include");
arguments.emplace_back("-F/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks");
#endif
#ifdef _WIN32
auto env_msystem_prefix = std::getenv("MSYSTEM_PREFIX");
if(env_msystem_prefix)
arguments.emplace_back("-I" + (boost::filesystem::path(env_msystem_prefix) / "lib/clang" / clang_version / "include").string());
#endif
#ifdef __APPLE__
// Add -isysroot argument if it is missing on MacOS, which is needed by newer libclang
if(std::none_of(arguments.begin(), arguments.end(), [](const std::string &argument) { return argument == "-isysroot"; })) {
auto sysroots = {"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk",
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk"};
boost::system::error_code ec;
for(auto &sysroot : sysroots) {
if(boost::filesystem::exists(sysroot, ec)) {
arguments.emplace_back("-isysroot");
arguments.emplace_back(sysroot);
break;
}
}
}
#endif
// Do not add -fretain-comments-from-system-headers if pch is used, since the pch was most likely made without this flag
if(std::none_of(arguments.begin(), arguments.end(), [](const std::string &argument) { return argument == "-include-pch"; }))
@ -234,7 +166,7 @@ std::vector<std::string> CompileCommands::get_arguments(const boost::filesystem:
arguments.emplace_back("-finclude-default-header");
arguments.emplace_back("-Wno-gcc-compat");
}
else if(is_header)
else if(is_header && extension != ".h") // libclang crashes if it tries to parse .h files as C++ file in a C project
arguments.emplace_back("-xc++");
if(!build_path.empty()) {

15
src/compile_commands.hpp

@ -6,23 +6,12 @@
class CompileCommands {
public:
class FindSystemIncludePaths {
int exit_status;
public:
std::vector<std::string> include_paths;
std::vector<std::string> framework_paths;
operator bool() const { return exit_status == 0; }
FindSystemIncludePaths();
};
class Command {
public:
boost::filesystem::path directory;
std::vector<std::string> parameters;
std::vector<std::string> arguments;
boost::filesystem::path file;
std::vector<std::string> parameter_values(const std::string &parameter_name) const;
std::vector<std::string> get_argument_values(const std::string &argument_name) const;
};
CompileCommands(const boost::filesystem::path &build_path);

755
src/config.cpp

@ -1,11 +1,13 @@
#include "config.hpp"
#include "files.hpp"
#include "filesystem.hpp"
#include "process.hpp"
#include "terminal.hpp"
#include "utility.hpp"
#include <algorithm>
#include <boost/algorithm/string.hpp>
#include <exception>
#include <iostream>
#include <thread>
Config::Config() {
home_path = filesystem::get_home_path();
@ -21,77 +23,65 @@ 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());
boost::property_tree::ptree cfg;
boost::property_tree::json_parser::read_json(config_json.string(), cfg);
JSON cfg(config_json);
update(cfg);
read(cfg);
}
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;
boost::property_tree::ptree cfg;
boost::property_tree::read_json(ss, cfg);
read(cfg);
JSON default_cfg(default_config());
read(default_cfg);
}
}
void Config::update(boost::property_tree::ptree &cfg) {
boost::property_tree::ptree default_cfg;
bool cfg_ok = true;
if(cfg.get<std::string>("version") != JUCI_VERSION) {
std::stringstream ss;
ss << default_config_file;
boost::property_tree::read_json(ss, default_cfg);
cfg_ok = false;
auto it_version = cfg.find("version");
if(it_version != cfg.not_found()) {
make_version_dependent_corrections(cfg, default_cfg, it_version->second.data());
it_version->second.data() = JUCI_VERSION;
}
auto style_path = home_juci_path / "styles";
filesystem::write(style_path / "juci-light.xml", juci_light_style);
filesystem::write(style_path / "juci-dark.xml", juci_dark_style);
filesystem::write(style_path / "juci-dark-blue.xml", juci_dark_blue_style);
}
else
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)
boost::property_tree::write_json((home_juci_path / "config" / "config.json").string(), cfg);
JSON default_cfg(default_config());
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() == "<primary>p") {
if(version_compare(version, "1.2.4") <= 0) {
auto keybindings = cfg.object("keybindings");
auto print = keybindings.string_optional("print");
if(print && *print == "<primary>p") {
keybindings.set("print", "");
dispatcher.post([] {
::Terminal::get().print("Preference change: keybindings.print set to \"\"\n");
});
it_file_print->second.data() = "";
}
}
}
@ -100,122 +90,609 @@ 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<std::string>(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<std::string>(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<std::string>();
}
void Config::read(const JSON &cfg) {
for(auto &keybinding : cfg.children("keybindings"))
menu.keys[keybinding.first] = keybinding.second.string();
auto source_json = cfg.get_child("source");
source.style = source_json.get<std::string>("style");
source.font = source_json.get<std::string>("font");
source.cleanup_whitespace_characters = source_json.get<bool>("cleanup_whitespace_characters");
source.show_whitespace_characters = source_json.get<std::string>("show_whitespace_characters");
source.format_style_on_save = source_json.get<bool>("format_style_on_save");
source.format_style_on_save_if_style_file_found = source_json.get<bool>("format_style_on_save_if_style_file_found");
source.smart_brackets = source_json.get<bool>("smart_brackets");
source.smart_inserts = source_json.get<bool>("smart_inserts");
auto source_json = cfg.object("source");
source.style = source_json.string("style");
source.font = source_json.string("font");
source.add_missing_newline_at_end_of_file_on_save = source_json.boolean("add_missing_newline_at_end_of_file_on_save", JSON::ParseOptions::accept_string);
source.remove_trailing_whitespace_characters_on_save = source_json.boolean("remove_trailing_whitespace_characters_on_save", 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<bool>("show_map");
source.map_font_size = source_json.get<unsigned>("map_font_size");
source.show_git_diff = source_json.get<bool>("show_git_diff");
source.show_background_pattern = source_json.get<bool>("show_background_pattern");
source.show_right_margin = source_json.get<bool>("show_right_margin");
source.right_margin_position = source_json.get<unsigned>("right_margin_position");
source.spellcheck_language = source_json.get<std::string>("spellcheck_language");
source.default_tab_char = source_json.get<char>("default_tab_char");
source.default_tab_size = source_json.get<unsigned>("default_tab_size");
source.auto_tab_char_and_size = source_json.get<bool>("auto_tab_char_and_size");
source.tab_indents_line = source_json.get<bool>("tab_indents_line");
source.word_wrap = source_json.get<std::string>("word_wrap");
source.highlight_current_line = source_json.get<bool>("highlight_current_line");
source.show_line_numbers = source_json.get<bool>("show_line_numbers");
source.enable_multiple_cursors = source_json.get<bool>("enable_multiple_cursors");
source.auto_reload_changed_files = source_json.get<bool>("auto_reload_changed_files");
source.search_for_selection = source_json.get<bool>("search_for_selection");
source.clang_format_style = source_json.get<std::string>("clang_format_style");
source.clang_usages_threads = static_cast<unsigned>(source_json.get<int>("clang_usages_threads"));
source.clang_tidy_enable = source_json.get<bool>("clang_tidy_enable");
source.clang_tidy_checks = source_json.get<std::string>("clang_tidy_checks");
source.clang_detailed_preprocessing_record = source_json.get<bool>("clang_detailed_preprocessing_record");
source.debug_place_cursor_at_stop = source_json.get<bool>("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<std::string>("separator");
auto &queries = source.documentation_searches.find(pt_doc_search_lang.first)->second.queries;
for(auto &i : pt_doc_search_lang.second.get_child("queries")) {
queries[i.first] = i.second.get_value<std::string>();
}
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.pixels_between_lines = source_json.integer("pixels_between_lines", 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.tooltip_top_offset = source_json.integer("tooltip_top_offset", JSON::ParseOptions::accept_string);
source.clang_format_style = source_json.string("clang_format_style");
source.clang_usages_threads = static_cast<unsigned>(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();
}
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");
std::stringstream ss(project_json.string("open_with_default_application"));
std::string str;
project.open_with_default_application.clear();
while(getline(ss, str, ',')) {
while(!str.empty() && str.front() == ' ')
str.erase(str.begin());
project.open_with_default_application.emplace_back(std::move(str));
}
version = cfg.get<std::string>("version");
theme.name = cfg.get<std::string>("gtk_theme.name");
theme.variant = cfg.get<std::string>("gtk_theme.variant");
theme.font = cfg.get<std::string>("gtk_theme.font");
project.default_build_path = cfg.get<std::string>("project.default_build_path");
project.debug_build_path = cfg.get<std::string>("project.debug_build_path");
project.cmake.command = cfg.get<std::string>("project.cmake.command");
project.cmake.compile_command = cfg.get<std::string>("project.cmake.compile_command");
project.meson.command = cfg.get<std::string>("project.meson.command");
project.meson.compile_command = cfg.get<std::string>("project.meson.compile_command");
project.default_build_management_system = cfg.get<std::string>("project.default_build_management_system");
project.save_on_compile_or_run = cfg.get<bool>("project.save_on_compile_or_run");
project.ctags_command = cfg.get<std::string>("project.ctags_command");
project.grep_command = cfg.get<std::string>("project.grep_command");
project.cargo_command = cfg.get<std::string>("project.cargo_command");
project.python_command = cfg.get<std::string>("project.python_command");
project.markdown_command = cfg.get<std::string>("project.markdown_command");
terminal.history_size = cfg.get<int>("terminal.history_size");
terminal.font = cfg.get<std::string>("terminal.font");
terminal.clear_on_compile = cfg.get<bool>("terminal.clear_on_compile");
terminal.clear_on_run_command = cfg.get<bool>("terminal.clear_on_run_command");
log.libclang = cfg.get<bool>("log.libclang");
log.language_server = cfg.get<bool>("log.language_server");
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 plugins_path = cfg.get<std::string>("plugins.path");
boost::replace_all(plugins_path, "<juci_home_directory>", home_juci_path.string());
plugins.path = plugins_path;
plugins.enabled = cfg.get<bool>("plugins.enabled");
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);
}
std::string Config::default_config() {
static auto get_config = [] {
std::string cmake_version;
unsigned thread_count = 0;
std::stringstream ss;
TinyProcessLib::Config processConfig{};
#ifdef JUCI_FLATPAK_SANDBOX
processConfig.flatpak_spawn_host = true;
#endif
TinyProcessLib::Process process(
"cmake --version", "",
[&ss](const char *buffer, size_t n) {
ss.write(buffer, n);
},
[](const char *buffer, size_t n) {}, false, processConfig);
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(
"add_missing_newline_at_end_of_file_on_save": true,
"remove_trailing_whitespace_characters_on_save": 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,
"pixels_between_lines": 0,
"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,
"tooltip_top_offset": 0,
"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 <project_directory_name> to insert the project top level directory name",
"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": "<default_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",
"open_with_default_application_comment": "Comma-separated list of file extensions that should be opened with system default applications",
"open_with_default_application": ".pdf,.png"
},
"keybindings": {
"preferences": "<primary>comma",
"snippets": "",
"commands": "",
"quit": "<primary>q",
"file_new_file": "<primary>n",
"file_new_folder": "<primary><shift>n",
"file_open_file": "<primary>o",
"file_open_folder": "<primary><shift>o",
"file_find_file": "<primary>p",
"file_switch_file_type": "<alt>o",
"file_reload_file": "",
"file_save": "<primary>s",
"file_save_as": "<primary><shift>s",
"file_close_file": "<primary>w",
"file_close_folder": "",
"file_close_project": "",
"file_close_other_files": "",
"file_print": "",
"edit_undo": "<primary>z",
"edit_redo": "<primary><shift>z",
"edit_cut": "<primary>x",
"edit_cut_lines": "<primary><shift>x",
"edit_copy": "<primary>c",
"edit_copy_lines": "<primary><shift>c",
"edit_paste": "<primary>v",
"edit_extend_selection": "<primary><shift>a",
"edit_shrink_selection": "<primary><shift><alt>a",
"edit_show_or_hide": "",
"edit_find": "<primary>f",
"source_spellcheck": "",
"source_spellcheck_clear": "",
"source_spellcheck_next_error": "<primary><shift>e",
"source_git_next_diff": "<primary>k",
"source_git_show_diff": "<alt>k",
"source_indentation_set_buffer_tab": "",
"source_indentation_auto_indent_buffer": "<primary><shift>i",
"source_goto_line": "<primary>g",
"source_center_cursor": "<primary>l",
"source_cursor_history_back": "<alt>Left",
"source_cursor_history_forward": "<alt>Right",
"source_show_completion_comment": "Add completion keybinding to disable interactive autocompletion",
"source_show_completion": "",
"source_find_symbol": "<primary><shift>f",
"source_find_pattern": "<alt><shift>f",
"source_comments_toggle": "<primary>slash",
"source_comments_add_documentation": "<primary><alt>slash",
"source_find_documentation": "<primary><shift>d",
"source_goto_declaration": "<primary>d",
"source_goto_type_declaration": "<alt><shift>d",
"source_goto_implementation": "<primary>i",
"source_goto_usage": "<primary>u",
"source_goto_method": "<primary>m",
"source_rename": "<primary>r",
"source_implement_method": "<primary><shift>m",
"source_goto_next_diagnostic": "<primary>e",
"source_apply_fix_its": "<control>space",
"project_set_run_arguments": "",
"project_compile_and_run": "<primary>Return",
"project_compile": "<primary><shift>Return",
"project_run_command": "<alt>Return",
"project_kill_last_running": "<primary>Escape",
"project_force_kill_last_running": "<primary><shift>Escape",
"debug_set_run_arguments": "",
"debug_start_continue": "<primary>y",
"debug_stop": "<primary><shift>y",
"debug_kill": "<primary><shift>k",
"debug_step_over": "<primary>j",
"debug_step_into": "<primary>t",
"debug_step_out": "<primary><shift>t",
"debug_backtrace": "<primary><shift>j",
"debug_show_variables": "<primary><shift>b",
"debug_run_command": "<alt><shift>Return",
"debug_toggle_breakpoint": "<primary>b",
"debug_show_breakpoints": "<primary><shift><alt>b",
"debug_goto_stop": "<primary><shift>l",)RAW"
#ifdef __linux
R"RAW(
"window_next_tab": "<primary>Tab",
"window_previous_tab": "<primary><shift>Tab",)RAW"
#else
R"RAW(
"window_next_tab": "<primary><alt>Right",
"window_previous_tab": "<primary><alt>Left",)RAW"
#endif
R"RAW(
"window_goto_tab": "",
"window_toggle_split": "",
"window_split_source_buffer": "",)RAW"
#ifdef __APPLE__
R"RAW(
"window_toggle_full_screen": "<primary><control>f",)RAW"
#else
R"RAW(
"window_toggle_full_screen": "F11",)RAW"
#endif
R"RAW(
"window_toggle_directories": "",
"window_toggle_terminal": "",
"window_toggle_status": "",
"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(<?xml version="1.0" encoding="UTF-8"?>
<style-scheme id="juci-light" _name="juci" version="1.0">
<author>juCi++ team</author>
<_description>Default juCi++ style</_description>
<!-- Palette -->
<color name="white" value="#FFFFFF"/>
<color name="black" value="#000000"/>
<color name="gray" value="#888888"/>
<color name="red" value="#CC0000"/>
<color name="green" value="#008800"/>
<color name="blue" value="#0000FF"/>
<color name="dark-blue" value="#002299"/>
<color name="yellow" value="#FFFF00"/>
<color name="light-yellow" value="#FFFF88"/>
<color name="orange" value="#FF8800"/>
<color name="purple" value="#990099"/>
<style name="text" foreground="#000000" background="#FFFFFF"/>
<style name="background-pattern" background="#rgba(0,0,0,.03)"/>
<style name="selection" background="#4A90D9"/>
<!-- Current Line Highlighting -->
<style name="current-line" background="#rgba(0,0,0,.07)"/>
<!-- Bracket Matching -->
<style name="bracket-match" foreground="white" background="gray" bold="true"/>
<style name="bracket-mismatch" foreground="white" background="#FF0000" bold="true"/>
<!-- Search Matching -->
<style name="search-match" foreground="#000000" background="#FFFF00"/>
<!-- Language specifics -->
<style name="def:builtin" foreground="blue"/>
<style name="def:constant" foreground="blue"/>
<style name="def:boolean" foreground="red"/>
<style name="def:decimal" foreground="red"/>
<style name="def:base-n-integer" foreground="red"/>
<style name="def:floating-point" foreground="red"/>
<style name="def:complex" foreground="red"/>
<style name="def:character" foreground="red"/>
<style name="def:special-char" foreground="red"/>
<!-- Language specifics used by clang-parser in default config -->
<style name="def:string" foreground="red"/>
<style name="def:comment" foreground="gray"/>
<style name="def:statement" foreground="blue"/>
<style name="def:type" foreground="blue"/>
<style name="def:function" foreground="dark-blue"/>
<style name="def:identifier" foreground="purple"/>
<style name="def:preprocessor" foreground="green"/>
<style name="def:error" foreground="red"/>
<style name="def:warning" foreground="orange"/>
<style name="def:note" foreground="black" background="light-yellow"/>
<style name="diff:added-line" foreground="green"/>
<style name="diff:removed-line" foreground="red"/>
<style name="diff:changed-line" foreground="orange"/>
<style name="diff:diff-file" use-style="def:type"/>
<style name="diff:location" use-style="def:statement"/>
<style name="diff:special-case" use-style="def:constant"/>
</style-scheme>
)RAW";
}
const char *Config::juci_dark_style() {
return R"RAW(<?xml version="1.0" encoding="UTF-8"?>
<style-scheme id="juci-dark" _name="juci" version="1.0">
<author>juCi++ team</author>
<_description>Dark juCi++ style</_description>
<!-- Palette -->
<color name="white" value="#D6D6D6"/>
<color name="black" value="#202428"/>
<color name="gray" value="#919191"/>
<color name="red" value="#FF9999"/>
<color name="yellow" value="#EEEE66"/>
<color name="green" value="#AACC99"/>
<color name="blue" value="#88AAFF"/>
<color name="light-blue" value="#AABBEE"/>
<color name="purple" value="#DD99DD"/>
<style name="text" foreground="white" background="black"/>
<style name="background-pattern" background="#rgba(255,255,255,.04)"/>
<style name="selection" background="#215D9C"/>
<!-- Current Line Highlighting -->
<style name="current-line" background="#rgba(255,255,255,.05)"/>
<!-- Bracket Matching -->
<style name="bracket-match" foreground="black" background="gray" bold="true"/>
<style name="bracket-mismatch" foreground="black" background="#FF0000" bold="true"/>
<!-- Search Matching -->
<style name="search-match" foreground="#000000" background="#FFFF00"/>
<!-- Language specifics -->
<style name="def:builtin" foreground="blue"/>
<style name="def:constant" foreground="blue"/>
<style name="def:boolean" foreground="red"/>
<style name="def:decimal" foreground="red"/>
<style name="def:base-n-integer" foreground="red"/>
<style name="def:floating-point" foreground="red"/>
<style name="def:complex" foreground="red"/>
<style name="def:character" foreground="red"/>
<style name="def:special-char" foreground="red"/>
<!-- Language specifics used by clang-parser in default config -->
<style name="def:string" foreground="red"/>
<style name="def:comment" foreground="gray"/>
<style name="def:statement" foreground="blue"/>
<style name="def:type" foreground="blue"/>
<style name="def:function" foreground="light-blue"/>
<style name="def:identifier" foreground="purple"/>
<style name="def:preprocessor" foreground="green"/>
<style name="def:error" foreground="red"/>
<style name="def:warning" foreground="yellow"/>
<style name="def:note" foreground="#E6E6E6" background="#383F46"/>
<style name="diff:added-line" foreground="green"/>
<style name="diff:removed-line" foreground="red"/>
<style name="diff:changed-line" foreground="yellow"/>
<style name="diff:diff-file" use-style="def:type"/>
<style name="diff:location" use-style="def:statement"/>
<style name="diff:special-case" use-style="def:constant"/>
</style-scheme>
)RAW";
}
const char *Config::juci_dark_blue_style() {
return R"RAW(<?xml version="1.0" encoding="UTF-8"?>
<style-scheme id="juci-dark-blue" _name="juci" version="1.0">
<author>juCi++ team</author>
<_description>Dark blue juCi++ style based on the Emacs deeper blue theme</_description>
<!-- Palette -->
<color name="white" value="#D6D6D6"/>
<color name="dark-blue" value="#202233"/>
<color name="gray" value="#919191"/>
<color name="red" value="#FF7777"/>
<color name="yellow" value="#FFE100"/>
<color name="light-yellow" value="#EAC595"/>
<color name="blue" value="#00CCFF"/>
<color name="green" value="#14ECA8"/>
<color name="light-blue" value="#8BFAFF"/>
<color name="light-green" value="#A0DB6B"/>
<style name="text" foreground="white" background="dark-blue"/>
<style name="background-pattern" background="#rgba(255,255,255,.04)"/>
<style name="selection" background="#215D9C"/>
<!-- Current Line Highlighting -->
<style name="current-line" background="#rgba(255,255,255,.05)"/>
<!-- Bracket Matching -->
<style name="bracket-match" foreground="dark-blue" background="gray" bold="true"/>
<style name="bracket-mismatch" foreground="dark-blue" background="#FF0000" bold="true"/>
<!-- Search Matching -->
<style name="search-match" foreground="#000000" background="#FFFF00"/>
<!-- Language specifics -->
<style name="def:builtin" foreground="blue"/>
<style name="def:constant" foreground="blue"/>
<style name="def:boolean" foreground="light-yellow"/>
<style name="def:decimal" foreground="light-yellow"/>
<style name="def:base-n-integer" foreground="light-yellow"/>
<style name="def:floating-point" foreground="light-yellow"/>
<style name="def:complex" foreground="light-yellow"/>
<style name="def:character" foreground="light-yellow"/>
<style name="def:special-char" foreground="light-yellow"/>
<!-- Language specifics used by clang-parser in default config -->
<style name="def:string" foreground="light-yellow"/>
<style name="def:comment" foreground="gray"/>
<style name="def:statement" foreground="blue"/>
<style name="def:type" foreground="blue"/>
<style name="def:function" foreground="light-blue"/>
<style name="def:identifier" foreground="light-green"/>
<style name="def:preprocessor" foreground="yellow"/>
<style name="def:error" foreground="red"/>
<style name="def:warning" foreground="yellow"/>
<style name="def:note" foreground="#E6E6E6" background="#383C59"/>
<style name="diff:added-line" foreground="green"/>
<style name="diff:removed-line" foreground="red"/>
<style name="diff:changed-line" foreground="yellow"/>
<style name="diff:diff-file" use-style="def:type"/>
<style name="diff:location" use-style="def:statement"/>
<style name="diff:special-case" use-style="def:constant"/>
</style-scheme>
)RAW";
}

32
src/config.hpp

@ -1,7 +1,7 @@
#pragma once
#include "dispatcher.hpp"
#include "json.hpp"
#include <boost/filesystem.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <string>
#include <unordered_map>
#include <utility>
@ -28,6 +28,7 @@ public:
std::string font;
bool clear_on_compile;
bool clear_on_run_command;
bool hide_entry_on_run_command;
};
class Project {
@ -54,6 +55,7 @@ public:
std::string cargo_command;
std::string python_command;
std::string markdown_command;
std::vector<boost::filesystem::path> open_with_default_application;
};
class Source {
@ -68,7 +70,8 @@ public:
std::string font;
std::string spellcheck_language;
bool cleanup_whitespace_characters;
bool add_missing_newline_at_end_of_file_on_save;
bool remove_trailing_whitespace_characters_on_save;
std::string show_whitespace_characters;
bool format_style_on_save;
@ -83,6 +86,7 @@ public:
bool show_background_pattern;
bool show_right_margin;
unsigned right_margin_position;
unsigned pixels_between_lines;
bool auto_tab_char_and_size;
char default_tab_char;
@ -94,6 +98,7 @@ public:
bool enable_multiple_cursors;
bool auto_reload_changed_files;
bool search_for_selection;
int tooltip_top_offset;
std::string clang_format_style;
unsigned clang_usages_threads;
@ -125,13 +130,12 @@ private:
public:
static Config &get() {
static Config singleton;
return singleton;
static Config instance;
return instance;
}
void load();
std::string version;
Menu menu;
Theme theme;
Terminal terminal;
@ -148,9 +152,17 @@ 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);
/// If you add or remove nodes from the default_config, increase the juci
/// version number (JUCI_VERSION) in ../CMakeLists.txt to automatically apply
/// the changes to user's ~/.juci/config/config.json files
std::string default_config();
const char *juci_light_style();
const char *juci_dark_style();
const char *juci_dark_blue_style();
};

84
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"
@ -22,11 +23,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 *";
@ -36,8 +36,49 @@ 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::Config processConfig{};
#ifdef JUCI_FLATPAK_SANDBOX
processConfig.flatpak_spawn_host = true;
#endif
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);
},
false, processConfig);
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() {
@ -61,10 +102,24 @@ Ctags::Location Ctags::get_location(const std::string &line_, bool add_markup, b
auto symbol_end = line.find('\t');
if(symbol_end == std::string::npos)
return location;
location.symbol = line.substr(0, symbol_end);
// Unescape symbol
if(symbol_end > 0) {
location.symbol.reserve(symbol_end);
bool escaped = false;
for(size_t i = 0; i < symbol_end; ++i) {
if(!escaped && line[i] == '\\') {
escaped = true;
continue;
}
escaped = false;
location.symbol += line[i];
}
}
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(!((chr >= 'A' && chr <= 'Z') || (chr >= 'a' && chr <= 'z') || (chr >= '0' && chr <= '9') || chr == '_' || chr == '$' || static_cast<unsigned char>(chr) >= 128))
location.symbol.erase(8, 1);
}
@ -138,6 +193,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 +201,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(), "</b>");
location.source.insert(i + symbol_size, "</b>");
location.source.insert(i, "<b>");
i += 7 + symbol.size() - 1;
i += 7 + symbol_size - 1;
first = false;
}
else {
@ -173,13 +229,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<std::string> Ctags::get_type_parts(const std::string &type) {
std::vector<std::string> 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((chr >= 'A' && chr <= 'Z') || (chr >= 'a' && chr <= 'z') || (chr >= '0' && chr <= '9') || chr == '_' || chr == '$' || static_cast<unsigned char>(chr) >= 128 || chr == '~') {
if(text_start == std::string::npos)
text_start = c;
}
@ -202,7 +258,7 @@ std::vector<Ctags::Location> Ctags::get_locations(const boost::filesystem::path
if(!ctags)
return {};
//insert name into type
// insert name into type
size_t c = 0;
size_t bracket_count = 0;
for(; c < type.size(); ++c) {
@ -236,7 +292,7 @@ std::vector<Ctags::Location> Ctags::get_locations(const boost::filesystem::path
auto source_parts = get_type_parts(location.source);
//Find match score
// Find match score
long score = 0;
size_t source_index = 0;
for(auto &part : parts) {

74
src/debug_lldb.cpp

@ -1,8 +1,4 @@
#include "debug_lldb.hpp"
#include <cstdio>
#ifdef __APPLE__
#include <cstdlib>
#endif
#include "config.hpp"
#include "filesystem.hpp"
#include "process.hpp"
@ -21,19 +17,12 @@ void log(const char *msg, void *) {
std::cout << "debugger log: " << msg << std::endl;
}
Debug::LLDB::LLDB() : state(lldb::StateType::eStateInvalid), buffer_size(131072) {
if(!getenv("LLDB_DEBUGSERVER_PATH")) {
Debug::LLDB::LLDB() : listener("juCi++ lldb listener"), state(lldb::StateType::eStateInvalid), buffer_size(131072) {
#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);
#endif
}
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
}
}
void Debug::LLDB::destroy_() {
@ -65,7 +54,7 @@ std::tuple<std::vector<std::string>, std::string, std::vector<std::string>> 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') ||
@ -112,10 +101,9 @@ void Debug::LLDB::start(const std::string &command, const boost::filesystem::pat
initialized = true;
lldb::SBDebugger::Initialize();
debugger = std::make_unique<lldb::SBDebugger>(lldb::SBDebugger::Create(true, log, nullptr));
listener = std::make_unique<lldb::SBListener>("juCi++ lldb listener");
}
//Create executable string and argument array
// Create executable string and argument array
auto parsed_run_arguments = parse_run_arguments(command);
auto &environment_from_arguments = std::get<0>(parsed_run_arguments);
auto &executable = std::get<1>(parsed_run_arguments);
@ -131,18 +119,19 @@ 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("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;
}
//Set breakpoints
// 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,16 +141,16 @@ void Debug::LLDB::start(const std::string &command, const boost::filesystem::pat
lldb::SBError error;
if(!remote_host.empty()) {
auto connect_string = "connect://" + remote_host;
process = std::make_unique<lldb::SBProcess>(target.ConnectRemote(*listener, connect_string.c_str(), "gdb-remote", error));
process = std::make_unique<lldb::SBProcess>(target.ConnectRemote(listener, connect_string.c_str(), "gdb-remote", error));
if(error.Fail()) {
Terminal::get().async_print(std::string("Error (debug): ") + error.GetCString() + '\n', true);
Terminal::get().async_print(std::string("\e[31mError (debug)\e[m: ") + error.GetCString() + '\n', true);
for(auto &handler : on_exit)
handler(-1);
return;
}
lldb::SBEvent event;
while(true) {
if(listener->GetNextEvent(event)) {
if(listener.WaitForEvent(std::numeric_limits<uint32_t>::max(), event)) {
if((event.GetType() & lldb::SBProcess::eBroadcastBitStateChanged) > 0) {
auto state = process->GetStateFromEvent(event);
this->state = state;
@ -195,11 +184,11 @@ void Debug::LLDB::start(const std::string &command, const boost::filesystem::pat
environment.emplace_back(environ[c]);
environment.emplace_back(nullptr);
process = std::make_unique<lldb::SBProcess>(target.Launch(*listener, argv.data(), environment.data(), nullptr, nullptr, nullptr, path.string().c_str(), lldb::eLaunchFlagNone, false, error));
process = std::make_unique<lldb::SBProcess>(target.Launch(listener, argv.data(), environment.data(), nullptr, nullptr, nullptr, path.string().c_str(), lldb::eLaunchFlagNone, false, error));
}
if(error.Fail()) {
Terminal::get().async_print(std::string("Error (debug): ") + error.GetCString() + '\n', true);
Terminal::get().async_print(std::string("\e[31mError (debug)\e[m: ") + error.GetCString() + '\n', true);
for(auto &handler : on_exit)
handler(-1);
return;
@ -218,8 +207,8 @@ void Debug::LLDB::start(const std::string &command, const boost::filesystem::pat
debug_thread = std::thread([this]() {
lldb::SBEvent event;
while(true) {
LockGuard lock(mutex);
if(listener->GetNextEvent(event)) {
if(listener.WaitForEvent(std::numeric_limits<uint32_t>::max(), event)) {
LockGuard lock(mutex);
if((event.GetType() & lldb::SBProcess::eBroadcastBitStateChanged) > 0) {
auto state = process->GetStateFromEvent(event);
this->state = state;
@ -256,7 +245,7 @@ void Debug::LLDB::start(const std::string &command, const boost::filesystem::pat
while((n = process->GetSTDOUT(buffer, buffer_size)) != 0)
Terminal::get().async_print(std::string(buffer, n));
}
//TODO: for some reason stderr is redirected to stdout
// TODO: for some reason stderr is redirected to stdout
if((event.GetType() & lldb::SBProcess::eBroadcastBitSTDERR) > 0) {
char buffer[buffer_size];
size_t n;
@ -264,8 +253,6 @@ void Debug::LLDB::start(const std::string &command, const boost::filesystem::pat
Terminal::get().async_print(std::string(buffer, n), true);
}
}
lock.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
});
}
@ -281,7 +268,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);
}
}
@ -290,7 +277,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);
}
}
@ -375,18 +362,21 @@ std::vector<Debug::LLDB::Variable> 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<lldb::SBValue>(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();
@ -436,7 +426,7 @@ std::string Debug::LLDB::get_value(const std::string &variable, const boost::fil
auto frame = process->GetSelectedThread().GetSelectedFrame();
auto values = frame.GetVariables(true, true, true, false);
//First try to find variable based on name, file and line number
// First try to find variable based on name, file and line number
if(!file_path.empty()) {
for(uint32_t value_index = 0; value_index < values.GetSize(); value_index++) {
lldb::SBStream stream;
@ -536,7 +526,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);
}
}
@ -555,7 +545,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;
}
}

51
src/debug_lldb.hpp

@ -24,7 +24,7 @@ namespace Debug {
uint32_t thread_index_id;
uint32_t frame_index;
std::string name;
std::string value;
std::function<std::string()> get_value;
bool declaration_found;
boost::filesystem::path file_path;
int line_nr;
@ -33,14 +33,14 @@ namespace Debug {
private:
LLDB();
void destroy_();
void destroy_() EXCLUDES(mutex);
static bool initialized;
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)
@ -59,39 +59,38 @@ namespace Debug {
void start(const std::string &command, const boost::filesystem::path &path = "",
const std::vector<std::pair<boost::filesystem::path, int>> &breakpoints = {},
const std::vector<std::string> &startup_commands = {}, const std::string &remote_host = "");
void continue_debug(); //can't use continue as function name
void stop();
void kill();
void step_over();
void step_into();
void step_out();
std::pair<std::string, std::string> run_command(const std::string &command);
std::vector<Frame> get_backtrace();
std::vector<Variable> get_variables();
void select_frame(uint32_t frame_index, uint32_t thread_index_id = 0);
const std::vector<std::string> &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<std::string, std::string> run_command(const std::string &command) EXCLUDES(mutex);
std::vector<Frame> get_backtrace() EXCLUDES(mutex);
std::vector<Variable> 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 write(const std::string &buffer);
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) EXCLUDES(mutex);
static void init_module(py::module &api);
private:
std::tuple<std::vector<std::string>, std::string, std::vector<std::string>> parse_run_arguments(const std::string &command);
lldb::SBListener listener;
std::unique_ptr<lldb::SBDebugger> debugger GUARDED_BY(mutex);
std::unique_ptr<lldb::SBListener> listener GUARDED_BY(mutex);
std::unique_ptr<lldb::SBProcess> process GUARDED_BY(mutex);
std::thread debug_thread;

73
src/dialogs.cpp → src/dialog.cpp

@ -1,22 +1,30 @@
#include "dialogs.hpp"
#include "dialog.hpp"
#include "filesystem.hpp"
#include <cmath>
Dialog::Message::Message(const std::string &text, std::function<void()> &&on_cancel, bool show_progress_bar) : Gtk::Window(Gtk::WindowType::WINDOW_POPUP) {
auto g_application = g_application_get_default();
auto gio_application = Glib::wrap(g_application, true);
auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application);
set_transient_for(*application->get_active_window());
set_transient_for(*Glib::RefPtr<Gtk::Application>::cast_dynamic(Gtk::Application::get_default())->get_active_window());
set_position(Gtk::WindowPosition::WIN_POS_CENTER_ON_PARENT);
set_modal(true);
set_type_hint(Gdk::WindowTypeHint::WINDOW_TYPE_HINT_NOTIFICATION);
property_decorated() = false;
set_skip_taskbar_hint(true);
auto box = Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL));
auto visual = get_screen()->get_rgba_visual();
if(visual)
gtk_widget_set_visual(reinterpret_cast<GtkWidget *>(gobj()), visual->gobj());
get_style_context()->add_class("juci_message_window");
auto vbox = Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL));
vbox->get_style_context()->add_class("juci_message_box");
auto hbox = Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_HORIZONTAL));
auto image = Gtk::manage(new Gtk::Image());
image->set_from_icon_name("dialog-information", Gtk::BuiltinIconSize::ICON_SIZE_BUTTON);
hbox->pack_start(*image, Gtk::PackOptions::PACK_SHRINK);
auto label = Gtk::manage(new Gtk::Label(text));
label->set_padding(10, 10);
box->pack_start(*label);
label->set_padding(7, 7);
hbox->pack_start(*label);
vbox->pack_start(*hbox);
if(on_cancel) {
auto cancel_button = Gtk::manage(new Gtk::Button("Cancel"));
cancel_button->signal_clicked().connect([label, on_cancel = std::move(on_cancel)] {
@ -24,12 +32,11 @@ Dialog::Message::Message(const std::string &text, std::function<void()> &&on_can
if(on_cancel)
on_cancel();
});
box->pack_start(*cancel_button);
vbox->pack_start(*cancel_button);
}
if(show_progress_bar)
box->pack_start(progress_bar);
add(*box);
vbox->pack_start(progress_bar);
add(*vbox);
show_all_children();
show_now();
@ -73,10 +80,7 @@ std::string Dialog::gtk_dialog(const boost::filesystem::path &path, const std::s
Gtk::FileChooserDialog dialog(title, action);
#endif
auto g_application = g_application_get_default();
auto gio_application = Glib::wrap(g_application, true);
auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application);
dialog.set_transient_for(*application->get_active_window());
dialog.set_transient_for(*Glib::RefPtr<Gtk::Application>::cast_dynamic(Gtk::Application::get_default())->get_active_window());
dialog.set_position(Gtk::WindowPosition::WIN_POS_CENTER_ON_PARENT);
if(title == "Save File As")
@ -84,9 +88,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<GtkFileChooser *>(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<GtkFileChooser *>(dialog.gobj()), current_path.string().c_str());
}
@ -111,3 +114,33 @@ void Dialog::init_module(py::module &api) {
;
}
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);
}

15
src/dialogs.hpp → src/dialog.hpp

@ -7,13 +7,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);
static void init_module(py::module &api);
class Message : public Gtk::Window {
public:
Message(const std::string &text, std::function<void()> &&on_cancel = {}, bool show_progrss_bar = false);
@ -30,4 +23,12 @@ private:
static std::string gtk_dialog(const boost::filesystem::path &path, const std::string &title,
const std::vector<std::pair<std::string, Gtk::ResponseType>> &buttons,
Gtk::FileChooserAction gtk_options);
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);
static void init_module(py::module &api);
};

31
src/dialogs_unix.cpp

@ -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);
}

163
src/dialogs_win.cpp

@ -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 <shobjidl.h>
#include <vector>
#include <windows.h>
#include <iostream> //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<COMDLG_FILTERSPEC> extensions;
if(!file_path.empty()) {
if(file_path.has_extension() && file_path.filename() != file_path.extension()) {
auto extension = (L"*" + file_path.extension().native()).c_str();
extensions.emplace_back(COMDLG_FILTERSPEC{extension, extension});
if(!set_default_file_extension(extension))
return "";
}
}
extensions.emplace_back(COMDLG_FILTERSPEC{L"All files", L"*.*"});
if(dialog->SetFileTypes(extensions.size(), extensions.data()) != S_OK)
return "";
return show();
}
private:
IFileDialog *dialog = nullptr;
DWORD options;
bool init(CLSID type) {
if(CoCreateInstance(type, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&dialog)) != S_OK)
return false;
if(dialog->GetOptions(&options) != S_OK)
return false;
return true;
}
/** available options are listed at https://msdn.microsoft.com/en-gb/library/windows/desktop/dn457282(v=vs.85).aspx */
bool add_option(unsigned option) {
if(dialog->SetOptions(options | option) != S_OK)
return false;
return true;
}
bool set_title(const std::wstring &title) {
if(dialog->SetTitle(title.c_str()) != S_OK)
return false;
return true;
}
/** Sets the extensions the browser can find */
bool set_default_file_extension(const std::wstring &file_extension) {
if(dialog->SetDefaultExtension(file_extension.c_str()) != S_OK)
return false;
return true;
}
/** Sets the directory to start browsing */
bool set_folder() {
auto g_application = g_application_get_default(); //TODO: Post issue that Gio::Application::get_default should return pointer and not Glib::RefPtr
auto gio_application = Glib::wrap(g_application, true);
auto application = Glib::RefPtr<Application>::cast_static(gio_application);
auto current_path = application->window->notebook.get_current_folder();
boost::system::error_code ec;
if(current_path.empty())
current_path = boost::filesystem::current_path(ec);
if(ec)
return false;
std::wstring path = current_path.native();
size_t pos = 0;
while((pos = path.find(L'/', pos)) != std::wstring::npos) { //TODO: issue bug report on boost::filesystem::path::native on MSYS2
path.replace(pos, 1, L"\\");
pos++;
}
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);
}

398
src/directories.cpp

@ -1,10 +1,14 @@
#include "directories.hpp"
#include "config.hpp"
#include "entrybox.hpp"
#include "filesystem.hpp"
#include "info.hpp"
#include "notebook.hpp"
#include "project.hpp"
#include "source.hpp"
#include "terminal.hpp"
#include "utility.hpp"
#include "window.hpp"
#include <algorithm>
bool Directories::TreeStore::row_drop_possible_vfunc(const Gtk::TreeModel::Path &path, const Gtk::SelectionData &selection_data) const {
@ -56,7 +60,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;
}
@ -103,8 +107,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);
@ -121,111 +123,6 @@ Directories::Directories() : Gtk::ListViewText(1) {
tree_store->set_sort_column(column_record.name, Gtk::SortType::SORT_ASCENDING);
tree_store->set_sort_func(column_record.name, [this](const Gtk::TreeModel::iterator &it1, const Gtk::TreeModel::iterator &it2) {
/// Natural comparison supporting UTF-8 and locale
struct Natural {
static bool is_digit(char chr) {
return chr >= '0' && chr <= '9';
}
static int compare_characters(size_t &i1, size_t &i2, const std::string &s1, const std::string &s2) {
ScopeGuard scope_guard{[&i1, &i2] {
++i1;
++i2;
}};
auto c1 = static_cast<unsigned char>(s1[i1]);
auto c2 = static_cast<unsigned char>(s2[i2]);
if(c1 < 0b10000000 && c2 < 0b10000000) { // Both characters are ascii
auto at = std::tolower(s1[i1]);
auto bt = std::tolower(s2[i2]);
if(at < bt)
return -1;
else if(at == bt)
return 0;
else
return 1;
}
Glib::ustring u1;
if(c1 >= 0b11110000)
u1 = s1.substr(i1, 4);
else if(c1 >= 0b11100000)
u1 = s1.substr(i1, 3);
else if(c1 >= 0b11000000)
u1 = s1.substr(i1, 2);
else
u1 = s1[i1];
Glib::ustring u2;
if(c2 >= 0b11110000)
u2 = s2.substr(i2, 4);
else if(c2 >= 0b11100000)
u2 = s2.substr(i2, 3);
else if(c2 >= 0b11000000)
u2 = s2.substr(i2, 2);
else
u2 = s2[i2];
i1 += u1.bytes() - 1;
i2 += u2.bytes() - 1;
u1 = u1.lowercase();
u2 = u2.lowercase();
if(u1 < u2)
return -1;
else if(u1 == u2)
return 0;
else
return 1;
}
static int compare_numbers(size_t &i1, size_t &i2, const std::string &s1, const std::string &s2) {
int result = 0;
while(true) {
if(i1 >= s1.size() || !is_digit(s1[i1])) {
if(i2 >= s2.size() || !is_digit(s2[i2])) // a and b has equal number of digits
return result;
return -1; // a has fewer digits
}
if(i2 >= s2.size() || !is_digit(s2[i2]))
return 1; // b has fewer digits
if(result == 0) {
if(s1[i1] < s2[i2])
result = -1;
if(s1[i1] > s2[i2])
result = 1;
}
++i1;
++i2;
}
}
static int compare(const std::string &s1, const std::string &s2) {
size_t i1 = 0;
size_t i2 = 0;
while(i1 < s1.size() && i2 < s2.size()) {
if(is_digit(s1[i1]) && !is_digit(s2[i2]))
return -1;
if(!is_digit(s1[i1]) && is_digit(s2[i2]))
return 1;
if(!is_digit(s1[i1]) && !is_digit(s2[i2])) {
auto result = compare_characters(i1, i2, s1, s2);
if(result != 0)
return result;
}
else {
auto result = compare_numbers(i1, i2, s1, s2);
if(result != 0)
return result;
}
}
if(i1 >= s1.size())
return -1;
return 1;
}
};
auto name1 = it1->get_value(column_record.name);
auto name2 = it2->get_value(column_record.name);
if(name1.empty())
@ -242,7 +139,7 @@ Directories::Directories() : Gtk::ListViewText(1) {
return Natural::compare(prefix1 + name1, prefix2 + name2);
});
set_enable_search(true); //TODO: why does this not work in OS X?
set_enable_search(true); // TODO: why does this not work in OS X?
set_search_column(column_record.name);
signal_row_activated().connect([this](const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *column) {
@ -250,9 +147,31 @@ Directories::Directories() : Gtk::ListViewText(1) {
if(iter) {
auto filesystem_path = iter->get_value(column_record.path);
if(filesystem_path != "") {
GdkModifierType state;
boost::system::error_code ec;
if(boost::filesystem::is_directory(boost::filesystem::path(filesystem_path), ec))
row_expanded(path) ? collapse_row(path) : expand_row(path, false);
else if(gtk_get_current_event_state(&state) && (state &
#ifdef __APPLE__
GDK_MOD2_MASK
#else
GDK_CONTROL_MASK
#endif
)) {
if(Project::compiling || Project::debugging) {
Info::get().print("Compile or debug in progress");
return;
}
Project::current = Project::create(filesystem_path);
if(Config::get().project.save_on_compile_or_run)
Project::save_files(!Project::current->build->project_path.empty() ? Project::current->build->project_path : filesystem_path.parent_path());
Project::current->compile_and_run(filesystem_path);
set_cursor(path);
}
else
Notebook::get().open(filesystem_path);
}
@ -287,12 +206,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 +250,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;
}
@ -358,7 +277,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] {
@ -372,7 +291,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 +300,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();
@ -404,15 +323,10 @@ 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)
Terminal::get().print("\e[33mWarning\e[m: language for " + target_path.string() + " has changed. Please reopen the file\n");
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");
}
}
@ -438,9 +352,13 @@ Directories::Directories() : Gtk::ListViewText(1) {
menu_item_delete.signal_activate().connect([this] {
if(menu_popup_row_path.empty())
return;
Gtk::MessageDialog dialog(*static_cast<Gtk::Window *>(get_toplevel()), "Delete!", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO);
Gtk::MessageDialog dialog(*static_cast<Gtk::Window *>(get_toplevel()), "Delete?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO);
Gtk::Image image;
image.set_from_icon_name("dialog-warning", Gtk::BuiltinIconSize::ICON_SIZE_DIALOG);
dialog.set_image(image);
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() + "?");
dialog.show_all();
int result = dialog.run();
if(result == Gtk::RESPONSE_YES) {
boost::system::error_code ec;
@ -448,7 +366,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;
}
@ -468,6 +386,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 " + filesystem::escape_argument(menu_popup_row_path.string()), "", nullptr, true);
#else
Terminal::get().async_process("xdg-open " + filesystem::escape_argument(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);
@ -489,13 +429,13 @@ Directories::Directories() : Gtk::ListViewText(1) {
}
Directories::~Directories() {
thread_pool.shutdown(true);
worker.stop();
}
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;
}
@ -503,7 +443,7 @@ void Directories::open(const boost::filesystem::path &dir_path) {
path = filesystem::get_normal_path(dir_path);
//TODO: report that set_title does not handle '_' correctly?
// TODO: report that set_title does not handle '_' correctly?
auto title = path.filename().string();
size_t pos = 0;
while((pos = title.find('_', pos)) != std::string::npos) {
@ -511,6 +451,7 @@ void Directories::open(const boost::filesystem::path &dir_path) {
pos += 2;
}
get_column(0)->set_title(title);
Window::get().set_title(path);
for(auto &directory : directories) {
if(directory.second.repository)
@ -519,6 +460,9 @@ void Directories::open(const boost::filesystem::path &dir_path) {
directories.clear();
add_or_update_path(path, Gtk::TreeModel::Row(), true);
if(auto view = Notebook::get().get_current_view())
view->update_status_file_path(view);
}
void Directories::close(const boost::filesystem::path &dir_path) {
@ -528,9 +472,13 @@ void Directories::close(const boost::filesystem::path &dir_path) {
tree_store->clear();
path.clear();
get_column(0)->set_title("");
Window::get().set_title();
}
else
remove_path(dir_path);
if(auto view = Notebook::get().get_current_view())
view->update_status_file_path(view);
}
void Directories::update() {
@ -557,7 +505,7 @@ void Directories::select(const boost::filesystem::path &select_path) {
if(!filesystem::file_in_path(select_path, path))
return;
//return if the select_path is already selected
// return if the select_path is already selected
auto iter = get_selection()->get_selected();
if(iter) {
if(iter->get_value(column_record.path) == select_path)
@ -572,9 +520,9 @@ void Directories::select(const boost::filesystem::path &select_path) {
else
parent_path = select_path.parent_path();
//check if select_path is already expanded
// check if select_path is already expanded
if(directories.find(parent_path.string()) != directories.end()) {
//set cursor at select_path and return
// set cursor at select_path and return
tree_store->foreach_iter([this, &select_path](const Gtk::TreeModel::iterator &iter) {
if(iter->get_value(column_record.path) == select_path) {
auto tree_path = Gtk::TreePath(iter);
@ -593,7 +541,7 @@ void Directories::select(const boost::filesystem::path &select_path) {
paths.emplace_front(parent_path);
}
//expand to select_path
// expand to select_path
for(auto &a_path : paths) {
tree_store->foreach_iter([this, &a_path](const Gtk::TreeModel::iterator &iter) {
if(iter->get_value(column_record.path) == a_path) {
@ -604,7 +552,7 @@ void Directories::select(const boost::filesystem::path &select_path) {
});
}
//set cursor at select_path
// set cursor at select_path
tree_store->foreach_iter([this, &select_path](const Gtk::TreeModel::iterator &iter) {
if(iter->get_value(column_record.path) == select_path) {
auto tree_path = Gtk::TreePath(iter);
@ -674,11 +622,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(
[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;
},
500);
}
});
@ -694,11 +644,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);
}
});
}
@ -724,8 +676,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) {
@ -787,86 +743,96 @@ void Directories::remove_path(const boost::filesystem::path &dir_path) {
}
}
void Directories::colorize_path(boost::filesystem::path dir_path_, bool include_parent_paths) {
auto dir_path = std::make_shared<boost::filesystem::path>(std::move(dir_path_));
auto it = directories.find(dir_path->string());
void Directories::colorize_path(boost::filesystem::path dir_path, bool include_parent_paths) {
auto it = directories.find(dir_path.string());
if(it == directories.end())
return;
if(it != directories.end() && it->second.repository) {
auto repository = it->second.repository;
thread_pool.push([this, dir_path, repository, include_parent_paths] {
boost::system::error_code ec;
if(!boost::filesystem::exists(dir_path, ec)) {
directories.erase(it);
return;
}
auto colorize = [this, dir_path = std::move(dir_path), include_parent_paths](const Git::Repository::Status &status = {}) {
auto it = directories.find(dir_path.string());
if(it == directories.end())
return;
auto normal_color = get_style_context()->get_color(Gtk::StateFlags::STATE_FLAG_NORMAL);
Gdk::RGBA yellow;
yellow.set_rgba(1.0, 1.0, 0.2);
double factor = 0.5;
yellow.set_red(normal_color.get_red() + factor * (yellow.get_red() - normal_color.get_red()));
yellow.set_green(normal_color.get_green() + factor * (yellow.get_green() - normal_color.get_green()));
yellow.set_blue(normal_color.get_blue() + factor * (yellow.get_blue() - normal_color.get_blue()));
Gdk::RGBA green;
green.set_rgba(0.0, 1.0, 0.0);
factor = 0.4;
green.set_red(normal_color.get_red() + factor * (green.get_red() - normal_color.get_red()));
green.set_green(normal_color.get_green() + factor * (green.get_green() - normal_color.get_green()));
green.set_blue(normal_color.get_blue() + factor * (green.get_blue() - normal_color.get_blue()));
boost::system::error_code ec;
do {
Gtk::TreeNodeChildren children(it->second.row ? it->second.row.children() : tree_store->children());
if(!children)
return;
for(auto &child : children) {
auto name = Glib::Markup::escape_text(child.get_value(column_record.name));
auto path = child.get_value(column_record.path);
// Use canonical path to follow symbolic links
auto canonical_path = filesystem::get_canonical_path(path);
Gdk::RGBA *color;
if(status.modified.find(canonical_path.generic_string()) != status.modified.end())
color = &yellow;
else if(status.added.find(canonical_path.generic_string()) != status.added.end())
color = &green;
else
color = &normal_color;
std::stringstream ss;
ss << '#' << std::setfill('0') << std::hex;
ss << std::setw(2) << std::hex << (color->get_red_u() >> 8);
ss << std::setw(2) << std::hex << (color->get_green_u() >> 8);
ss << std::setw(2) << std::hex << (color->get_blue_u() >> 8);
child.set_value(column_record.markup, "<span foreground=\"" + ss.str() + "\">" + name + "</span>");
auto type = child.get_value(column_record.type);
if(type == PathType::unknown)
child.set_value(column_record.markup, "<i>" + child.get_value(column_record.markup) + "</i>");
}
if(!include_parent_paths)
break;
auto path = boost::filesystem::path(it->first);
if(boost::filesystem::exists(path / ".git", ec))
break;
if(path == path.root_directory())
break;
auto parent_path = boost::filesystem::path(it->first).parent_path();
it = directories.find(parent_path.string());
} while(it != directories.end());
};
if(it->second.repository) {
worker.post([this, repository = it->second.repository, colorize = std::move(colorize)] {
Git::Repository::Status status;
try {
status = repository->get_status();
}
catch(const std::exception &e) {
Terminal::get().async_print(std::string("Error (git): ") + e.what() + '\n', true);
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)] {
auto it = directories.find(dir_path->string());
if(it == directories.end())
return;
auto normal_color = get_style_context()->get_color(Gtk::StateFlags::STATE_FLAG_NORMAL);
Gdk::RGBA yellow;
yellow.set_rgba(1.0, 1.0, 0.2);
double factor = 0.5;
yellow.set_red(normal_color.get_red() + factor * (yellow.get_red() - normal_color.get_red()));
yellow.set_green(normal_color.get_green() + factor * (yellow.get_green() - normal_color.get_green()));
yellow.set_blue(normal_color.get_blue() + factor * (yellow.get_blue() - normal_color.get_blue()));
Gdk::RGBA green;
green.set_rgba(0.0, 1.0, 0.0);
factor = 0.4;
green.set_red(normal_color.get_red() + factor * (green.get_red() - normal_color.get_red()));
green.set_green(normal_color.get_green() + factor * (green.get_green() - normal_color.get_green()));
green.set_blue(normal_color.get_blue() + factor * (green.get_blue() - normal_color.get_blue()));
boost::system::error_code ec;
do {
Gtk::TreeNodeChildren children(it->second.row ? it->second.row.children() : tree_store->children());
if(!children)
return;
for(auto &child : children) {
auto name = Glib::Markup::escape_text(child.get_value(column_record.name));
auto path = child.get_value(column_record.path);
// Use canonical path to follow symbolic links
auto canonical_path = filesystem::get_canonical_path(path);
Gdk::RGBA *color;
if(status.modified.find(canonical_path.generic_string()) != status.modified.end())
color = &yellow;
else if(status.added.find(canonical_path.generic_string()) != status.added.end())
color = &green;
else
color = &normal_color;
std::stringstream ss;
ss << '#' << std::setfill('0') << std::hex;
ss << std::setw(2) << std::hex << (color->get_red_u() >> 8);
ss << std::setw(2) << std::hex << (color->get_green_u() >> 8);
ss << std::setw(2) << std::hex << (color->get_blue_u() >> 8);
child.set_value(column_record.markup, "<span foreground=\"" + ss.str() + "\">" + name + "</span>");
auto type = child.get_value(column_record.type);
if(type == PathType::unknown)
child.set_value(column_record.markup, "<i>" + child.get_value(column_record.markup) + "</i>");
}
if(!include_parent_paths)
break;
auto path = boost::filesystem::path(it->first);
if(boost::filesystem::exists(path / ".git", ec))
break;
if(path == path.root_directory())
break;
auto parent_path = boost::filesystem::path(it->first).parent_path();
it = directories.find(parent_path.string());
} while(it != directories.end());
dispatcher.post([colorize = std::move(colorize), status = std::move(status)] {
colorize(status);
});
});
}
else
colorize();
}

18
src/directories.hpp

@ -2,6 +2,7 @@
#include "boost/filesystem.hpp"
#include "dispatcher.hpp"
#include "git.hpp"
#include "workers.hpp"
#include <atomic>
#include <gtkmm.h>
#include <string>
@ -18,7 +19,10 @@ class Directories : public Gtk::ListViewText {
std::shared_ptr<sigc::connection> connection;
};
enum class PathType { known, unknown };
enum class PathType {
known,
unknown
};
class TreeStore : public Gtk::TreeStore {
protected:
@ -52,8 +56,8 @@ class Directories : public Gtk::ListViewText {
public:
static Directories &get() {
static Directories singleton;
return singleton;
static Directories instance;
return instance;
}
~Directories() override;
@ -78,17 +82,21 @@ private:
std::unordered_map<std::string, DirectoryData> directories;
Glib::ThreadPool thread_pool;
Workers worker;
Dispatcher dispatcher;
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;
};

23
src/entrybox.cpp

@ -2,20 +2,15 @@
std::unordered_map<std::string, std::vector<std::string>> EntryBox::entry_histories;
EntryBox::Entry::Entry(const std::string &content, std::function<void(const std::string &content)> on_activate_, unsigned width_chars) : Gtk::Entry(), on_activate(std::move(on_activate_)) {
EntryBox::Entry::Entry(const std::string &content, std::function<void(const std::string &content)> on_activate_) : Gtk::Entry(), on_activate(std::move(on_activate_)) {
set_max_length(0);
set_width_chars(width_chars);
set_text(content);
last_content = content;
selected_history = -1;
signal_activate().connect([this]() {
if(this->on_activate) {
auto &history = EntryBox::entry_histories[get_placeholder_text()];
auto text = get_text();
if(!text.empty() && (history.empty() || *history.begin() != text))
history.emplace(history.begin(), text);
selected_history = -1;
this->on_activate(text);
update_history();
this->on_activate(get_text());
}
});
signal_changed().connect([this] {
@ -58,6 +53,14 @@ EntryBox::Entry::Entry(const std::string &content, std::function<void(const std:
});
}
void EntryBox::Entry::update_history() {
auto &history = EntryBox::entry_histories[get_placeholder_text()];
auto text = get_text();
if(!text.empty() && (history.empty() || *history.begin() != text))
history.emplace(history.begin(), text);
selected_history = -1;
}
EntryBox::Button::Button(const std::string &label, std::function<void()> on_activate_) : Gtk::Button(label), on_activate(std::move(on_activate_)) {
set_focus_on_click(false);
signal_clicked().connect([this]() {
@ -97,7 +100,7 @@ void EntryBox::clear() {
void EntryBox::show() {
std::vector<Gtk::Widget *> focus_chain;
for(auto &entry : entries) {
lower_box.pack_start(entry, Gtk::PACK_SHRINK);
lower_box.pack_start(entry, Gtk::PACK_EXPAND_WIDGET);
focus_chain.emplace_back(&entry);
}
for(auto &button : buttons)
@ -110,6 +113,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);
}
}

9
src/entrybox.hpp

@ -10,9 +10,12 @@ class EntryBox : public Gtk::Box {
public:
class Entry : public Gtk::Entry {
public:
Entry(const std::string &content = "", std::function<void(const std::string &content)> on_activate_ = nullptr, unsigned width_chars = -1);
Entry(const std::string &content = "", std::function<void(const std::string &content)> on_activate_ = nullptr);
std::function<void(const std::string &content)> on_activate;
/// Only the default activation on an entry will update its history.
void update_history();
private:
long selected_history;
std::string last_content;
@ -39,8 +42,8 @@ private:
public:
static EntryBox &get() {
static EntryBox singleton;
return singleton;
static EntryBox instance;
return instance;
}
Gtk::Box upper_box;

241
src/filesystem.cpp

@ -1,14 +1,17 @@
#include "filesystem.hpp"
#include "process.hpp"
#include "utility.hpp"
#include <algorithm>
#include <fstream>
#include <iostream>
#include <sstream>
//Only use on small files
boost::optional<boost::filesystem::path> filesystem::rust_sysroot_path;
boost::optional<std::vector<boost::filesystem::path>> filesystem::executable_search_paths;
// 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();
@ -20,9 +23,9 @@ std::string filesystem::read(const std::string &path) {
return str;
}
//Only use on small files
// 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
@ -31,7 +34,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] == '"') {
@ -42,7 +45,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) {
@ -81,22 +84,74 @@ std::string filesystem::unescape_argument(const std::string &argument) {
return unescaped;
}
boost::filesystem::path filesystem::get_home_path() noexcept {
static boost::filesystem::path home_path;
if(!home_path.empty())
return home_path;
std::vector<std::string> 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;
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);
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();
return boost::filesystem::path(path);
}
return boost::filesystem::path();
#endif
};
static boost::filesystem::path path = get_path();
return path;
}
const boost::filesystem::path &filesystem::get_home_path() noexcept {
auto get_path = [] {
std::vector<std::string> 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 = get_path();
return path;
}
const boost::filesystem::path &filesystem::get_rust_sysroot_path() noexcept {
auto get_path = [] {
std::string path;
TinyProcessLib::Config processConfig{};
#ifdef JUCI_FLATPAK_SANDBOX
processConfig.flatpak_spawn_host = true;
#endif
TinyProcessLib::Process process(
"rustc --print sysroot", "",
[&path](const char *buffer, size_t length) {
path += std::string(buffer, length);
},
[](const char *buffer, size_t n) {}, false, processConfig);
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_sysroot_path)
rust_sysroot_path = get_path();
return *rust_sysroot_path;
}
boost::filesystem::path filesystem::get_short_path(const boost::filesystem::path &path) noexcept {
@ -128,13 +183,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) {
@ -188,40 +243,53 @@ 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;
#endif
static std::vector<boost::filesystem::path> bin_paths = {"/usr/bin", "/usr/local/bin"};
try {
for(auto &path : bin_paths) {
if(boost::filesystem::exists(path / executable_name))
for(auto &path : get_executable_search_paths()) {
if(is_executable(path / executable_name))
return executable_name;
}
auto &executable_name_str = executable_name.string();
for(auto &path : bin_paths) {
boost::filesystem::path executable;
for(boost::filesystem::directory_iterator it(path), end; it != end; ++it) {
auto it_path = it->path();
auto it_path_filename_str = it_path.filename().string();
if(starts_with(it_path_filename_str, executable_name_str)) {
if(it_path > executable &&
((it_path_filename_str.size() > executable_name_str.size() &&
it_path_filename_str[executable_name_str.size()] >= '0' &&
it_path_filename_str[executable_name_str.size()] <= '9') ||
(it_path_filename_str.size() > executable_name_str.size() + 1 &&
it_path_filename_str[executable_name_str.size()] == '-' &&
it_path_filename_str[executable_name_str.size() + 1] >= '0' &&
it_path_filename_str[executable_name_str.size() + 1] <= '9')) &&
!boost::filesystem::is_directory(it_path))
executable = it_path;
auto executable_name_str = executable_name.string();
for(auto &folder : get_executable_search_paths()) {
boost::filesystem::path latest_executable;
std::string latest_version;
for(boost::filesystem::directory_iterator it(folder), end; it != end; ++it) {
const auto &file = it->path();
auto filename = file.filename().string();
if(starts_with(filename, executable_name_str)) {
if(filename.size() > executable_name_str.size() && filename[executable_name_str.size()] >= '0' && filename[executable_name_str.size()] <= '9' && is_executable(file)) {
auto version = filename.substr(executable_name_str.size());
if(version_compare(version, latest_version) > 0) {
latest_executable = file;
latest_version = version;
}
}
else if(filename.size() > executable_name_str.size() + 1 && filename[executable_name_str.size()] == '-' && filename[executable_name_str.size() + 1] >= '0' && filename[executable_name_str.size() + 1] <= '9' && is_executable(file)) {
auto version = filename.substr(executable_name_str.size() + 1);
if(version_compare(version, latest_version) > 0) {
latest_executable = file;
latest_version = version;
}
}
}
}
if(!executable.empty())
return executable;
if(!latest_executable.empty())
return latest_executable;
}
}
catch(...) {
@ -231,36 +299,46 @@ boost::filesystem::path filesystem::get_executable(const boost::filesystem::path
}
// Based on https://stackoverflow.com/a/11295568
const std::vector<boost::filesystem::path> &filesystem::get_executable_search_paths() {
static std::vector<boost::filesystem::path> result;
if(!result.empty())
return result;
const std::string env = getenv("PATH");
const char delimiter = ':';
size_t previous = 0;
size_t pos;
while((pos = env.find(delimiter, previous)) != std::string::npos) {
result.emplace_back(env.substr(previous, pos - previous));
previous = pos + 1;
}
result.emplace_back(env.substr(previous));
const std::vector<boost::filesystem::path> &filesystem::get_executable_search_paths() noexcept {
auto get_paths = [] {
std::vector<boost::filesystem::path> paths;
auto c_env = std::getenv("PATH");
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;
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 result;
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) {
boost::filesystem::path filesystem::find_executable(const std::string &executable_name) noexcept {
for(auto &path : get_executable_search_paths()) {
boost::system::error_code ec;
auto executable_path = path / executable_name;
if(boost::filesystem::exists(executable_path, ec))
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";
@ -274,10 +352,15 @@ std::string filesystem::get_uri_from_path(const boost::filesystem::path &path) {
uri += chr;
}
#ifdef _WIN32
if(uri.size() > 9 && ((uri[7] >= 'a' && uri[7] <= 'z') || (uri[7] >= 'A' && uri[7] <= 'Z')) && uri[8] == ':' && uri[9] == '/')
uri.insert(7, "/");
#endif
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://"))
@ -299,10 +382,17 @@ boost::filesystem::path filesystem::get_path_from_uri(const std::string &uri) {
unencoded += encoded[i];
}
#ifdef _WIN32
if(unencoded.size() > 3 && unencoded[0] == '/' && ((unencoded[1] >= 'a' && unencoded[1] <= 'z') || (unencoded[1] >= 'A' && unencoded[1] <= 'Z')) && unencoded[2] == ':' && unencoded[3] == '/') {
unencoded.erase(0, 1);
unencoded[0] = std::toupper(unencoded[0]);
}
#endif
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);
}
@ -310,3 +400,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
}

41
src/filesystem.hpp

@ -1,5 +1,7 @@
#pragma once
#include <boost/filesystem.hpp>
#include <boost/optional.hpp>
#include <fstream>
#include <string>
#include <vector>
@ -13,35 +15,50 @@ 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;
static boost::filesystem::path get_home_path() noexcept;
/// Does not resolve symbolic links. Returns empty path on failure.
static const boost::filesystem::path &get_current_path() noexcept;
/// Returns empty path on failure
static const boost::filesystem::path &get_home_path() noexcept;
/// Returns empty path on failure
static const boost::filesystem::path &get_rust_sysroot_path() noexcept;
/// Set to {} to reset get_rust_sysroot_path
static boost::optional<boost::filesystem::path> rust_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 ~)
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
/// Returns path with dot, dot-dot and directory separator elements removed
static boost::filesystem::path get_normal_path(const boost::filesystem::path &path) noexcept;
static boost::filesystem::path get_relative_path(const boost::filesystem::path &path, const boost::filesystem::path &base) noexcept;
/// Return executable with latest version in filename on systems that is lacking executable_name symbolic link
static boost::filesystem::path get_absolute_path(const boost::filesystem::path &path, const boost::filesystem::path &base) noexcept;
/// Returns executable name with latest version in filename on systems that is lacking executable_name symbolic link
static boost::filesystem::path get_executable(const boost::filesystem::path &executable_name) noexcept;
static const std::vector<boost::filesystem::path> &get_executable_search_paths();
static const std::vector<boost::filesystem::path> &get_executable_search_paths() noexcept;
/// Set to {} to reset get_executable_search_paths
static boost::optional<std::vector<boost::filesystem::path>> executable_search_paths;
static boost::filesystem::path find_executable(const std::string &executable_name);
/// Returns full executable path if found, or empty path otherwise.
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;
};

125
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<Lines *>(payload);
auto start = hunk->new_start - 1;
auto end = hunk->new_start + hunk->new_lines - 1;
if(hunk->old_lines == 0 && hunk->new_lines > 0)
lines->added.emplace_back(start, end);
else if(hunk->new_lines == 0 && hunk->old_lines > 0)
lines->removed.emplace_back(start);
else
lines->modified.emplace_back(start, end);
return 0;
}, 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<Lines *>(payload);
auto start = hunk->new_start - 1;
auto end = hunk->new_start + hunk->new_lines - 1;
if(hunk->old_lines == 0 && hunk->new_lines > 0)
lines->added.emplace_back(start, end);
else if(hunk->new_lines == 0 && hunk->old_lines > 0)
lines->removed.emplace_back(start);
else
lines->modified.emplace_back(start, end);
return 0;
},
nullptr, &lines);
if(error)
throw std::runtime_error(error.message());
return lines;
@ -62,11 +63,13 @@ std::vector<Git::Repository::Diff::Hunk> 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<std::vector<Git::Repository::Diff::Hunk> *>(payload);
hunks->emplace_back(hunk->old_start, hunk->old_lines, hunk->new_start, hunk->new_lines);
return 0;
}, nullptr, &hunks);
error.code = git_diff_buffers(
old_buffer.c_str(), old_buffer.size(), nullptr, new_buffer.c_str(), new_buffer.size(), nullptr, &options, nullptr, nullptr, [](const git_diff_delta *delta, const git_diff_hunk *hunk, void *payload) {
auto hunks = static_cast<std::vector<Git::Repository::Diff::Hunk> *>(payload);
hunks->emplace_back(hunk->old_start, hunk->old_lines, hunk->new_start, hunk->new_lines);
return 0;
},
nullptr, &hunks);
if(error)
throw std::runtime_error(error.message());
return hunks;
@ -76,18 +79,20 @@ std::string Git::Repository::Diff::get_details(const std::string &buffer, int li
std::pair<std::string, int> 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<std::pair<std::string, int> *>(payload);
auto line_nr = details->second;
auto start = hunk->new_start - 1;
auto end = hunk->new_start + hunk->new_lines - 1;
if(line_nr == start || (line_nr >= start && line_nr < end)) {
if(details->first.empty())
details->first += std::string(hunk->header, hunk->header_len);
details->first += line->origin + std::string(line->content, line->content_len);
}
return 0;
}, &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<std::pair<std::string, int> *>(payload);
auto line_nr = details->second;
auto start = hunk->new_start - 1;
auto end = hunk->new_start + hunk->new_lines - 1;
if(line_nr == start || (line_nr >= start && line_nr < end)) {
if(details->first.empty())
details->first += std::string(hunk->header, hunk->header_len);
details->first += line->origin + std::string(line->content, line->content_len);
}
return 0;
},
&details);
if(error)
throw std::runtime_error(error.message());
return details.first;
@ -111,13 +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<Gio::File> &file,
const Glib::RefPtr<Gio::File> &,
Gio::FileMonitorEvent monitor_event) {
if(monitor_event != Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) {
this->clear_saved_status();
}
}, false);
monitor_changed_connection = monitor->signal_changed().connect(
[this](const Glib::RefPtr<Gio::File> &file,
const Glib::RefPtr<Gio::File> &,
Gio::FileMonitorEvent monitor_event) {
if(monitor_event != Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) {
this->clear_saved_status();
}
},
false);
}
Git::Repository::~Repository() {
@ -138,27 +145,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<Data *>(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);
error.code = git_status_foreach(
repository.get(), [](const char *path, unsigned int status_flags, void *payload) {
auto data = static_cast<Data *>(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);
if(error)
throw std::runtime_error(error.message());

4
src/git.hpp

@ -92,12 +92,12 @@ public:
private:
static bool initialized GUARDED_BY(mutex);
///Mutex for thread safe operations
/// Mutex for thread safe operations
static Mutex mutex;
static Error error GUARDED_BY(mutex);
///Call initialize in public static methods
/// Call initialize in public static methods
static void initialize() noexcept REQUIRES(mutex);
static boost::filesystem::path path(const char *cpath, boost::optional<size_t> cpath_length = {}) noexcept REQUIRES(mutex);

58
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"
@ -10,16 +11,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;
@ -29,18 +28,53 @@ 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 + " *";
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);
});
std::stringstream stdin_stream;
Terminal::get().process(stdin_stream, output, command, project_path);
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() {

19
src/info.cpp

@ -11,9 +11,9 @@ Info::Info() {
get_style_context()->add_class("juci_info");
//Workaround from https://bugzilla.gnome.org/show_bug.cgi?id=710888
//Issue described at the same issue report
//TODO: remove later
// Workaround from https://bugzilla.gnome.org/show_bug.cgi?id=710888
// Issue described at the same issue report
// TODO: remove later
auto revealer = gtk_widget_get_template_child(GTK_WIDGET(gobj()), GTK_TYPE_INFO_BAR, "revealer");
if(revealer) {
gtk_revealer_set_transition_type(GTK_REVEALER(revealer), GTK_REVEALER_TRANSITION_TYPE_NONE);
@ -21,15 +21,18 @@ Info::Info() {
}
}
void Info::print(const std::string &text) {
timeout_connection.disconnect();
//Timeout based on https://en.wikipedia.org/wiki/Words_per_minute
// 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();

497
src/json.cpp

@ -0,0 +1,497 @@
#include "nlohmann/json.hpp"
#include "json.hpp"
#include <algorithm>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
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;
}
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> 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<std::vector<std::pair<std::string, JSON>>> JSON::children_optional(const std::string &key) const noexcept {
try {
return children(key);
}
catch(...) {
return {};
}
}
std::vector<std::pair<std::string, JSON>> JSON::children_or_empty(const std::string &key) const noexcept {
try {
return children(key);
}
catch(...) {
return {};
}
}
std::vector<std::pair<std::string, JSON>> JSON::children(const std::string &key) const {
return JSON(&ptr->at(key)).children();
}
boost::optional<std::vector<std::pair<std::string, JSON>>> JSON::children_optional() const noexcept {
try {
return children();
}
catch(...) {
return {};
}
}
std::vector<std::pair<std::string, JSON>> JSON::children_or_empty() const noexcept {
try {
return children();
}
catch(...) {
return {};
}
}
std::vector<std::pair<std::string, JSON>> JSON::children() const {
if(!ptr->is_object())
throw std::runtime_error("value '" + to_string() + "' is not an object");
std::vector<std::pair<std::string, JSON>> result;
for(auto it = ptr->begin(); it != ptr->end(); ++it)
result.emplace_back(it.key(), JSON(&*it));
return result;
}
boost::optional<JSON> 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> 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<std::vector<JSON>> JSON::array_optional(const std::string &key) const noexcept {
try {
return array(key);
}
catch(...) {
return {};
}
}
std::vector<JSON> JSON::array_or_empty(const std::string &key) const noexcept {
try {
return array(key);
}
catch(...) {
return {};
}
}
std::vector<JSON> JSON::array(const std::string &key) const {
return JSON(&ptr->at(key)).array();
}
boost::optional<std::vector<JSON>> JSON::array_optional() const noexcept {
try {
return array();
}
catch(...) {
return {};
}
}
std::vector<JSON> JSON::array_or_empty() const noexcept {
try {
return array();
}
catch(...) {
return {};
}
}
std::vector<JSON> JSON::array() const {
if(!ptr->is_array())
throw std::runtime_error("value '" + to_string() + "' is not an array");
std::vector<JSON> result;
result.reserve(ptr->size());
for(auto &e : *ptr)
result.emplace_back(&e);
return result;
}
boost::optional<std::string> 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<std::string>();
}
boost::optional<std::string> 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<std::string>();
}
boost::optional<long long> 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<long long> 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<nlohmann::ordered_json::number_integer_t *>())
return *integer;
if(auto floating_point = ptr->get<nlohmann::ordered_json::number_float_t *>())
return *floating_point;
if(parse_options == ParseOptions::accept_string) {
if(auto string = ptr->get<nlohmann::ordered_json::string_t *>()) {
try {
return std::stoll(*string);
}
catch(...) {
}
}
}
throw std::runtime_error("value '" + to_string() + "' could not be converted to integer");
}
boost::optional<double> 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<double> 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<nlohmann::ordered_json::number_float_t *>())
return *floating_point;
if(auto integer = ptr->get<nlohmann::ordered_json::number_integer_t *>())
return *integer;
if(parse_options == ParseOptions::accept_string) {
if(auto string = ptr->get<nlohmann::ordered_json::string_t *>()) {
try {
return std::stof(*string);
}
catch(...) {
}
}
}
throw std::runtime_error("value '" + to_string() + "' could not be converted to floating point");
}
boost::optional<bool> 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<bool> 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<nlohmann::ordered_json::boolean_t *>())
return *boolean;
if(auto integer = ptr->get<nlohmann::ordered_json::number_integer_t *>()) {
if(*integer == 1)
return true;
if(*integer == 0)
return false;
}
if(parse_options == ParseOptions::accept_string) {
if(auto string = ptr->get<nlohmann::ordered_json::string_t *>()) {
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");
}

128
src/json.hpp

@ -0,0 +1,128 @@
#pragma once
#include "nlohmann/json_fwd.hpp"
#include <boost/filesystem.hpp>
#include <boost/optional.hpp>
#include <ostream>
class JSON {
nlohmann::ordered_json *ptr;
bool owner;
public:
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<long long>(value));
}
void set(const std::string &key, long value) noexcept {
set(key, static_cast<long long>(value));
}
void set(const std::string &key, long long value) noexcept;
void set(const std::string &key, unsigned value) noexcept {
set(key, static_cast<long long>(value));
}
void set(const std::string &key, unsigned long value) noexcept {
set(key, static_cast<long long>(value));
}
void set(const std::string &key, unsigned long long value) noexcept {
set(key, static_cast<long long>(value));
}
void set(const std::string &key, float value) noexcept {
set(key, static_cast<double>(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<JSON> child_optional(const std::string &key) const noexcept;
JSON child(const std::string &key) const;
boost::optional<std::vector<std::pair<std::string, JSON>>> children_optional(const std::string &key) const noexcept;
std::vector<std::pair<std::string, JSON>> children_or_empty(const std::string &key) const noexcept;
std::vector<std::pair<std::string, JSON>> children(const std::string &key) const;
boost::optional<std::vector<std::pair<std::string, JSON>>> children_optional() const noexcept;
std::vector<std::pair<std::string, JSON>> children_or_empty() const noexcept;
std::vector<std::pair<std::string, JSON>> children() const;
boost::optional<JSON> object_optional(const std::string &key) const noexcept;
JSON object(const std::string &key) const;
boost::optional<JSON> object_optional() const noexcept;
JSON object() const;
boost::optional<std::vector<JSON>> array_optional(const std::string &key) const noexcept;
std::vector<JSON> array_or_empty(const std::string &key) const noexcept;
std::vector<JSON> array(const std::string &key) const;
boost::optional<std::vector<JSON>> array_optional() const noexcept;
std::vector<JSON> array_or_empty() const noexcept;
std::vector<JSON> array() const;
boost::optional<std::string> 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<std::string> string_optional() const noexcept;
std::string string_or(const std::string &default_value) const noexcept;
std::string string() const;
boost::optional<long long> 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<long long> 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<double> 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<double> 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<bool> 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<bool> 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;
};

33
src/juci.cpp

@ -25,13 +25,12 @@ int Application::on_command_line(const Glib::RefPtr<Gio::ApplicationCommandLine>
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)) {
@ -40,7 +39,7 @@ int Application::on_command_line(const Glib::RefPtr<Gio::ApplicationCommandLine>
else if(boost::filesystem::is_directory(path, ec))
directories.emplace_back(path);
}
//Open new file if parent path exists
// Open new file if parent path exists
else {
if(path.is_absolute() && boost::filesystem::is_directory(path.parent_path(), ec))
files.emplace_back(path, 0);
@ -55,12 +54,12 @@ int Application::on_command_line(const Glib::RefPtr<Gio::ApplicationCommandLine>
void Application::on_activate() {
std::vector<std::pair<int, int>> file_offsets;
std::string current_file;
window.init();
window.load_session(directories, files, file_offsets, current_file, directories.empty() && files.empty());
window.add_widgets();
add_window(window);
window.show();
boost::filesystem::path current_file;
Window::get().init();
Window::get().load_session(directories, files, file_offsets, current_file, directories.empty() && files.empty());
Window::get().add_widgets();
add_window(Window::get());
Window::get().show();
bool first_directory = true;
for(auto &directory : directories) {
@ -122,20 +121,18 @@ void Application::on_startup() {
Gtk::Application::on_startup();
Menu::get().build();
if(!Menu::get().juci_menu || !Menu::get().window_menu) {
std::cerr << "Menu not found." << std::endl;
}
else {
if(Menu::get().juci_menu)
set_app_menu(Menu::get().juci_menu);
if(!Menu::get().window_menu)
std::cerr << "Menu not found." << std::endl;
else
set_menubar(Menu::get().window_menu);
}
}
Application::Application(Plugins &p) : Gtk::Application("no.sout.juci", Gio::APPLICATION_NON_UNIQUE | Gio::APPLICATION_HANDLES_COMMAND_LINE), window(p) {
Glib::set_application_name("juCi++");
//Gtk::MessageDialog without buttons caused text to be selected, this prevents that
// Gtk::MessageDialog without buttons caused text to be selected, this prevents that
Gtk::Settings::get_default()->property_gtk_label_select_on_focus() = false;
}

151
src/menu.cpp

@ -1,9 +1,48 @@
#include "menu.hpp"
#include "config.hpp"
#include "terminal.hpp"
#include <iostream>
#include <pybind11/functional.h>
#include <string>
const Glib::ustring juci_section_xml = R"RAW(<section>
<item>
<attribute name='label' translatable='yes'>_About</attribute>
<attribute name='action'>app.about</attribute>
</item>
</section>
<section>
<item>
<attribute name='label' translatable='yes'>_Preferences</attribute>
<attribute name='action'>app.preferences</attribute>
</item>
</section>
<section>
<item>
<attribute name='label' translatable='yes'>_Snippets</attribute>
<attribute name='action'>app.snippets</attribute>
</item>
<item>
<attribute name='label' translatable='yes'>_Commands</attribute>
<attribute name='action'>app.commands</attribute>
</item>
</section>
<section>
<item>
<attribute name='label' translatable='yes'>_Quit</attribute>
<attribute name='action'>app.quit</attribute>
</item>
</section>
)RAW";
#ifdef __APPLE__
const Glib::ustring juci_menu_xml = "<menu id='juci-menu'>" + juci_section_xml + "</menu>";
const Glib::ustring juci_submenu_xml = "";
#else
const Glib::ustring juci_menu_xml = "";
const Glib::ustring juci_submenu_xml = "<submenu><attribute name='label' translatable='no'>juCi++</attribute>" + juci_section_xml + "</submenu>";
#endif
const Glib::ustring menu_xml = R"RAW(<interface>
<menu id='right-click-line-menu'>
<section>
@ -87,34 +126,11 @@ const Glib::ustring menu_xml = R"RAW(<interface>
</item>
</section>
</menu>
<menu id='juci-menu'>
<section>
<item>
<attribute name='label' translatable='yes'>_About</attribute>
<attribute name='action'>app.about</attribute>
</item>
</section>
<section>
<item>
<attribute name='label' translatable='yes'>_Preferences</attribute>
<attribute name='action'>app.preferences</attribute>
</item>
</section>
<section>
<item>
<attribute name='label' translatable='yes'>_Snippets</attribute>
<attribute name='action'>app.snippets</attribute>
</item>
</section>
<section>
<item>
<attribute name='label' translatable='yes'>_Quit</attribute>
<attribute name='action'>app.quit</attribute>
</item>
</section>
</menu>
)RAW" + juci_menu_xml + R"RAW(
<menu id='window-menu'>
)RAW" + juci_submenu_xml + R"RAW(
<submenu>
<attribute name='label' translatable='yes'>_File</attribute>
<section>
@ -136,6 +152,10 @@ const Glib::ustring menu_xml = R"RAW(<interface>
<attribute name='label' translatable='no'>C++</attribute>
<attribute name='action'>app.file_new_project_cpp</attribute>
</item>
<item>
<attribute name='label' translatable='no'>Rust</attribute>
<attribute name='action'>app.file_new_project_rust</attribute>
</item>
</submenu>
</section>
<section>
@ -148,6 +168,16 @@ const Glib::ustring menu_xml = R"RAW(<interface>
<attribute name='action'>app.file_open_folder</attribute>
</item>
</section>
<section>
<item>
<attribute name='label' translatable='yes'>_Find _File</attribute>
<attribute name='action'>app.file_find_file</attribute>
</item>
<item>
<attribute name='label' translatable='yes'>_Switch _File _Type</attribute>
<attribute name='action'>app.file_switch_file_type</attribute>
</item>
</section>
<section>
<item>
<attribute name='label' translatable='yes'>_Reload _File</attribute>
@ -177,6 +207,10 @@ const Glib::ustring menu_xml = R"RAW(<interface>
<attribute name='label' translatable='yes'>_Close _Project</attribute>
<attribute name='action'>app.file_close_project</attribute>
</item>
<item>
<attribute name='label' translatable='yes'>_Close _Other _Files</attribute>
<attribute name='action'>app.file_close_other_files</attribute>
</item>
</section>
<section>
<item>
@ -311,10 +345,6 @@ const Glib::ustring menu_xml = R"RAW(<interface>
</submenu>
</section>
<section>
<item>
<attribute name='label' translatable='yes'>_Find _File</attribute>
<attribute name='action'>app.source_find_file</attribute>
</item>
<item>
<attribute name='label' translatable='yes'>_Find _Symbol</attribute>
<attribute name='action'>app.source_find_symbol</attribute>
@ -519,7 +549,23 @@ const Glib::ustring menu_xml = R"RAW(<interface>
<attribute name='action'>app.window_toggle_full_screen</attribute>
</item>
<item>
<attribute name='label' translatable='yes'>_Toggle _Tabs _Visibility</attribute>
<attribute name='label' translatable='yes'>_Toggle _Directories</attribute>
<attribute name='action'>app.window_toggle_directories</attribute>
</item>
<item>
<attribute name='label' translatable='yes'>_Toggle _Terminal</attribute>
<attribute name='action'>app.window_toggle_terminal</attribute>
</item>
<item>
<attribute name='label' translatable='yes'>_Toggle _Status</attribute>
<attribute name='action'>app.window_toggle_status</attribute>
</item>
<item>
<attribute name='label' translatable='yes'>_Toggle _Menu</attribute>
<attribute name='action'>app.window_toggle_menu</attribute>
</item>
<item>
<attribute name='label' translatable='yes'>_Toggle _Tabs</attribute>
<attribute name='action'>app.window_toggle_tabs</attribute>
</item>
<item>
@ -539,21 +585,38 @@ const Glib::ustring menu_xml = R"RAW(<interface>
)RAW";
void Menu::add_action(const std::string &name, const std::function<void()> &action) {
auto g_application = g_application_get_default();
auto gio_application = Glib::wrap(g_application, true);
auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application);
actions[name] = application->add_action(name, action);
actions[name] = Glib::RefPtr<Gtk::Application>::cast_dynamic(Gtk::Application::get_default())->add_action(name, action);
}
void Menu::set_keys() {
auto g_application = g_application_get_default();
auto gio_application = Glib::wrap(g_application, true);
auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application);
auto application = Glib::RefPtr<Gtk::Application>::cast_dynamic(Gtk::Application::get_default());
accelerators_with_multiple_actions.clear();
for(auto &action_and_key : Config::get().menu.keys) {
auto it = actions.find(action_and_key.first);
if(it != actions.end()) {
if(!action_and_key.second.empty()) {
application->set_accel_for_action("app." + action_and_key.first, action_and_key.second);
guint key = 0;
GdkModifierType modifier = static_cast<GdkModifierType>(0);
gtk_accelerator_parse(action_and_key.second.c_str(), &key, &modifier);
if(key == 0 && modifier == 0)
Terminal::get().async_print("\e[31mError\e[m: could not parse key string \"" + action_and_key.second + "\" for action " + action_and_key.first + "\n", true);
else
accelerators_with_multiple_actions[std::make_pair(key, modifier)].emplace_back(it->second);
}
else
application->unset_accels_for_action("app." + action_and_key.first);
}
}
for(auto &key : Config::get().menu.keys) {
if(key.second.size() > 0 && actions.find(key.first) != actions.end())
application->set_accel_for_action("app." + key.first, key.second);
for(auto it = accelerators_with_multiple_actions.begin(); it != accelerators_with_multiple_actions.end();) {
if(it->second.size() < 2)
it = accelerators_with_multiple_actions.erase(it);
else
++it;
}
}
@ -571,8 +634,8 @@ void Menu::build() {
ptr = Glib::RefPtr<Gio::Menu>::cast_dynamic(object);
right_click_selected_menu = std::make_unique<Gtk::Menu>(ptr);
}
catch(const Glib::Error &ex) {
std::cerr << "building menu failed: " << ex.what();
catch(const Glib::Error &e) {
std::cerr << "building menu failed: " << e.what();
}
}

5
src/menu.hpp

@ -10,12 +10,13 @@ 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<void()> &action);
std::unordered_map<std::string, Glib::RefPtr<Gio::SimpleAction>> actions;
std::map<std::pair<guint, GdkModifierType>, std::vector<Glib::RefPtr<Gio::SimpleAction>>> accelerators_with_multiple_actions;
void set_keys();
void build();

56
src/meson.cpp

@ -1,22 +1,23 @@
#include "meson.hpp"
#include "compile_commands.hpp"
#include "config.hpp"
#include "dialogs.hpp"
#include "dialog.hpp"
#include "filesystem.hpp"
#include "json.hpp"
#include "terminal.hpp"
#include "utility.hpp"
#include <boost/optional.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <fstream>
#include <future>
#include <regex>
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)) {
const static std::regex project_regex("^ *project *\\(.*", std::regex::icase | std::regex::optimize);
const static std::regex project_regex("^ *project *\\(.*\r?$", std::regex::icase | std::regex::optimize);
std::smatch sm;
if(std::regex_match(line, sm, project_regex))
return true;
@ -50,7 +51,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;
}
}
@ -64,24 +65,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<int> promise;
boost::optional<int> 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) {
@ -93,7 +94,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;
}
}
@ -106,24 +107,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<int> promise;
boost::optional<int> 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) {
@ -134,7 +135,7 @@ boost::filesystem::path Meson::get_executable(const boost::filesystem::path &bui
for(auto &command : compile_commands.commands) {
auto source_file = filesystem::get_normal_path(command.file);
auto values = command.parameter_values("-o");
auto values = command.get_argument_values("-o");
if(!values.empty()) {
size_t pos;
if((pos = values[0].find('@')) != std::string::npos) {
@ -156,18 +157,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<std::string>("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<std::string>(""));
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<std::string>(""));
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();

296
src/notebook.cpp

@ -1,15 +1,19 @@
#include "notebook.hpp"
#include "config.hpp"
#include "dialog.hpp"
#include "directories.hpp"
#include "filesystem.hpp"
#include "project.hpp"
#include "selection_dialog.hpp"
#include "source_clang.hpp"
#include "source_generic.hpp"
#include "source_language_protocol.hpp"
#include "utility.hpp"
#include <fstream>
#include <gtksourceview-3.0/gtksourceview/gtksourcemap.h>
#include <regex>
Notebook::TabLabel::TabLabel(const std::function<void()> &on_close) {
set_can_focus(false);
@ -66,6 +70,28 @@ 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)
#ifdef _WIN32
boost::filesystem::path cargo_bin;
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
boost::system::error_code 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;
Glib::setenv("PATH", !env.empty() ? env + delimiter + cargo_bin.string() : cargo_bin.string());
filesystem::rust_sysroot_path = {};
filesystem::executable_search_paths = {};
}
}
}
size_t Notebook::size() {
@ -89,7 +115,7 @@ Source::View *Notebook::get_current_view() {
if(view == current_view)
return view;
}
//In case there exist a tab that has not yet received focus again in a different notebook
// In case there exist a tab that has not yet received focus again in a different notebook
for(int notebook_index = 0; notebook_index < 2; ++notebook_index) {
auto page = notebooks[notebook_index].get_current_page();
if(page >= 0)
@ -117,12 +143,24 @@ 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;
}
auto file_path = filesystem::get_normal_path(file_path_);
if(std::any_of(Config::get().project.open_with_default_application.begin(), Config::get().project.open_with_default_application.end(), [&file_path](const boost::filesystem::path &extension) {
return extension == file_path.extension();
})) {
#ifdef __APPLE__
Terminal::get().async_process("open " + filesystem::escape_argument(file_path.string()), "", nullptr, true);
#else
Terminal::get().async_process("xdg-open " + filesystem::escape_argument(file_path.string()), "", nullptr, true);
#endif
Directories::get().select(file_path);
return true;
}
if((position == Position::right || position == Position::split) && !split)
toggle_split();
@ -147,7 +185,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();
@ -156,26 +194,127 @@ 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";
}
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));
size_t source_views_previous_size = source_views.size();
if(language_id == "chdr" || language_id == "cpphdr" || language_id == "c" || language_id == "cpp" || language_id == "objc") {
if(!filesystem::find_executable("clang-language-server").empty())
source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id, "clang-language-server"));
else
source_views.emplace_back(new Source::ClangView(file_path, language));
}
else if(language && !language_protocol_language_id.empty() && !filesystem::find_executable(language_protocol_language_id + "-language-server").empty())
source_views.emplace_back(new Source::LanguageProtocolView(file_path, language, language_protocol_language_id));
else
source_views.emplace_back(new Source::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("rustup").empty())
install_rust();
if(!filesystem::find_executable("rustup").empty()) {
static auto rust_analyzer_installed = []() -> bool {
std::stringstream stdin_stream, stdout_stream;
auto exit_status = Terminal::get().process(stdin_stream, stdout_stream, "rustup component list --installed");
if(exit_status != 0)
return false;
std::string line;
while(std::getline(stdout_stream, line)) {
if(starts_with(line, "rust-analyzer"))
return true;
}
return false;
}();
if(rust_analyzer_installed)
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())));
if(source_views_previous_size == source_views.size()) {
static bool show_dialog = true;
if(show_dialog) {
show_dialog = false;
// Install rust-analyzer
Gtk::MessageDialog dialog(*static_cast<Gtk::Window *>(get_toplevel()), "Install Rust language server?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO);
Gtk::Image image;
image.set_from_icon_name("dialog-question", Gtk::BuiltinIconSize::ICON_SIZE_DIALOG);
dialog.set_image(image);
dialog.set_default_response(Gtk::RESPONSE_YES);
dialog.set_secondary_text("Rust language server not found. Would you like to install rust-analyzer?");
dialog.show_all();
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<int> exit_status;
std::string command = "rustup component add rust-analyzer";
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;
show_dialog = true; // Show dialog again
}
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");
rust_analyzer_installed = true;
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())));
}
}
}
}
}
}
if(source_views_previous_size == source_views.size()) {
if(!language_id.empty()) {
static std::set<std::string> shown;
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") {
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" : "") + ".\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);
}
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));
}
auto view = source_views.back();
@ -219,8 +358,12 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position
}
};
view->update_status_file_path = [this](Source::BaseView *view) {
if(get_current_view() == view)
status_file_path.set_text(' ' + filesystem::get_short_path(view->file_path).string());
if(get_current_view() == view) {
if(!Directories::get().path.empty() && filesystem::file_in_path(view->file_path, Directories::get().path))
status_file_path.set_text(' ' + filesystem::get_relative_path(view->file_path, Directories::get().path).string());
else
status_file_path.set_text(' ' + filesystem::get_short_path(view->file_path).string());
}
};
view->update_status_branch = [this](Source::BaseView *view) {
if(get_current_view() == view) {
@ -309,9 +452,9 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position
configure(source_views.size() - 1);
//Set up tab label
// 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();
@ -402,7 +545,7 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position
});
#ifdef JUCI_ENABLE_DEBUG
if(dynamic_cast<Source::ClangView *>(view) || (view->language && view->language->get_id() == "rust")) {
if(dynamic_cast<Source::ClangView *>(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);
@ -464,6 +607,67 @@ bool Notebook::open(const boost::filesystem::path &file_path_, Position position
return true;
}
void Notebook::install_rust() {
static bool show_dialog = true;
if(show_dialog) {
show_dialog = false;
Gtk::MessageDialog dialog(*static_cast<Gtk::Window *>(get_toplevel()), "Install Rust?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO);
Gtk::Image image;
image.set_from_icon_name("dialog-question", Gtk::BuiltinIconSize::ICON_SIZE_DIALOG);
dialog.set_image(image);
dialog.set_default_response(Gtk::RESPONSE_YES);
dialog.set_secondary_text("Rust not found. Would you like to install Rust?");
dialog.show_all();
int result = dialog.run();
dialog.hide();
if(result == Gtk::RESPONSE_YES) {
bool canceled = false;
Dialog::Message message("Installing Rust", [&canceled] {
canceled = true;
});
boost::optional<int> 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;
show_dialog = true; // Show dialog again
}
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()) {
#ifdef _WIN32
boost::filesystem::path cargo_bin;
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()) {
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::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));
@ -560,6 +764,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) {
@ -585,10 +797,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);
@ -616,12 +824,14 @@ void Notebook::toggle_split() {
pack2(notebooks[1], true, true);
set_position(get_width() / 2);
show_all();
//Make sure the position is correct
//TODO: report bug to gtk if it is not fixed in gtk3.22
Glib::signal_timeout().connect([this] {
set_position(get_width() / 2);
return false;
}, 200);
// Make sure the position is correct
// TODO: report bug to gtk if it is not fixed in gtk3.22
Glib::signal_timeout().connect(
[this] {
set_position(get_width() / 2);
return false;
},
200);
}
else {
for(size_t c = size() - 1; c != static_cast<size_t>(-1); --c) {
@ -714,17 +924,17 @@ void Notebook::set_current_view(Source::View *view) {
}
bool Notebook::save_modified_dialog(size_t index) {
Gtk::MessageDialog dialog(*static_cast<Gtk::Window *>(get_toplevel()), "Save file!", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO);
Gtk::MessageDialog dialog(*static_cast<Gtk::Window *>(get_toplevel()), "Save file?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO);
Gtk::Image image;
image.set_from_icon_name("document-save", Gtk::BuiltinIconSize::ICON_SIZE_DIALOG);
dialog.set_image(image);
dialog.set_default_response(Gtk::RESPONSE_YES);
dialog.set_secondary_text("Do you want to save: " + get_view(index)->file_path.string() + " ?");
dialog.set_secondary_text("Do you want to save " + filesystem::get_short_path(get_view(index)->file_path).string() + "?");
dialog.show_all();
int result = dialog.run();
if(result == Gtk::RESPONSE_YES) {
if(result == Gtk::RESPONSE_YES)
return save(index);
}
else if(result == Gtk::RESPONSE_NO) {
else if(result == Gtk::RESPONSE_NO)
return true;
}
else {
return false;
}
return false;
}

16
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<Gtk::Notebook> notebooks;
@ -37,15 +37,21 @@ public:
Source::View *get_view(size_t index);
Source::View *get_current_view();
std::vector<Source::View *> &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 install_rust();
void open_uri(const std::string &uri);
void configure(size_t index);
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();
@ -81,7 +87,7 @@ private:
/// Throws if view is not found
std::pair<size_t, int> get_notebook_page(Source::View *view);
std::vector<Source::View *> source_views; //Is NOT freed in destructor, this is intended for quick program exit.
std::vector<Source::View *> source_views; // Is NOT freed in destructor, this is intended for quick program exit.
std::vector<std::unique_ptr<Gtk::Widget>> source_maps;
std::vector<std::unique_ptr<Gtk::ScrolledWindow>> scrolled_windows;
std::vector<std::unique_ptr<Gtk::Box>> hboxes;

687
src/project.cpp

@ -1,4 +1,5 @@
#include "project.hpp"
#include "commands.hpp"
#include "config.hpp"
#include "directories.hpp"
#include "filesystem.hpp"
@ -15,7 +16,6 @@
#include "info.hpp"
#include "snippets.hpp"
#include "source_clang.hpp"
#include "source_language_protocol.hpp"
#include "usages_clang.hpp"
#include <future>
@ -36,11 +36,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();
}
}
@ -50,11 +47,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();
}
}
@ -79,14 +73,17 @@ 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->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
@ -153,7 +150,7 @@ void Project::debug_update_stop() {
}
}
}
//Add debug stop source mark
// Add debug stop source mark
debug_last_stop_file_path.clear();
for(size_t c = 0; c < Notebook::get().size(); c++) {
auto view = Notebook::get().get_view(c);
@ -170,22 +167,32 @@ void Project::debug_update_stop() {
}
}
std::shared_ptr<Project::Base> Project::create() {
std::shared_ptr<Project::Base> Project::create(const boost::filesystem::path &file_path) {
std::unique_ptr<Project::Build> build;
if(auto view = Notebook::get().get_current_view()) {
std::string language_id;
if(!file_path.empty()) {
build = Build::create(file_path);
if(auto language = Source::guess_language(file_path))
language_id = language->get_id();
}
else if(auto view = Notebook::get().get_current_view()) {
build = Build::create(view->file_path);
if(view->language) {
auto language_id = view->language->get_id();
if(language_id == "markdown")
return std::shared_ptr<Project::Base>(new Project::Markdown(std::move(build)));
if(language_id == "js")
return std::shared_ptr<Project::Base>(new Project::JavaScript(std::move(build)));
if(language_id == "python")
return std::shared_ptr<Project::Base>(new Project::Python(std::move(build)));
if(language_id == "html")
return std::shared_ptr<Project::Base>(new Project::HTML(std::move(build)));
}
language_id = view->language_id;
}
if(build) {
if(language_id == "markdown")
return std::shared_ptr<Project::Base>(new Project::Markdown(std::move(build)));
if(language_id == "js")
return std::shared_ptr<Project::Base>(new Project::JavaScript(std::move(build)));
if(language_id == "python")
return std::shared_ptr<Project::Base>(new Project::Python(std::move(build)));
if(language_id == "html")
return std::shared_ptr<Project::Base>(new Project::HTML(std::move(build)));
if(language_id == "go")
return std::shared_ptr<Project::Base>(new Project::Go(std::move(build)));
if(language_id == "julia")
return std::shared_ptr<Project::Base>(new Project::Julia(std::move(build)));
}
else
build = Build::create(Directories::get().path);
@ -198,6 +205,8 @@ std::shared_ptr<Project::Base> Project::create() {
return std::shared_ptr<Project::Base>(new Project::JavaScript(std::move(build)));
if(dynamic_cast<PythonMain *>(build.get()))
return std::shared_ptr<Project::Base>(new Project::Python(std::move(build)));
if(dynamic_cast<GoBuild *>(build.get()))
return std::shared_ptr<Project::Base>(new Project::Go(std::move(build)));
return std::shared_ptr<Project::Base>(new Project::Base(std::move(build)));
}
@ -210,7 +219,7 @@ void Project::Base::compile() {
Info::get().print("Could not find a supported project");
}
void Project::Base::compile_and_run() {
void Project::Base::compile_and_run(const boost::filesystem::path &file_path) {
Info::get().print("Could not find a supported project");
}
@ -266,15 +275,20 @@ std::pair<std::string, std::string> 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<std::string, std::string> Project::LLDB::debug_get_run_arguments() {
auto debug_build_path = build->get_debug_path();
auto default_build_path = build->get_default_path();
if(debug_build_path.empty() || default_build_path.empty())
if(debug_build_path.empty() || default_build_path.empty() || !build->update_default())
return {"", ""};
auto project_path = build->project_path.string();
@ -343,7 +357,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;
@ -351,29 +365,31 @@ void Project::LLDB::debug_start() {
if(debug_build_path.empty() || !build->update_debug())
return;
auto project_path = std::make_shared<boost::filesystem::path>(build->project_path);
auto run_arguments_it = debug_run_arguments.find(project_path->string());
auto run_arguments = std::make_shared<std::string>();
if(run_arguments_it != debug_run_arguments.end())
*run_arguments = run_arguments_it->second.arguments;
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()) {
if(!build->is_valid())
Terminal::get().print("\e[31mError\e[m: build folder no longer valid, please rebuild project.\n", true);
run_arguments = build->get_executable(view ? view->file_path : Directories::get().path).string();
if(run_arguments.empty()) {
if(!build->is_valid(default_build_path))
Terminal::get().print("\e[31mError\e[m: build folder no longer valid, please use Recreate Build in the Project menu.\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 Set Run Arguments in the Debug menu, or open a source file within a directory where an executable is defined.\n");
}
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;
@ -381,116 +397,117 @@ 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().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)
Terminal::get().print("\e[2mCompiling and debugging: " + run_arguments + "\e[m\n");
Terminal::get().async_process(build->get_compile_command(), debug_build_path, [self = shared_from_this(), debug_build_path, run_arguments, project_path = build->project_path, remote_host](int exit_status) {
if(exit_status != 0) {
debugging = false;
if(!self->build->is_valid(debug_build_path))
Terminal::get().print("\e[31mError\e[m: build folder no longer valid, please use Recreate Build in the Project menu.\n", true);
}
else {
self->dispatcher.post([self, run_arguments, project_path] {
std::vector<std::pair<boost::filesystem::path, int>> 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(*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);
if(n > 0)
status += " (" + std::string(buffer, n <= 100 ? n : 100) + ")";
auto line_entry = thread.GetSelectedFrame().GetLineEntry();
if(line_entry.IsValid()) {
lldb::SBStream stream;
line_entry.GetFileSpec().GetDescription(stream);
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;
}
}
void Project::LLDB::debug_start(const std::string &command, const boost::filesystem::path &path, const std::string &remote_host) {
std::vector<std::pair<boost::filesystem::path, int>> 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);
}
}
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<std::string> startup_commands;
if(dynamic_cast<CargoBuild *>(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);
});
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;
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<std::string> startup_commands;
if(dynamic_cast<CargoBuild *>(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::ios::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() {
@ -525,23 +542,23 @@ 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<Debug::LLDB::Frame> 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 = "<i>" + frame.module_filename + "</i>";
//Shorten frame.function_name if it is too long
// Shorten frame.function_name if it is too long
if(frame.function_name.size() > 120) {
frame.function_name = frame.function_name.substr(0, 58) + "...." + frame.function_name.substr(frame.function_name.size() - 58);
}
@ -551,7 +568,6 @@ void Project::LLDB::debug_backtrace() {
auto file_path = frame.file_path.filename().string();
row += ":<b>" + Glib::Markup::escape_text(file_path) + ":" + std::to_string(frame.line_nr) + "</b> - " + Glib::Markup::escape_text(frame.function_name);
}
rows.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 +575,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 +595,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<std::vector<Debug::LLDB::Variable>>(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<std::vector<Debug::LLDB::Variable>>();
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) + " - <b>" + Glib::Markup::escape_text(variable.name) + "</b>";
rows->emplace_back(variable);
SelectionDialog::get()->add_row(row);
}
SelectionDialog::get()->on_select = [rows](unsigned int index, const std::string &text, bool hide_window) {
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,17 +632,17 @@ void Project::LLDB::debug_show_variables() {
self->debug_variable_tooltips.clear();
};
SelectionDialog::get()->on_change = [self = this->shared_from_this(), rows, view](boost::optional<unsigned int> index, const std::string &text) {
SelectionDialog::get()->on_change = [self = this->shared_from_this(), variables, view](boost::optional<unsigned int> 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.value;
Glib::ustring value = variable.get_value();
if(!value.empty()) {
Glib::ustring::iterator iter;
while(!value.validate(iter)) {
@ -679,148 +694,9 @@ void Project::LLDB::debug_write(const std::string &buffer) {
}
#endif
void Project::LanguageProtocol::show_symbols() {
auto project_path = std::make_shared<boost::filesystem::path>(build->project_path);
auto view = Notebook::get().get_current_view();
auto language_protocol_view = dynamic_cast<Source::LanguageProtocolView *>(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<std::vector<std::pair<::LanguageProtocol::Location, std::string>>>();
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<void> 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<std::string>("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) + "::" : "") + "<b>" + Glib::Markup::escape_text(it->second.get<std::string>("name")) + "</b>";
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<void> 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<void(const boost::property_tree::ptree &ptee, const std::string &container)> 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<std::string>("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) + "<b>" + Glib::Markup::escape_text(it->second.get<std::string>("name")) + "</b>";
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<std::string>("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<std::string, std::string> Project::Clang::get_run_arguments() {
auto build_path = build->get_default_path();
if(build_path.empty())
if(build_path.empty() || !build->update_default())
return {"", ""};
auto project_path = build->project_path.string();
@ -852,13 +728,15 @@ 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().async_process(build->get_compile_command(), default_build_path, [](int exit_status) {
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, [self = shared_from_this(), default_build_path](int exit_status) {
compiling = false;
if(exit_status != 0 && !self->build->is_valid(default_build_path))
Terminal::get().print("\e[31mError\e[m: build folder no longer valid, please use Recreate Build in the Project menu.\n", true);
});
}
void Project::Clang::compile_and_run() {
void Project::Clang::compile_and_run(const boost::filesystem::path &file_path) {
auto default_build_path = build->get_default_path();
if(default_build_path.empty() || !build->update_default())
return;
@ -871,14 +749,19 @@ void Project::Clang::compile_and_run() {
arguments = run_arguments_it->second;
if(arguments.empty()) {
auto view = Notebook::get().get_current_view();
auto executable = build->get_executable(view ? view->file_path : Directories::get().path);
boost::filesystem::path executable;
if(!file_path.empty())
executable = build->get_executable(file_path);
else {
auto view = Notebook::get().get_current_view();
executable = build->get_executable(view ? view->file_path : Directories::get().path);
}
if(executable.empty()) {
if(!build->is_valid())
Terminal::get().print("\e[31mError\e[m: build folder no longer valid, please rebuild project.\n", true);
if(!build->is_valid(default_build_path))
Terminal::get().print("\e[31mError\e[m: build folder no longer valid, please use Recreate Build in the Project menu.\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 Set Run Arguments in the Project menu, or open a source file within a directory where an executable is defined.\n");
}
return;
}
@ -890,14 +773,16 @@ 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().async_process(build->get_compile_command(), default_build_path, [arguments, project_path](int exit_status) {
Terminal::get().print("\e[2mCompiling and running: " + arguments + "\e[m\n");
Terminal::get().async_process(build->get_compile_command(), default_build_path, [self = shared_from_this(), arguments, project_path, default_build_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().print("\e[2m" + arguments + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n");
});
}
else if(!self->build->is_valid(default_build_path))
Terminal::get().print("\e[31mError\e[m: build folder no longer valid, please use Recreate Build in the Project menu.\n", true);
});
}
@ -914,17 +799,21 @@ void Project::Clang::recreate_build() {
bool has_debug_build = !debug_build_path.empty() && boost::filesystem::exists(debug_build_path, ec);
if(has_default_build || has_debug_build) {
Gtk::MessageDialog dialog(*static_cast<Gtk::Window *>(Notebook::get().get_toplevel()), "Recreate Build", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO);
dialog.set_default_response(Gtk::RESPONSE_NO);
Gtk::MessageDialog dialog(*static_cast<Gtk::Window *>(Notebook::get().get_toplevel()), "Recreate Build?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO);
Gtk::Image image;
image.set_from_icon_name("dialog-question", Gtk::BuiltinIconSize::ICON_SIZE_DIALOG);
dialog.set_image(image);
dialog.set_default_response(Gtk::RESPONSE_YES);
std::string message = "Are you sure you want to recreate ";
if(has_default_build)
message += default_build_path.string();
message += filesystem::get_short_path(default_build_path).string();
if(has_debug_build) {
if(has_default_build)
message += " and ";
message += debug_build_path.string();
message += filesystem::get_short_path(debug_build_path).string();
}
dialog.set_secondary_text(message + "?");
dialog.show_all();
if(dialog.run() != Gtk::RESPONSE_YES)
return;
Usages::Clang::erase_all_caches_for_project(build->project_path, default_build_path);
@ -969,80 +858,91 @@ 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);
}
void Project::Markdown::compile_and_run(const boost::filesystem::path &file_path) {
std::string command;
if(!file_path.empty())
command = Config::get().project.markdown_command + ' ' + filesystem::escape_argument(filesystem::get_short_path(file_path).string());
else if(auto view = Notebook::get().get_current_view())
command = Config::get().project.markdown_command + ' ' + filesystem::escape_argument(filesystem::get_short_path(view->file_path).string());
else
return;
Terminal::get().async_process(
command, "", [command](int exit_status) {
if(exit_status == 127)
Terminal::get().print("\e[31mError\e[m: executable not found: " + command + "\n", true);
},
true);
}
void Project::Python::compile_and_run() {
void Project::Python::compile_and_run(const boost::filesystem::path &file_path) {
std::string command = Config::get().project.python_command + ' ';
boost::filesystem::path path;
if(dynamic_cast<PythonMain *>(build.get())) {
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(!file_path.empty()) {
command += filesystem::escape_argument(filesystem::get_short_path(file_path).string());
path = file_path.parent_path();
}
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();
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().print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n");
});
}
void Project::JavaScript::compile_and_run() {
void Project::JavaScript::compile_and_run(const boost::filesystem::path &file_path) {
std::string command;
boost::filesystem::path path;
if(dynamic_cast<NpmBuild *>(build.get())) {
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(!file_path.empty()) {
command = "node " + filesystem::escape_argument(filesystem::get_short_path(file_path).string());
path = file_path.parent_path();
}
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();
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().print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n");
});
}
void Project::HTML::compile_and_run() {
void Project::HTML::compile_and_run(const boost::filesystem::path &file_path) {
if(dynamic_cast<NpmBuild *>(build.get())) {
std::string command = "npm start";
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().print("\e[2m" + command + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n");
});
}
else if(!file_path.empty())
Notebook::get().open_uri(std::string("file://") + file_path.string());
else if(auto view = Notebook::get().get_current_view())
Notebook::get().open_uri(std::string("file://") + view->file_path.string());
}
@ -1055,7 +955,7 @@ std::pair<std::string, std::string> 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};
}
@ -1066,30 +966,79 @@ 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) {
Terminal::get().async_process(build->get_compile_command(), build->project_path, [](int exit_status) {
compiling = false;
});
}
void Project::Rust::compile_and_run() {
void Project::Rust::compile_and_run(const boost::filesystem::path &file_path) {
compiling = true;
if(Config::get().terminal.clear_on_compile)
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().print("\e[2m" + arguments + " returned: " + (exit_status == 0 ? "\e[32m" : "\e[31m") + std::to_string(exit_status) + "\e[m\n");
});
}
});
}
void Project::Go::compile_and_run(const boost::filesystem::path &file_path) {
std::string command;
boost::filesystem::path path;
if(dynamic_cast<GoBuild *>(build.get())) {
command = "go run .";
path = build->project_path;
}
else if(!file_path.empty()) {
command = "go run " + filesystem::escape_argument(filesystem::get_short_path(file_path).string());
path = file_path.parent_path();
}
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, 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(const boost::filesystem::path &file_path) {
std::string command;
boost::filesystem::path path;
if(!file_path.empty()) {
command = "julia " + filesystem::escape_argument(filesystem::get_short_path(file_path).string());
path = file_path.parent_path();
}
if(auto view = Notebook::get().get_current_view()) {
command = "julia " + 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, 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");
});
}

43
src/project.hpp

@ -56,15 +56,16 @@ namespace Project {
virtual std::pair<std::string, std::string> get_run_arguments();
virtual void compile();
virtual void compile_and_run();
virtual void compile_and_run(const boost::filesystem::path &file_path = {});
virtual void recreate_build();
virtual void show_symbols();
void show_symbols();
virtual std::pair<std::string, std::string> debug_get_run_arguments();
virtual Project::DebugOptions *debug_get_options() { return nullptr; }
Tooltips debug_variable_tooltips;
virtual void debug_start();
virtual void debug_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<std::string, std::string> 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;
@ -105,7 +107,6 @@ namespace Project {
class LanguageProtocol : public virtual Base {
public:
virtual std::string get_language_id() = 0;
void show_symbols() override;
};
class Clang : public LLDB {
@ -114,7 +115,7 @@ namespace Project {
std::pair<std::string, std::string> get_run_arguments() override;
void compile() override;
void compile_and_run() override;
void compile_and_run(const boost::filesystem::path &file_path = {}) override;
void recreate_build() override;
};
@ -122,14 +123,14 @@ namespace Project {
public:
Markdown(std::unique_ptr<Build> &&build) : Base(std::move(build)) {}
void compile_and_run() override;
void compile_and_run(const boost::filesystem::path &file_path = {}) override;
};
class Python : public LanguageProtocol {
public:
Python(std::unique_ptr<Build> &&build) : Base(std::move(build)) {}
void compile_and_run() override;
void compile_and_run(const boost::filesystem::path &file_path = {}) override;
std::string get_language_id() override { return "python"; }
};
@ -138,7 +139,7 @@ namespace Project {
public:
JavaScript(std::unique_ptr<Build> &&build) : Base(std::move(build)) {}
void compile_and_run() override;
void compile_and_run(const boost::filesystem::path &file_path = {}) override;
std::string get_language_id() override { return "javascript"; }
};
@ -147,7 +148,7 @@ namespace Project {
public:
HTML(std::unique_ptr<Build> &&build) : Base(std::move(build)) {}
void compile_and_run() override;
void compile_and_run(const boost::filesystem::path &file_path = {}) override;
};
class Rust : public LLDB, public LanguageProtocol {
@ -156,11 +157,29 @@ namespace Project {
std::pair<std::string, std::string> get_run_arguments() override;
void compile() override;
void compile_and_run() override;
void compile_and_run(const boost::filesystem::path &file_path = {}) override;
std::string get_language_id() override { return "rust"; }
};
std::shared_ptr<Base> create();
class Go : public LanguageProtocol {
public:
Go(std::unique_ptr<Build> &&build) : Base(std::move(build)) {}
void compile_and_run(const boost::filesystem::path &file_path = {}) override;
std::string get_language_id() override { return "go"; }
};
class Julia : public LanguageProtocol {
public:
Julia(std::unique_ptr<Build> &&build) : Base(std::move(build)) {}
void compile_and_run(const boost::filesystem::path &file_path = {}) override;
std::string get_language_id() override { return "julia"; }
};
std::shared_ptr<Base> create(const boost::filesystem::path &file_path = {});
extern std::shared_ptr<Base> current;
}; // namespace Project

135
src/project_build.cpp

@ -1,8 +1,10 @@
#include "project_build.hpp"
#include "config.hpp"
#include "filesystem.hpp"
#include "json.hpp"
#include "terminal.hpp"
#include <boost/algorithm/string.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <fstream>
#include <regex>
std::unique_ptr<Project::Build> Project::Build::create(const boost::filesystem::path &path) {
@ -17,8 +19,6 @@ std::unique_ptr<Project::Build> Project::Build::create(const boost::filesystem::
std::unique_ptr<Project::Build> build(new CMakeBuild(path));
if(!build->project_path.empty())
return build;
else
return std::make_unique<Project::Build>();
}
if(boost::filesystem::exists(search_path / "meson.build"), ec) {
@ -33,6 +33,12 @@ std::unique_ptr<Project::Build> Project::Build::create(const boost::filesystem::
return build;
}
if(boost::filesystem::exists(search_path / "compile_commands.json", ec)) {
std::unique_ptr<Project::Build> build(new CompileCommandsInProjectRootBuild());
build->project_path = search_path;
return build;
}
if(boost::filesystem::exists(search_path / "Cargo.toml", ec)) {
std::unique_ptr<Project::Build> build(new CargoBuild());
build->project_path = search_path;
@ -51,6 +57,12 @@ std::unique_ptr<Project::Build> Project::Build::create(const boost::filesystem::
return build;
}
if(boost::filesystem::exists(search_path / "go.mod", ec)) {
std::unique_ptr<Project::Build> build(new GoBuild());
build->project_path = search_path;
return build;
}
if(search_path == search_path.root_directory())
break;
search_path = search_path.parent_path();
@ -93,10 +105,22 @@ boost::filesystem::path Project::Build::get_debug_path() {
return filesystem::get_normal_path(debug_build_path);
}
std::vector<boost::filesystem::path> Project::Build::get_exclude_paths() {
if(!project_path.empty())
return {project_path / ".git"};
return {};
std::vector<std::string> Project::Build::get_exclude_folders() {
auto default_build_path = Config::get().project.default_build_path;
boost::replace_all(default_build_path, "<project_directory_name>", "");
auto debug_build_path = Config::get().project.debug_build_path;
boost::replace_all(debug_build_path, "<default_build_path>", Config::get().project.default_build_path);
boost::replace_all(debug_build_path, "<project_directory_name>", "");
return std::vector<std::string>{
".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) {
@ -127,30 +151,30 @@ boost::filesystem::path Project::CMakeBuild::get_executable(const boost::filesys
return executable;
}
bool Project::CMakeBuild::is_valid() {
if(project_path.empty())
return true;
auto default_path = get_default_path();
if(default_path.empty())
const auto max_path_length_linux = 4095;
bool Project::CMakeBuild::is_valid(const boost::filesystem::path &build_path) const {
if(project_path.empty() || build_path.empty())
return true;
std::ifstream input((default_path / "CMakeCache.txt").string(), std::ofstream::binary);
std::ifstream input((build_path / "CMakeCache.txt").string(), std::ios::binary);
if(!input)
return true;
std::string line;
bool correct_source_dir = false;
bool correct_cmake_command = false;
while(std::getline(input, line)) {
const static std::regex regex("^.*_SOURCE_DIR:STATIC=(.*)$", std::regex::optimize);
if(line.size() >= max_path_length_linux) {
continue;
}
const static std::regex source_dir_regex("^.*_SOURCE_DIR:STATIC=(.*)\r?$", std::regex::optimize);
const static std::regex cmake_command_regex("^CMAKE_COMMAND:INTERNAL=(.*)\r?$", std::regex::optimize);
std::smatch sm;
if(std::regex_match(line, sm, regex))
return boost::filesystem::path(sm[1].str()) == project_path;
if(std::regex_match(line, sm, source_dir_regex))
correct_source_dir |= boost::filesystem::path(sm[1].str()) == project_path;
else if(std::regex_match(line, sm, cmake_command_regex))
correct_cmake_command |= filesystem::is_executable(sm[1].str());
}
return true;
}
std::vector<boost::filesystem::path> 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;
return correct_source_dir && correct_cmake_command;
}
Project::MesonBuild::MesonBuild(const boost::filesystem::path &path) : Project::Build(), meson(path) {
@ -181,55 +205,48 @@ boost::filesystem::path Project::MesonBuild::get_executable(const boost::filesys
return executable;
}
bool Project::MesonBuild::is_valid() {
if(project_path.empty())
return true;
auto default_path = get_default_path();
if(default_path.empty())
bool Project::MesonBuild::is_valid(const boost::filesystem::path &build_path) const {
if(project_path.empty() || build_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<std::string>("directories.source")) == project_path;
JSON info(build_path / "meson-info" / "meson-info.json");
return boost::filesystem::path(info.object("directories").string("source")) == project_path;
}
catch(...) {
}
return true;
}
std::vector<boost::filesystem::path> 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;
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;
}
std::vector<boost::filesystem::path> 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;
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";
}
std::vector<boost::filesystem::path> 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<boost::filesystem::path> 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<boost::filesystem::path> 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;
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;
}

36
src/project_build.hpp

@ -2,6 +2,7 @@
#include "cmake.hpp"
#include "meson.hpp"
#include <boost/filesystem.hpp>
#include <memory>
namespace Project {
class Build {
@ -18,10 +19,10 @@ namespace Project {
virtual std::string get_compile_command() { return std::string(); }
virtual boost::filesystem::path get_executable(const boost::filesystem::path &path) { return boost::filesystem::path(); }
/// Returns true if the project path reported by build system is correct
virtual bool is_valid() { return true; }
/// Returns false if an invalid build is found, true otherwise even when build is not found.
virtual bool is_valid(const boost::filesystem::path &build_path) const { return true; }
virtual std::vector<boost::filesystem::path> get_exclude_paths();
std::vector<std::string> get_exclude_folders();
static std::unique_ptr<Build> create(const boost::filesystem::path &path);
};
@ -38,9 +39,7 @@ namespace Project {
std::string get_compile_command() override;
boost::filesystem::path get_executable(const boost::filesystem::path &path) override;
bool is_valid() override;
std::vector<boost::filesystem::path> get_exclude_paths() override;
bool is_valid(const boost::filesystem::path &build_path) const override;
};
class MesonBuild : public Build {
@ -55,36 +54,35 @@ namespace Project {
std::string get_compile_command() override;
boost::filesystem::path get_executable(const boost::filesystem::path &path) override;
bool is_valid() override;
std::vector<boost::filesystem::path> get_exclude_paths() override;
bool is_valid(const boost::filesystem::path &build_path) const override;
};
class CompileCommandsBuild : public Build {
public:
std::vector<boost::filesystem::path> get_exclude_paths() override;
};
class CompileCommandsInProjectRootBuild : public Build {
public:
boost::filesystem::path get_default_path() override { return project_path; };
};
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(); }
std::vector<boost::filesystem::path> get_exclude_paths() override;
boost::filesystem::path get_executable(const boost::filesystem::path &path) override;
};
class NpmBuild : public Build {
public:
std::vector<boost::filesystem::path> get_exclude_paths() override;
};
class PythonMain : public Build {
public:
std::vector<boost::filesystem::path> get_exclude_paths() override;
};
class GoBuild : public Build {
};
} // namespace Project

159
src/selection_dialog.cpp

@ -1,4 +1,5 @@
#include "selection_dialog.hpp"
#include "utility.hpp"
#include <algorithm>
SelectionDialogBase::ListViewText::ListViewText(bool use_markup) : Gtk::TreeView(), use_markup(use_markup) {
@ -35,19 +36,20 @@ void SelectionDialogBase::ListViewText::clear() {
size = 0;
}
SelectionDialogBase::SelectionDialogBase(Gtk::TextView *text_view, const boost::optional<Gtk::TextIter> &start_iter, bool show_search_entry, bool use_markup)
: start_mark(start_iter ? Source::Mark(*start_iter) : Source::Mark()), text_view(text_view), window(Gtk::WindowType::WINDOW_POPUP), vbox(Gtk::Orientation::ORIENTATION_VERTICAL), list_view_text(use_markup), show_search_entry(show_search_entry) {
auto g_application = g_application_get_default();
auto gio_application = Glib::wrap(g_application, true);
auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application);
window.set_transient_for(*application->get_active_window());
SelectionDialogBase::SelectionDialogBase(Source::BaseView *view_, const boost::optional<Gtk::TextIter> &start_iter, bool show_search_entry_, bool use_markup)
: start_mark(start_iter ? Source::Mark(*start_iter) : Source::Mark()), view(view_), window(Gtk::WindowType::WINDOW_POPUP), vbox(Gtk::Orientation::ORIENTATION_VERTICAL), list_view_text(use_markup), show_search_entry(show_search_entry_) {
window.set_transient_for(*Glib::RefPtr<Gtk::Application>::cast_dynamic(Gtk::Application::get_default())->get_active_window());
window.set_type_hint(Gdk::WindowTypeHint::WINDOW_TYPE_HINT_COMBO);
search_entry.signal_changed().connect([this] {
if(on_search_entry_changed)
on_search_entry_changed(search_entry.get_text());
}, false);
window.get_style_context()->add_class("juci_selection_dialog");
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);
@ -64,10 +66,7 @@ SelectionDialogBase::SelectionDialogBase(Gtk::TextView *text_view, const boost::
window.add(vbox);
list_view_text.signal_realize().connect([this]() {
auto g_application = g_application_get_default();
auto gio_application = Glib::wrap(g_application, true);
auto application = Glib::RefPtr<Gtk::Application>::cast_static(gio_application);
auto application_window = application->get_active_window();
auto application_window = Glib::RefPtr<Gtk::Application>::cast_dynamic(Gtk::Application::get_default())->get_active_window();
// Calculate window width and height
int row_width = 0, padding_height = 0, window_height = 0;
@ -84,12 +83,12 @@ SelectionDialogBase::SelectionDialogBase(Gtk::TextView *text_view, const boost::
++c;
}
if(this->text_view && row_width > this->text_view->get_width() * 2 / 3)
row_width = this->text_view->get_width() * 2 / 3;
if(view && row_width > view->get_width() * 2 / 3)
row_width = view->get_width() * 2 / 3;
else if(row_width > application_window->get_width() / 2)
row_width = application_window->get_width() / 2;
if(this->show_search_entry)
if(show_search_entry)
window_height += search_entry.get_height();
int window_width = row_width + 1;
window.resize(window_width, window_height);
@ -102,32 +101,32 @@ SelectionDialogBase::SelectionDialogBase(Gtk::TextView *text_view, const boost::
window.move(root_x, root_y);
};
if(this->text_view) {
if(view) {
Gdk::Rectangle visible_rect;
this->text_view->get_visible_rect(visible_rect);
view->get_visible_rect(visible_rect);
int visible_window_x, visible_window_max_y;
this->text_view->buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, visible_rect.get_x(), visible_rect.get_y() + visible_rect.get_height(), visible_window_x, visible_window_max_y);
view->buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, visible_rect.get_x(), visible_rect.get_y() + visible_rect.get_height(), visible_window_x, visible_window_max_y);
Gdk::Rectangle iter_rect;
this->text_view->get_iter_location(this->start_mark->get_iter(), iter_rect);
view->get_iter_location(start_mark->get_iter(), iter_rect);
int buffer_x = iter_rect.get_x();
int buffer_y = iter_rect.get_y() + iter_rect.get_height();
int window_x, window_y;
this->text_view->buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, buffer_x, buffer_y, window_x, window_y);
view->buffer_to_window_coords(Gtk::TextWindowType::TEXT_WINDOW_TEXT, buffer_x, buffer_y, window_x, window_y);
if(window_y < 0 || window_y > visible_window_max_y) // Move dialog to center if it is above or below visible parts of text_view
if(window_y < 0 || window_y > visible_window_max_y) // Move dialog to center if it is above or below visible parts of view
move_window_to_center();
else {
window_x = std::max(window_x, visible_window_x); // Adjust right if dialog is left of text_view
window_x = std::max(window_x, visible_window_x); // Adjust right if dialog is left of view
int root_x, root_y;
this->text_view->get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->get_root_coords(window_x, window_y, root_x, root_y);
view->get_window(Gtk::TextWindowType::TEXT_WINDOW_TEXT)->get_root_coords(window_x, window_y, root_x, root_y);
// Adjust left if dialog is right of screen
auto screen_width = Gdk::Screen::get_default()->get_width();
root_x = root_x + window_width > screen_width ? screen_width - window_width : root_x;
window.move(root_x, root_y + 1); //TODO: replace 1 with some margin
window.move(root_x, root_y + 1); // TODO: replace 1 with some margin
}
}
else
@ -139,6 +138,14 @@ SelectionDialogBase::SelectionDialogBase(Gtk::TextView *text_view, const boost::
});
}
SelectionDialogBase::~SelectionDialogBase() {
#if defined(__APPLE__) && GTK_VERSION_GT_MICRO_CORRECTED(3, 24, 34) && !GTK_VERSION_GT_MICRO_CORRECTED(3, 24, 37)
// Workaround for https://gitlab.gnome.org/GNOME/gtk/-/issues/5593 by keeping window alive slightly longer
window.close();
Glib::signal_timeout().connect([window = std::make_shared<Gtk::Window>(std::move(window))] { return false; }, 5000);
#endif
}
void SelectionDialogBase::cursor_changed() {
if(!is_visible())
return;
@ -166,8 +173,8 @@ void SelectionDialogBase::erase_rows() {
void SelectionDialogBase::show() {
window.show_all();
if(text_view)
text_view->grab_focus();
if(view)
view->grab_focus();
if(list_view_text.get_model()->children().size() > 0) {
if(!list_view_text.get_selection()->get_selected()) {
@ -175,11 +182,13 @@ void SelectionDialogBase::show() {
cursor_changed();
}
else if(list_view_text.get_model()->children().begin() != list_view_text.get_selection()->get_selected()) {
Glib::signal_idle().connect([this] {
if((this == SelectionDialog::get().get() || this == CompletionDialog::get().get()) && is_visible())
list_view_text.scroll_to_row(list_view_text.get_model()->get_path(list_view_text.get_selection()->get_selected()), 0.5);
return false;
});
Glib::signal_timeout().connect(
[this] {
if((this == SelectionDialog::get().get() || this == CompletionDialog::get().get()) && is_visible())
list_view_text.scroll_to_row(list_view_text.get_model()->get_path(list_view_text.get_selection()->get_selected()), 0.5);
return false;
},
0);
}
}
if(on_show)
@ -206,8 +215,8 @@ void SelectionDialogBase::hide() {
std::unique_ptr<SelectionDialog> SelectionDialog::instance;
SelectionDialog::SelectionDialog(Gtk::TextView *text_view, const boost::optional<Gtk::TextIter> &start_iter, bool show_search_entry, bool use_markup)
: SelectionDialogBase(text_view, start_iter, show_search_entry, use_markup) {
SelectionDialog::SelectionDialog(Source::BaseView *view, const boost::optional<Gtk::TextIter> &start_iter, bool show_search_entry, bool use_markup)
: SelectionDialogBase(view, start_iter, show_search_entry, use_markup) {
auto search_text = std::make_shared<std::string>();
auto filter_model = Gtk::TreeModelFilter::create(list_view_text.get_model());
@ -239,7 +248,7 @@ SelectionDialog::SelectionDialog(Gtk::TextView *text_view, const boost::optional
search_entry.signal_changed().connect([this, search_text, filter_model]() {
*search_text = search_entry.get_text();
filter_model->refilter();
list_view_text.set_search_entry(search_entry); //TODO:Report the need of this to GTK's git (bug)
list_view_text.set_search_entry(search_entry); // TODO:Report the need of this to GTK's git (bug)
if(search_text->empty()) {
if(list_view_text.get_model()->children().size() > 0)
list_view_text.set_cursor(list_view_text.get_model()->get_path(list_view_text.get_model()->children().begin()));
@ -267,6 +276,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;
}
@ -276,6 +287,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;
}
@ -298,31 +315,39 @@ bool SelectionDialog::on_key_press(GdkEventKey *event) {
return false;
}
else if(show_search_entry) {
#ifdef __APPLE__ //OS X bug most likely: Gtk::Entry will not work if window is of type POPUP
int search_entry_length = search_entry.get_text_length();
if(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)
#ifdef __APPLE__ // OS X bug most likely: Gtk::Entry will not work if window is of type POPUP
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);
@ -335,8 +360,8 @@ bool SelectionDialog::on_key_press(GdkEventKey *event) {
std::unique_ptr<CompletionDialog> CompletionDialog::instance;
CompletionDialog::CompletionDialog(Gtk::TextView *text_view, const Gtk::TextIter &start_iter) : SelectionDialogBase(text_view, start_iter, false, false) {
show_offset = text_view->get_buffer()->get_insert()->get_iter().get_offset();
CompletionDialog::CompletionDialog(Source::BaseView *view, const Gtk::TextIter &start_iter) : SelectionDialogBase(view, start_iter, false, false) {
show_offset = view->get_buffer()->get_insert()->get_iter().get_offset();
auto search_text = std::make_shared<std::string>();
auto filter_model = Gtk::TreeModelFilter::create(list_view_text.get_model());
@ -365,14 +390,14 @@ CompletionDialog::CompletionDialog(Gtk::TextView *text_view, const Gtk::TextIter
search_entry.signal_changed().connect([this, search_text, filter_model]() {
*search_text = search_entry.get_text();
filter_model->refilter();
list_view_text.set_search_entry(search_entry); //TODO:Report the need of this to GTK's git (bug)
list_view_text.set_search_entry(search_entry); // TODO:Report the need of this to GTK's git (bug)
});
list_view_text.signal_row_activated().connect([this](const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *) {
select();
});
auto text = text_view->get_buffer()->get_text(start_mark->get_iter(), text_view->get_buffer()->get_insert()->get_iter());
auto text = view->get_buffer()->get_text(start_mark->get_iter(), view->get_buffer()->get_insert()->get_iter());
if(text.size() > 0) {
search_entry.set_text(text);
list_view_text.set_search_entry(search_entry);
@ -394,10 +419,10 @@ bool CompletionDialog::on_key_release(GdkEventKey *event) {
(event->keyval >= GDK_KEY_Shift_L && event->keyval <= GDK_KEY_Hyper_R))
return false;
if(show_offset > text_view->get_buffer()->get_insert()->get_iter().get_offset())
if(show_offset > view->get_buffer()->get_insert()->get_iter().get_offset())
hide();
else {
auto text = text_view->get_buffer()->get_text(start_mark->get_iter(), text_view->get_buffer()->get_insert()->get_iter());
auto text = view->get_buffer()->get_text(start_mark->get_iter(), view->get_buffer()->get_insert()->get_iter());
search_entry.set_text(text);
list_view_text.set_search_entry(search_entry);
if(text == "") {
@ -410,9 +435,9 @@ 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(view->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());
view->get_buffer()->erase(start_mark->get_iter(), view->get_buffer()->get_insert()->get_iter());
row_in_entry = false;
if(event->keyval == GDK_KEY_BackSpace)
return true;
@ -430,6 +455,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()));
@ -444,6 +473,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();

24
src/selection_dialog.hpp

@ -5,6 +5,12 @@
#include <gtkmm.h>
#include <unordered_map>
// GTK_VERSION_GT_MICRO has a typo (in gtkmm/base.h)
#define GTK_VERSION_GT_MICRO_CORRECTED(major, minor, micro) \
((GTK_MAJOR_VERSION > major) || \
(GTK_MAJOR_VERSION == major) && (GTK_MINOR_VERSION > minor) || \
(GTK_MAJOR_VERSION == major) && (GTK_MINOR_VERSION == minor) && (GTK_MICRO_VERSION > micro))
class SelectionDialogBase {
class ListViewText : public Gtk::TreeView {
class ColumnRecord : public Gtk::TreeModel::ColumnRecord {
@ -38,8 +44,8 @@ class SelectionDialogBase {
};
public:
SelectionDialogBase(Gtk::TextView *text_view, const boost::optional<Gtk::TextIter> &start_iter, bool show_search_entry, bool use_markup);
virtual ~SelectionDialogBase() {}
SelectionDialogBase(Source::BaseView *view, const boost::optional<Gtk::TextIter> &start_iter, bool show_search_entry, bool use_markup);
virtual ~SelectionDialogBase();
void add_row(const std::string &row);
void erase_rows();
void set_cursor_at_last_row();
@ -59,7 +65,7 @@ public:
protected:
void cursor_changed();
Gtk::TextView *text_view;
Source::BaseView *view;
Gtk::Window window;
Gtk::Box vbox;
Gtk::ScrolledWindow scrolled_window;
@ -71,14 +77,14 @@ protected:
};
class SelectionDialog : public SelectionDialogBase {
SelectionDialog(Gtk::TextView *text_view, const boost::optional<Gtk::TextIter> &start_iter, bool show_search_entry, bool use_markup);
SelectionDialog(Source::BaseView *view, const boost::optional<Gtk::TextIter> &start_iter, bool show_search_entry, bool use_markup);
static std::unique_ptr<SelectionDialog> instance;
public:
bool on_key_press(GdkEventKey *event);
static void create(Gtk::TextView *text_view, bool show_search_entry = true, bool use_markup = false) {
instance = std::unique_ptr<SelectionDialog>(new SelectionDialog(text_view, text_view->get_buffer()->get_insert()->get_iter(), show_search_entry, use_markup));
static void create(Source::BaseView *view, bool show_search_entry = true, bool use_markup = false) {
instance = std::unique_ptr<SelectionDialog>(new SelectionDialog(view, view->get_buffer()->get_insert()->get_iter(), show_search_entry, use_markup));
}
static void create(bool show_search_entry = true, bool use_markup = false) {
instance = std::unique_ptr<SelectionDialog>(new SelectionDialog(nullptr, {}, show_search_entry, use_markup));
@ -87,15 +93,15 @@ public:
};
class CompletionDialog : public SelectionDialogBase {
CompletionDialog(Gtk::TextView *text_view, const Gtk::TextIter &start_iter);
CompletionDialog(Source::BaseView *view, const Gtk::TextIter &start_iter);
static std::unique_ptr<CompletionDialog> instance;
public:
bool on_key_release(GdkEventKey *event);
bool on_key_press(GdkEventKey *event);
static void create(Gtk::TextView *text_view, const Gtk::TextIter &start_iter) {
instance = std::unique_ptr<CompletionDialog>(new CompletionDialog(text_view, start_iter));
static void create(Source::BaseView *view, const Gtk::TextIter &start_iter) {
instance = std::unique_ptr<CompletionDialog>(new CompletionDialog(view, start_iter));
}
static std::unique_ptr<CompletionDialog> &get() { return instance; }

15
src/snippets.cpp

@ -1,8 +1,8 @@
#include "snippets.hpp"
#include "config.hpp"
#include "filesystem.hpp"
#include "json.hpp"
#include "terminal.hpp"
#include <boost/property_tree/json_parser.hpp>
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<Snippet>());
for(auto snippet_it = language_it->second.begin(); snippet_it != language_it->second.end(); ++snippet_it) {
auto key_string = snippet_it->second.get<std::string>("key", "");
JSON languages(snippets_file);
for(auto &language : languages.children()) {
snippets.emplace_back(std::regex(language.first), std::vector<Snippet>());
for(auto &snippet : language.second.array()) {
auto key_string = snippet.string_or("key", "");
guint key = 0;
GdkModifierType modifier = static_cast<GdkModifierType>(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<std::string>("prefix", ""), key, modifier, snippet_it->second.get<std::string>("body"), snippet_it->second.get<std::string>("description", "")});
snippets.back().second.emplace_back(Snippet{snippet.string_or("prefix", ""), key, modifier, snippet.string("body"), snippet.string_or("description", "")});
}
}
}

4
src/snippets.hpp

@ -17,8 +17,8 @@ public:
};
static Snippets &get() {
static Snippets singleton;
return singleton;
static Snippets instance;
return instance;
}
std::vector<std::pair<std::regex, std::vector<Snippet>>> snippets;

1547
src/source.cpp

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save